+ Existing callers should use the Keyring directly from + any thread and should not try to avoid EDT anymore. +
++ SPI implementors should not be changed and they may + continue to assume that they will not be called directly + from EDT. +
++ It hasn't been allowed to call the Keyring from EDT. + This change removes the limitation as the need to read + password from the UI is not so rare. To resolve this people + had to code custom threading solution to prevent possible + deadlock on fallback implementation of the keyring API. +
+- XXX no answer for exec-threading + The API methods may be called from any thread. The SPI implementors may + assume that they will not be called directly from EDT.
The key identifier should be unique for the whole application, * so qualify it with any prefixes as needed. - *
Avoid calling methods on this class from the event dispatch thread, - * as some provider implementations may need to block while displaying a dialog - * (e.g. prompting for a master password to access the keyring). + *
Since 1.10 it is allowed to call methods of this class from even + * dispatch thread. */ -public class Keyring { +public final class Keyring { + + // throughput 1 is intentional + private static final RequestProcessor KEYRING_ACCESS = new RequestProcessor(Keyring.class); + + private static final long SAFE_DELAY = 70; private Keyring() {} @@ -82,43 +100,118 @@ return PROVIDER; } + private static synchronized char[] readImpl(String key) { + LOG.log(Level.FINEST, "reading: {0}", key); + return provider().read(key); + } + /** * Reads a key from the ring. + *
+ * This method can be called from any thread.
+ * All the changes done by previous calls to {@link #delete(java.lang.String)}
+ * or {@link #save(java.lang.String, char[], java.lang.String)} methods
+ * are guaranteed to be visible by subsequent calls to this method.
+ *
* @param key the identifier of the key
* @return its value if found (you may null out its elements), else null if not present
*/
- public static synchronized char[] read(String key) {
+ @NbBundle.Messages("MSG_KeyringAccess=Requesting keyring access")
+ @CheckForNull
+ public static char[] read(@NonNull final String key) {
Parameters.notNull("key", key);
- LOG.log(Level.FINEST, "reading: {0}", key);
- return provider().read(key);
+
+ try {
+ final Future
+ * This method can be called from any thread.
+ * The changes done by multiple calls to {@link #delete(java.lang.String)}
+ * or {@link #save(java.lang.String, char[], java.lang.String)} methods
+ * are guaranteed to be processed in order in which they were called.
+ *
* @param key a key identifier
* @param password the password or other sensitive information associated with the key
* (its contents will be nulled out by end of call)
* @param description a user-visible description of the key (may be null)
*/
- public static synchronized void save(String key, char[] password, String description) {
+ public static void save(@NonNull final String key, @NonNull final char[] password,
+ @NullAllowed final String description) {
+
Parameters.notNull("key", key);
Parameters.notNull("password", password);
- LOG.log(Level.FINEST, "saving: {0}", key);
- provider().save(key, password, description);
- Arrays.fill(password, (char) 0);
+
+ KEYRING_ACCESS.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Keyring.saveImpl(key, password, description);
+ }
+ });
+ }
+
+ private static synchronized void deleteImpl(String key) {
+ LOG.log(Level.FINEST, "deleting: {0}", key);
+ provider().delete(key);
}
/**
* Deletes a key from the ring.
* If the key was not in the ring to begin with, does nothing.
+ *
+ * This method can be called from any thread.
+ * The changes done by multiple calls to {@link #delete(java.lang.String)}
+ * or {@link #save(java.lang.String, char[], java.lang.String)} methods
+ * are guaranteed to be processed in order in which they were called.
+ *
* @param key a key identifier
*/
- public static synchronized void delete(String key) {
+ public static void delete(@NonNull final String key) {
Parameters.notNull("key", key);
- LOG.log(Level.FINEST, "deleting: {0}", key);
- provider().delete(key);
+
+ KEYRING_ACCESS.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Keyring.deleteImpl(key);
+ }
+ });
}
private static class DummyKeyringProvider implements KeyringProvider {
@@ -156,4 +249,29 @@
return result;
}
+ private static class ProgressRunnable