KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > key > PromptingKeyProvider


1 /*
2  * Copyright 2006 Schlichtherle IT Services
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package de.schlichtherle.key;
18
19 /**
20  * A "friendly" implementation of {@link KeyProvider} which prompts the user
21  * for a key for its protected resource, enforcing a three seconds suspension
22  * penalty if a wrong key was provided.
23  * The user is prompted via an instance of the {@link PromptingKeyProviderUI}
24  * user interface which is determined by the default instance of
25  * {@link PromptingKeyManager} as returned by {@link KeyManager#getInstance}.
26  * <p>
27  * Like its base class, this class does not impose a certain run time type
28  * of the key.
29  * It is actually the user interface implementation which determines the run
30  * time type of the key provided by {@link #getCreateKey} and
31  * {@link #getOpenKey}.
32  * Because the user interface implementation is determined by the singleton
33  * {@link PromptingKeyManager}, it is ultimately at the discretion of
34  * the key manager which type of keys are actually provided by this class.
35  * <p>
36  * Unlike its base class, instances of this class cannot get shared
37  * among multiple protected resources because each instance has a unique
38  * {@link #getResourceID() resource identifier} associated with it.
39  * Each try to share a key provider of this class among multiple protected
40  * resources with the singleton {@link KeyManager} will be prosecuted and
41  * sentenced with an {@link IllegalStateException} or, at the discretion of
42  * this class, some other {@link RuntimeException}.
43  * <p>
44  * This class is thread safe.
45  *
46  * @see PromptingKeyProviderUI
47  * @see KeyProvider
48  * @see PromptingKeyManager
49  * @author Christian Schlichtherle
50  * @version @version@
51  * @since TrueZIP 6.0
52  */

53 public class PromptingKeyProvider extends AbstractKeyProvider {
54
55     /**
56      * Used to lock out prompting by multiple threads.
57      * Note that the prompting methods in this class <em>must not</em> be
58      * synchronized on this instance since this would cause the Swing
59      * based default implementation of the key manager to dead lock.
60      * This is because the GUI is run from AWT's Event Dispatching Thread,
61      * which must call some methods of this instance while another thread
62      * is waiting for the key manager to return from prompting.
63      * Instead, the prompting methods use this object to lock out concurrent
64      * prompting by multiple threads.
65      */

66     private final PromptingLock lock = new PromptingLock();
67
68     /**
69      * The resource identifier for the protected resource.
70      */

71     private String JavaDoc resourceID;
72
73     /**
74      * The user interface instance which is used to prompt the user for a key.
75      */

76     private PromptingKeyProviderUI ui;
77
78     private State state = Reset.STATE;
79
80     /**
81      * Returns the unique resource identifier of the protected resource
82      * for which this key provider is used.
83      */

84     public synchronized String JavaDoc getResourceID() {
85         return resourceID;
86     }
87
88     final synchronized void setResourceID(String JavaDoc resourceID) {
89         this.resourceID = resourceID;
90     }
91
92     /**
93      * Returns the identifier which is used by the {@link PromptingKeyManager}
94      * to look up an instance of the {@link PromptingKeyProviderUI} user
95      * interface class which is then used to prompt the user for a key.
96      * The implementation in this class returns the fully qualified name
97      * of this class.
98      * <p>
99      * Subclasses which want to use a custom user interface should overwrite
100      * this method to return the fully qualified name of their respective
101      * class as the identifier and provide a custom
102      * <code>PromptingKeyManager</code> which has registered a
103      * <code>PromptingKeyProviderUI</code> class for this identifier.
104      */

105     protected String JavaDoc getUIClassID() {
106         return "PromptingKeyProvider"; // support code obfuscation!
107
}
108
109     private synchronized final PromptingKeyProviderUI getUI() {
110         return ui;
111     }
112
113     final synchronized void setUI(final PromptingKeyProviderUI ui) {
114         this.ui = ui;
115     }
116
117     private synchronized final State getState() {
118         return state;
119     }
120
121     private synchronized final void setState(final State state) {
122         this.state = state;
123     }
124
125     /**
126      * Returns a clone of the key which may be used to create a new
127      * protected resource or entirely replace the contents of an already
128      * existing protected resource.
129      * Returns the key itself if cloning it fails for some reason.
130      * If the key is an array, a shallow copy of the array is
131      * returned.
132      * <p>
133      * If required or explicitly requested by the user, the user is prompted
134      * for this key.
135      *
136      * @throws UnknownKeyException If the user has cancelled prompting or
137      * prompting has been disabled by the {@link PromptingKeyManager}.
138      * @throws RuntimeException If cloning the key results in a runtime
139      * exception.
140      *
141      * @see KeyProvider#getCreateKey
142      */

143     public final Object JavaDoc getCreateKey()
144     throws UnknownKeyException {
145         synchronized (lock) {
146             return getState().getCreateKey(this);
147         }
148     }
149
150     /**
151      * Prompts for a key to create or entirely overwrite a protected resource.
152      */

153     private Object JavaDoc promptCreateKey()
154     throws UnknownKeyException {
155         PromptingKeyManager.ensurePrompting();
156
157         final Object JavaDoc oldKey = getKey();
158
159         try {
160             final PromptingKeyProviderUI ui = getUI();
161             ui.promptCreateKey(this);
162         } catch (RuntimeException JavaDoc failure) {
163             // If the cause is an UnkownKeyException, pass it on without
164
// changing the state. This is used by the PromptingKeyProviderUI
165
// class to indicate that key prompting has been interrupted, so
166
// we could expect the cause to be a
167
// KeyPromptingInterruptedException actually.
168
final Throwable JavaDoc cause = failure.getCause();
169             if (cause instanceof UnknownKeyException)
170                 throw (UnknownKeyException) cause;
171             else
172                 throw failure;
173         }
174
175         resetKey(oldKey);
176
177         if (getKey() != null) {
178             setState(KeyChanged.STATE);
179             return cloneKey();
180         } else {
181             setState(Cancelled.STATE);
182             throw new KeyPromptingCancelledException();
183         }
184     }
185
186     /**
187      * Returns a clone of the key which may be used to open an
188      * existing protected resource in order to access its contents.
189      * Returns the key itself if cloning it fails for some reason.
190      * If the key is an array, a shallow copy of the array is
191      * returned.
192      * <p>
193      * If required, the user is prompted for this key.
194      * <p>
195      * This method enforces a three seconds suspension penalty if
196      * {@link #invalidOpenKey} was called by the same thread before
197      * in order to qualify as a "friendly" implementation.
198      *
199      * @throws UnknownKeyException If the user has cancelled prompting or
200      * prompting has been disabled by the {@link PromptingKeyManager}.
201      * @throws RuntimeException If cloning the key results in a runtime
202      * exception.
203      *
204      * @see KeyProvider#getOpenKey
205      */

206     protected final Object JavaDoc getOpenKeyImpl()
207     throws UnknownKeyException {
208         synchronized (lock) {
209             return getState().getOpenKey(this);
210         }
211     }
212
213     /**
214      * Prompts for a key to open a protected resource.
215      */

216     private Object JavaDoc promptOpenKey(final boolean invalid)
217     throws UnknownKeyException {
218         PromptingKeyManager.ensurePrompting();
219
220         final Object JavaDoc oldKey = getKey();
221
222         final boolean changeKey;
223         try {
224             final PromptingKeyProviderUI ui = getUI();
225             if (invalid)
226                 changeKey = ui.promptInvalidOpenKey(this);
227             else
228                 changeKey = ui.promptUnknownOpenKey(this);
229         } catch (RuntimeException JavaDoc failure) {
230             // If the cause is an UnkownKeyException, pass it on without
231
// changing the state. This is used by the PromptingKeyProviderUI
232
// class to indicate that key prompting has been interrupted, so
233
// we could expect the cause to be a
234
// KeyPromptingInterruptedException actually.
235
final Throwable JavaDoc cause = failure.getCause();
236             if (cause instanceof UnknownKeyException)
237                 throw (UnknownKeyException) cause;
238             else
239                 throw failure;
240         }
241
242         resetKey(oldKey);
243
244         if (getKey() != null) {
245             if (changeKey)
246                 setState(KeyChangeRequested.STATE);
247             else
248                 setState(KeyProvided.STATE);
249             return super.getOpenKey();
250         } else {
251             setState(Cancelled.STATE);
252             throw new KeyPromptingCancelledException();
253         }
254     }
255
256     private void resetKey(final Object JavaDoc oldKey) {
257         if (oldKey != null && oldKey != getKey()) {
258             final Object JavaDoc newKey = getKey();
259             try {
260                 setKey(oldKey);
261                 resetKey();
262             } finally {
263                 setKey(newKey);
264             }
265         }
266     }
267
268     /**
269      * Called to indicate that authentication of the key returned by
270      * {@link #getOpenKey()} has failed and to request an entirely different
271      * key.
272      * The user is prompted for a new key on the next call to
273      * {@link #getOpenKey}.
274      * Note that the user may actually not be prompted at the next call to
275      * {@link #getOpenKey} again if prompting has been disabled by the
276      * {@link PromptingKeyManager} or this provider is in a state where
277      * calling this method does not make any sense.
278      *
279      * @see KeyProvider#invalidOpenKey
280      */

281     protected final void invalidOpenKeyImpl() {
282         synchronized (lock) {
283             getState().invalidOpenKey(this);
284         }
285     }
286
287     /**
288      * Resets this key provider if and only if prompting for a key has been
289      * cancelled.
290      * It is safe to call this method while another thread is actually
291      * prompting for a key.
292      */

293     final void resetCancelledPrompt() {
294         getState().resetCancelledPrompt(this);
295     }
296
297     /**
298      * Resets this key provider and finally calls {@link #onReset()}.
299      */

300     public synchronized final void reset() {
301         setState(Reset.STATE);
302         try {
303             resetKey();
304         } finally {
305             onReset();
306         }
307     }
308
309     /**
310      * This hook is run after {@link #reset()} has been called.
311      * This method is called from the constructor in the class
312      * {@link AbstractKeyProvider}.
313      * The implementation in this class does nothing.
314      * May be overwritten by subclasses.
315      */

316     protected void onReset() {
317     }
318
319     /**
320      * Like the super class implementation, but throws an
321      * {@link IllegalStateException} if this instance is already mapped for
322      * another resource identifier.
323      *
324      * @throws IllegalStateException If this instance is already mapped for
325      * another resource identifier or mapping is prohibited
326      * by a constraint in a subclass. In the latter case, please refer
327      * to the subclass documentation for more information.
328      */

329     protected synchronized KeyProvider addToKeyManager(final String JavaDoc resourceID)
330     throws NullPointerException JavaDoc, IllegalStateException JavaDoc {
331         final String JavaDoc oldResourceID = getResourceID();
332         if (oldResourceID != null && !oldResourceID.equals(resourceID))
333             throw new IllegalStateException JavaDoc(
334                     "PromptingKeyProvider instances cannot be shared!");
335         final KeyProvider provider = super.addToKeyManager(resourceID);
336         setResourceID(resourceID);
337
338         return provider;
339     }
340
341     protected synchronized KeyProvider removeFromKeyManager(final String JavaDoc resourceID)
342     throws NullPointerException JavaDoc, IllegalStateException JavaDoc {
343         assert getResourceID() == resourceID;
344         final KeyProvider provider = super.removeFromKeyManager(resourceID);
345         setResourceID(null);
346
347         return provider;
348     }
349
350     //
351
// Shared (flyweight) state member classes.
352
//
353

354     private abstract static class State {
355         public abstract Object JavaDoc getCreateKey(PromptingKeyProvider provider)
356         throws UnknownKeyException;
357
358         public abstract Object JavaDoc getOpenKey(PromptingKeyProvider provider)
359         throws UnknownKeyException;
360
361         public void invalidOpenKey(PromptingKeyProvider provider) {
362         }
363
364         public void resetCancelledPrompt(PromptingKeyProvider provider) {
365         }
366     }
367
368     private static class Reset extends State {
369         private static final State STATE = new Reset();
370
371         public Object JavaDoc getCreateKey(PromptingKeyProvider provider)
372         throws UnknownKeyException {
373             return provider.promptCreateKey();
374         }
375
376         public Object JavaDoc getOpenKey(PromptingKeyProvider provider)
377         throws UnknownKeyException {
378             return provider.promptOpenKey(false);
379         }
380
381         public void invalidOpenKey(PromptingKeyProvider provider) {
382         }
383     }
384
385     private static class KeyInvalidated extends Reset {
386         private static final State STATE = new KeyInvalidated();
387
388         public Object JavaDoc getOpenKey(PromptingKeyProvider provider)
389         throws UnknownKeyException {
390             return provider.promptOpenKey(true);
391         }
392     }
393
394     private static class KeyProvided extends State {
395         private static final State STATE = new KeyProvided();
396
397         public Object JavaDoc getCreateKey(PromptingKeyProvider provider)
398         throws UnknownKeyException {
399             return provider.cloneKey();
400         }
401
402         public Object JavaDoc getOpenKey(PromptingKeyProvider provider) {
403             return provider.cloneKey();
404         }
405
406         public void invalidOpenKey(PromptingKeyProvider provider) {
407             provider.setState(KeyInvalidated.STATE);
408         }
409     }
410
411     private static class KeyChangeRequested extends KeyProvided {
412         private static final State STATE = new KeyChangeRequested();
413
414         public Object JavaDoc getCreateKey(PromptingKeyProvider provider)
415         throws UnknownKeyException {
416             return provider.promptCreateKey();
417         }
418     }
419
420     private static class KeyChanged extends KeyProvided {
421         private static final State STATE = new KeyChanged();
422
423         public void invalidOpenKey(PromptingKeyProvider provider) {
424         }
425     }
426
427     private static class Cancelled extends State {
428         private static final State STATE = new Cancelled();
429
430         public Object JavaDoc getCreateKey(PromptingKeyProvider provider)
431         throws UnknownKeyException {
432             throw new KeyPromptingCancelledException();
433         }
434
435         public Object JavaDoc getOpenKey(PromptingKeyProvider provider)
436         throws UnknownKeyException {
437             throw new KeyPromptingCancelledException();
438         }
439
440         public void invalidOpenKey(PromptingKeyProvider provider) {
441         }
442
443         public void resetCancelledPrompt(PromptingKeyProvider provider) {
444             provider.reset();
445         }
446     }
447
448     private static class PromptingLock { }
449 }
450
Popular Tags