KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > key > passwd > swing > PromptingKeyProviderUI


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.passwd.swing;
18
19 import de.schlichtherle.awt.EventQueue;
20 import de.schlichtherle.awt.EventDispatchTimeoutException;
21 import de.schlichtherle.key.*;
22
23 import java.awt.*;
24 import java.io.*;
25 import java.lang.reflect.*;
26 import java.util.*;
27 import java.util.logging.*;
28
29 import javax.swing.*;
30
31 /**
32  * A Swing based user interface to prompt for passwords or key files.
33  * This class is thread safe.
34  *
35  * @author Christian Schlichtherle
36  * @version @version@
37  * @since TrueZIP 6.0
38  */

39 public class PromptingKeyProviderUI
40         implements de.schlichtherle.key.PromptingKeyProviderUI {
41
42     private static final String JavaDoc PACKAGE_NAME
43             = "de/schlichtherle/key/passwd/swing".replace('/', '.'); // support code obfuscation!
44
private static final String JavaDoc CLASS_NAME
45             = PACKAGE_NAME + "/PromptingKeyProviderUI".replace('/', '.'); // support code obfuscation!
46
private static final ResourceBundle resources = ResourceBundle.getBundle(CLASS_NAME);
47     private static final Logger logger = Logger.getLogger(CLASS_NAME);
48
49     /**
50      * The timeout for the EDT to <em>start</em> prompting for a key in
51      * milliseconds.
52      */

53     private static final long START_PROMPTING_TIMEOUT = 1000;
54
55     /**
56      * This is the number of bytes to load from the beginning of a key file.
57      * A valid key file for encryption must contain at least this number of
58      * bytes!
59      */

60     // Must be a multiple of 2 and must be long enough so that
61
// GZIPOutputStream most likely produces more than 2 * 256 / 8 bytes
62
// output.
63
public static final int KEY_FILE_LEN = 512;
64
65     private static final Map openKeyPanels = new WeakHashMap();
66
67     /**
68      * The last resource ID used when prompting.
69      * Initialized to the empty string.
70      */

71     static String JavaDoc lastResourceID = "";
72
73     /**
74      * @deprecated This field is not used anymore and will be removed for the
75      * next major release number.
76      */

77     private CreateKeyPanel createKeyPanel;
78
79     /**
80      * @deprecated This field is not used anymore and will be removed for the
81      * next major release number.
82      */

83     private OpenKeyPanel openKeyPanel;
84     
85     private Feedback unknownCreateKeyFeedback;
86     private Feedback invalidCreateKeyFeedback;
87     private Feedback unknownOpenKeyFeedback;
88     private Feedback invalidOpenKeyFeedback;
89
90     /**
91      * Reads the encryption key as a byte sequence from the given pathname
92      * into a buffer of exactly <code>KEY_FILE_LEN</code> bytes and returns it.
93      *
94      * @throws EOFException If the file is not at least <code>KEY_FILE_LEN</code>
95      * bytes long.
96      * @throws IOException If an IOException occurs when opening, reading or
97      * closing the file.
98      */

99     static byte[] readKeyFile(String JavaDoc pathname)
100     throws IOException {
101         final byte[] buf = new byte[KEY_FILE_LEN];
102
103         final RandomAccessFile raf = new RandomAccessFile(pathname, "r");
104         try {
105             raf.readFully(buf);
106         } finally {
107             raf.close();
108         }
109
110         return buf;
111     }
112
113     /**
114      * @deprecated This method is not used anymore and will be removed for the
115      * next major release number.
116      * It's use may dead lock the GUI.
117      * Use {@link #createCreateKeyPanel} instead.
118      */

119     protected CreateKeyPanel getCreateKeyPanel() {
120         if (createKeyPanel == null)
121             createKeyPanel = createCreateKeyPanel();
122         return createKeyPanel;
123     }
124
125     /**
126      * A factory method to create the Create Protected Resource Key Panel.
127      */

128     protected CreateKeyPanel createCreateKeyPanel() {
129         return new CreateKeyPanel();
130     }
131
132     /**
133      * @deprecated This method is not used anymore and will be removed for the
134      * next major release number.
135      * It's use may dead lock the GUI.
136      * Use {@link #createOpenKeyPanel} instead.
137      */

138     protected OpenKeyPanel getOpenKeyPanel() {
139         if (openKeyPanel == null)
140             openKeyPanel = createOpenKeyPanel();
141         return openKeyPanel;
142     }
143
144     /**
145      * A factory method to create the Open Protected Resource Key Panel.
146      */

147     protected OpenKeyPanel createOpenKeyPanel() {
148         return new OpenKeyPanel();
149     }
150
151     protected Feedback getUnknownCreateKeyFeedback() {
152         if (unknownCreateKeyFeedback == null)
153             unknownCreateKeyFeedback = createFeedback("UnknownCreateKeyFeedback");
154         return unknownCreateKeyFeedback;
155     }
156
157     protected void setUnkownCreateKeyFeedback(final Feedback uckf) {
158         this.unknownCreateKeyFeedback = uckf;
159     }
160
161     protected Feedback getInvalidCreateKeyFeedback() {
162         if (invalidCreateKeyFeedback == null)
163             invalidCreateKeyFeedback = createFeedback("InvalidCreateKeyFeedback");
164         return invalidCreateKeyFeedback;
165     }
166
167     protected void setInvalidCreateKeyFeedback(final Feedback ickf) {
168         this.invalidCreateKeyFeedback = ickf;
169     }
170
171     protected Feedback getUnknownOpenKeyFeedback() {
172         if (unknownOpenKeyFeedback == null)
173             unknownOpenKeyFeedback = createFeedback("UnknownOpenKeyFeedback");
174         return unknownOpenKeyFeedback;
175     }
176
177     protected void setUnknownOpenKeyFeedback(final Feedback uokf) {
178         this.unknownOpenKeyFeedback = uokf;
179     }
180
181     protected Feedback getInvalidOpenKeyFeedback() {
182         if (invalidOpenKeyFeedback == null)
183             invalidOpenKeyFeedback = createFeedback("InvalidOpenKeyFeedback");
184         return invalidOpenKeyFeedback;
185     }
186
187     private static Feedback createFeedback(final String JavaDoc type) {
188         try {
189             String JavaDoc n = System.getProperty(
190                     PACKAGE_NAME + "." + type,
191                     PACKAGE_NAME + ".Basic" + type);
192             Thread JavaDoc t = Thread.currentThread();
193             ClassLoader JavaDoc l = t.getContextClassLoader();
194             Class JavaDoc c = l.loadClass(n);
195             Feedback f = (Feedback) c.newInstance();
196             return f;
197         } catch (ClassNotFoundException JavaDoc ex) {
198             logger.log(Level.WARNING, "", ex);
199         } catch (IllegalAccessException JavaDoc ex) {
200             logger.log(Level.WARNING, "", ex);
201         } catch (InstantiationException JavaDoc ex) {
202             logger.log(Level.WARNING, "", ex);
203         }
204         return null;
205     }
206
207     protected void setInvalidOpenKeyFeedback(final Feedback iokf) {
208         this.invalidOpenKeyFeedback = iokf;
209     }
210
211     public /*synchronized*/ final void promptCreateKey(
212             final PromptingKeyProvider provider) {
213         final Runnable JavaDoc task = new Runnable JavaDoc() {
214             public void run() {
215                 promptCreateKey(provider, null);
216             }
217         };
218         multiplexOnEDT(task); // synchronized on class instance!
219
}
220
221     public /*synchronized*/ final boolean promptUnknownOpenKey(
222             final PromptingKeyProvider provider) {
223         final BooleanRunnable task = new BooleanRunnable() {
224             public void run() {
225                 result = promptOpenKey(provider, false, null);
226             }
227         };
228         multiplexOnEDT(task); // synchronized on class instance!
229
return task.result;
230     }
231
232     public /*synchronized*/ final boolean promptInvalidOpenKey(
233             final PromptingKeyProvider provider) {
234         final BooleanRunnable task = new BooleanRunnable() {
235             public void run() {
236                 result = promptOpenKey(provider, true, null);
237             }
238         };
239         multiplexOnEDT(task); // synchronized on class instance!
240
return task.result;
241     }
242
243     private abstract static class BooleanRunnable implements Runnable JavaDoc {
244         public boolean result;
245     }
246
247     /**
248      * This method is only called by the AWT Event Dispatch Thread,
249      * so it doesn't need to be thread safe.
250      */

251     protected void promptCreateKey(
252             final PromptingKeyProvider provider,
253             final JComponent extraDataUI) {
254         assert EventQueue.isDispatchThread();
255
256         final CreateKeyPanel createKeyPanel = createCreateKeyPanel();
257         createKeyPanel.setExtraDataUI(extraDataUI);
258
259         final Window parent = PromptingKeyManager.getParentWindow();
260         try {
261             final Thread JavaDoc curThread = Thread.currentThread();
262             while (!curThread.interrupted()) { // test and clear status!
263
// Setting this inside the loop has the side effect of
264
// de-highlighting the resource ID in the panel if the
265
// loop iteration has to be repeated due to an invalid
266
// user input.
267
createKeyPanel.setResourceID(provider.getResourceID());
268                 createKeyPanel.setFeedback(createKeyPanel.getError() != null
269                         ? getInvalidCreateKeyFeedback()
270                         : getUnknownCreateKeyFeedback());
271
272                 final int result;
273                 try {
274                     result = JOptionPane.showConfirmDialog(
275                             parent,
276                             createKeyPanel,
277                             resources.getString("newPasswdDialog.title"),
278                             JOptionPane.OK_CANCEL_OPTION,
279                             JOptionPane.QUESTION_MESSAGE);
280                 } catch (StackOverflowError JavaDoc failure) {
281                     // Workaround for bug ID #6471418 - should be fixed in
282
// JSE 1.5.0_11
283
boolean interrupted = curThread.interrupted(); // test and clear status!
284
assert interrupted;
285                     break;
286                 }
287                 if (curThread.interrupted()) // test and clear status!
288
break;
289
290                 if (result != JOptionPane.OK_OPTION)
291                     break; // reuse old key
292

293                 final Object JavaDoc createKey = createKeyPanel.getCreateKey();
294                 if (createKey != null) { // valid input?
295
provider.setKey(createKey);
296                     break;
297                 }
298
299                 // Continue looping until valid input.
300
assert createKeyPanel.getError() != null;
301             }
302         } finally {
303             // Do NOT do this within the loop - would close the next
304
// JOptionPane on repeat.
305
eventuallyDispose(parent);
306         }
307     }
308
309     /**
310      * This method is only called by the AWT Event Dispatch Thread,
311      * so it doesn't need to be thread safe.
312      */

313     protected boolean promptOpenKey(
314             final PromptingKeyProvider provider,
315             final boolean invalid,
316             final JComponent extraDataUI) {
317         assert EventQueue.isDispatchThread();
318
319         final OpenKeyPanel openKeyPanel;
320         if (invalid) {
321             OpenKeyPanel panel = (OpenKeyPanel) openKeyPanels.get(provider);
322             openKeyPanel = panel != null ? panel : createOpenKeyPanel();
323             openKeyPanel.setError(resources.getString("invalidKey"));
324         } else {
325             openKeyPanel = createOpenKeyPanel();
326             openKeyPanel.setExtraDataUI(extraDataUI);
327         }
328         openKeyPanels.put(provider, openKeyPanel);
329
330         final Window parent = PromptingKeyManager.getParentWindow();
331         try {
332             final Thread JavaDoc curThread = Thread.currentThread();
333             while (!curThread.interrupted()) { // test and clear status!
334
// Setting this inside the loop has the side effect of
335
// de-highlighting the resource ID in the panel if the
336
// loop iteration has to be repeated due to an invalid
337
// user input.
338
openKeyPanel.setResourceID(provider.getResourceID());
339                 openKeyPanel.setFeedback(openKeyPanel.getError() != null
340                         ? getInvalidOpenKeyFeedback()
341                         : getUnknownOpenKeyFeedback());
342
343                 final int result;
344                 try {
345                     result = JOptionPane.showConfirmDialog(
346                             parent,
347                             openKeyPanel,
348                             resources.getString("passwdDialog.title"),
349                             JOptionPane.OK_CANCEL_OPTION,
350                             JOptionPane.QUESTION_MESSAGE);
351                 } catch (StackOverflowError JavaDoc failure) {
352                     // Workaround for bug ID #6471418 - should be fixed in
353
// JSE 1.5.0_11
354
boolean interrupted = curThread.interrupted(); // test and clear status!
355
assert interrupted;
356                     break;
357                 }
358                 if (curThread.interrupted()) // test and clear status!
359
break;
360
361                 if (result != JOptionPane.OK_OPTION) {
362                     provider.setKey(null);
363                     break;
364                 }
365
366                 final Object JavaDoc openKey = openKeyPanel.getOpenKey();
367                 if (openKey != null) { // valid input?
368
provider.setKey(openKey);
369                     break;
370                 }
371
372                 // Continue looping until valid input.
373
assert openKeyPanel.getError() != null;
374             }
375         } finally {
376             // Do NOT do this within the loop - would close the next
377
// JOptionPane on repeat.
378
eventuallyDispose(parent);
379         }
380
381         return openKeyPanel.isKeyChangeRequested();
382     }
383
384     /**
385      * The following is a double work around for Sun's J2SE 1.4.2
386      * which has been tested with 1.4.2-b28 and 1.4.2_12-b03 on the
387      * Windows platform.
388      * The issue for which this work around is required is known
389      * to be present in the Java code of the AWT package, so this
390      * should pertain to all platforms.
391      * This issue has been fixed with Sun's JSE 1.5.0-b64 or higher.
392      * <p>
393      * The issue is that an application will not terminate until all
394      * Window's have been dispose()d or System.exit() has been called -
395      * it is not sufficient just to hide() all Window's.
396      * <p>
397      * The JOptionPane properly dispose()s its Dialog which displays our
398      * password panels.
399      * However, by default <code>JOptionPane</code> uses an internal
400      * <code>Frame</code> as its parent window if the application does not
401      * specify a parent window explicitly.
402      * This class also uses this frame unless the client application has
403      * called {@link PromptingKeyManager#setParentWindow()}.
404      * <code>JOptionPane</code> never dispose()s the parent window, so the
405      * client application may eventually not terminate.
406      * <p>
407      * The workaround is to dispose the parent window if it's not showing.
408      */

409     private static void eventuallyDispose(final Window window) {
410         if (!window.isShowing())
411             window.dispose();
412     }
413
414     /**
415      * Invokes the given <code>task</code> on the AWT Event Dispatching Thread
416      * (EDT) and waits until it's finished.
417      * <p>
418      * In multithreaded environments, although technically possible,
419      * do not allow multiple threads to prompt for a key concurrently,
420      * because this would only confuse users.
421      * By explicitly locking the class object rather than the instance,
422      * we enforce this even if multiple implementations and instances
423      * are used.
424      * <p>
425      * If the current thread is interrupted, an
426      * {@link UndeclaredThrowableException} is thrown with a
427      * {@link KeyPromptingInterruptedException} as its cause.
428      * <p>
429      * If a {@link Throwable} is thrown by the EDT, then it's wrapped in an
430      * {@link UndeclaredThrowableException} and re-thrown by this thread.
431      */

432     private static void multiplexOnEDT(final Runnable JavaDoc task) {
433         if (Thread.currentThread().interrupted())
434             throw new UndeclaredThrowableException(
435                     new KeyPromptingInterruptedException());
436
437         if (EventQueue.isDispatchThread()) {
438             task.run();
439         } else {
440             synchronized (PromptingKeyProviderUI.class) {
441                 try {
442                     EventQueue.invokeAndWaitUninterruptibly(
443                             task, START_PROMPTING_TIMEOUT);
444                 } catch (EventDispatchTimeoutException failure) {
445                     // Timeout while waiting for the EDT to start the task.
446
// Now wrap this in two exceptions: The outermost exception will
447
// be catched by the PromptingKeyProvider class and its cause
448
// will be unwrapped and passed on to the client application by
449
// the PromptingKeyProvider class.
450
throw new UndeclaredThrowableException(
451                             new KeyPromptingTimeoutException(failure));
452                 /*} catch (InterruptedException failure) {
453                     // We've been interrupted while waiting for the EDT.
454                     // Now wrap this in two exceptions: The outermost exception will
455                     // be catched by the PromptingKeyProvider class and its cause
456                     // will be unwrapped and passed on to the client application by
457                     // the PromptingKeyProvider class.
458                     throw new UndeclaredThrowableException(
459                             new KeyPromptingInterruptedException(failure));
460                 */
} catch (InvocationTargetException failure) {
461                     throw new UndeclaredThrowableException(failure);
462                 } finally {
463                     Thread.currentThread().interrupted();
464                 }
465             }
466         }
467     }
468 }
469
Popular Tags