All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.key.PromptingKeyProvider Maven / Gradle / Ivy

Go to download

TrueZIP is a Java based Virtual File System (VFS) to enable transparent, multi-threaded read/write access to archive files (ZIP, TAR etc.) as if they were directories. Archive files may be arbitrarily nested and the nesting level is only limited by heap and file system size.

The newest version!
/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.key;

/**
 * A "friendly" implementation of {@link KeyProvider} which prompts the user
 * for a key for its protected resource, enforcing a three seconds suspension
 * penalty if a wrong key was provided.
 * The user is prompted via an instance of the {@link PromptingKeyProviderUI}
 * user interface which is determined by the default instance of
 * {@link PromptingKeyManager} as returned by {@link KeyManager#getInstance}.
 * 

* Like its base class, this class does not impose a certain run time type * of the key. * It is actually the user interface implementation which determines the run * time type of the key provided by {@link #getCreateKey} and * {@link #getOpenKey}. * Because the user interface implementation is determined by the singleton * {@link PromptingKeyManager}, it is ultimately at the discretion of * the key manager which type of keys are actually provided by this class. *

* Unlike its base class, instances of this class cannot get shared * among multiple protected resources because each instance has a unique * {@link #getResourceID() resource identifier} associated with it. * Each try to share a key provider of this class among multiple protected * resources with the singleton {@link KeyManager} will be prosecuted and * sentenced with an {@link IllegalStateException} or, at the discretion of * this class, some other {@link RuntimeException}. *

* This class is thread safe. * * @see PromptingKeyProviderUI * @see KeyProvider * @see PromptingKeyManager * @author Christian Schlichtherle * @version $Id$ * @since TrueZIP 6.0 */ public class PromptingKeyProvider extends AbstractKeyProvider { /** * Used to lock out prompting by multiple threads. * Note that the prompting methods in this class must not be * synchronized on this instance since this would cause the Swing * based default implementation of the key manager to dead lock. * This is because the GUI is run from AWT's Event Dispatching Thread, * which must call some methods of this instance while another thread * is waiting for the key manager to return from prompting. * Instead, the prompting methods use this object to lock out concurrent * prompting by multiple threads. */ private final PromptingLock lock = new PromptingLock(); /** * The resource identifier for the protected resource. */ private String resourceID; /** * The user interface instance which is used to prompt the user for a key. */ private PromptingKeyProviderUI ui; private State state = Reset.STATE; /** * Returns the unique resource identifier of the protected resource * for which this key provider is used. */ public synchronized String getResourceID() { return resourceID; } final synchronized void setResourceID(String resourceID) { this.resourceID = resourceID; } /** * Returns the identifier which is used by the {@link PromptingKeyManager} * to look up an instance of the {@link PromptingKeyProviderUI} user * interface class which is then used to prompt the user for a key. * The implementation in this class returns the fully qualified name * of this class. *

* Subclasses which want to use a custom user interface should overwrite * this method to return the fully qualified name of their respective * class as the identifier and provide a custom * {@code PromptingKeyManager} which has registered a * {@code PromptingKeyProviderUI} class for this identifier. */ protected String getUIClassID() { return "PromptingKeyProvider"; } private synchronized final PromptingKeyProviderUI getUI() { return ui; } final synchronized void setUI(final PromptingKeyProviderUI ui) { this.ui = ui; } private synchronized final State getState() { return state; } private synchronized final void setState(final State state) { this.state = state; } /** * Returns a clone of the key which may be used to create a new * protected resource or entirely replace the contents of an already * existing protected resource. * Returns the key itself if cloning it fails for some reason. * If the key is an array, a shallow copy of the array is * returned. *

* If required or explicitly requested by the user, the user is prompted * for this key. * * @throws UnknownKeyException If the user has cancelled prompting or * prompting has been disabled by the {@link PromptingKeyManager}. * @throws RuntimeException If cloning the key results in a runtime * exception. * * @see KeyProvider#getCreateKey */ public final Object getCreateKeyImpl() throws UnknownKeyException { synchronized (lock) { return getState().getCreateKey(this); } } /** * Prompts for a key to create or entirely overwrite a protected resource. */ private Object promptCreateKey() throws UnknownKeyException { PromptingKeyManager.ensurePrompting(); final Object oldKey = getKey(); try { getUI().promptCreateKey(this); } catch (RuntimeException failure) { // If the cause is an UnkownKeyException, pass it on without // changing the state. This is used by the PromptingKeyProviderUI // class to indicate that key prompting has been interrupted, so // we could expect the cause to be a // KeyPromptingInterruptedException actually. final Throwable cause = failure.getCause(); if (cause instanceof UnknownKeyException) throw (UnknownKeyException) cause; else throw failure; } resetKey(oldKey); final Object newKey = getKey(); if (newKey != null) { setState(KeyChanged.STATE); return newKey; } else { setState(Cancelled.STATE); throw new KeyPromptingCancelledException(); } } /** * Returns a clone of the key which may be used to open an * existing protected resource in order to access its contents. * Returns the key itself if cloning it fails for some reason. * If the key is an array, a shallow copy of the array is * returned. *

* If required, the user is prompted for this key. *

* This method enforces a three seconds suspension penalty if * {@link #invalidOpenKey} was called by the same thread before * in order to qualify as a "friendly" implementation. * * @throws UnknownKeyException If the user has cancelled prompting or * prompting has been disabled by the {@link PromptingKeyManager}. * @throws RuntimeException If cloning the key results in a runtime * exception. * * @see KeyProvider#getOpenKey */ protected final Object getOpenKeyImpl() throws UnknownKeyException { synchronized (lock) { return getState().getOpenKey(this); } } /** * Prompts for a key to open a protected resource. */ private Object promptOpenKey(final boolean invalid) throws UnknownKeyException { PromptingKeyManager.ensurePrompting(); final Object oldKey = getKey(); final boolean changeKey; try { final PromptingKeyProviderUI ui = getUI(); if (invalid) changeKey = ui.promptInvalidOpenKey(this); else changeKey = ui.promptUnknownOpenKey(this); } catch (RuntimeException failure) { // If the cause is an UnkownKeyException, pass it on without // changing the state. This is used by the PromptingKeyProviderUI // class to indicate that key prompting has been interrupted, so // we could expect the cause to be a // KeyPromptingInterruptedException actually. final Throwable cause = failure.getCause(); if (cause instanceof UnknownKeyException) throw (UnknownKeyException) cause; else throw failure; } resetKey(oldKey); final Object newKey = getKey(); if (newKey != null) { if (changeKey) setState(KeyChangeRequested.STATE); else setState(KeyProvided.STATE); return newKey; } else { setState(Cancelled.STATE); throw new KeyPromptingCancelledException(); } } private void resetKey(final Object oldKey) { if (oldKey != null && oldKey != getKey()) { final Object newKey = getKey(); try { setKey(oldKey); resetKey(); } finally { setKey(newKey); } } } /** * Called to indicate that authentication of the key returned by * {@link #getOpenKey()} has failed and to request an entirely different * key. * The user is prompted for a new key on the next call to * {@link #getOpenKey}. * Note that the user may actually not be prompted at the next call to * {@link #getOpenKey} again if prompting has been disabled by the * {@link PromptingKeyManager} or this provider is in a state where * calling this method does not make any sense. * * @see KeyProvider#invalidOpenKey */ protected final void invalidOpenKeyImpl() { synchronized (lock) { getState().invalidOpenKey(this); } } /** * Resets this key provider if and only if prompting for a key has been * cancelled. * It is safe to call this method while another thread is actually * prompting for a key. */ final void resetCancelledPrompt() { getState().resetCancelledPrompt(this); } /** * Resets this key provider and finally calls {@link #onReset()}. */ public synchronized final void reset() { setState(Reset.STATE); try { resetKey(); } finally { onReset(); } } /** * This hook is run after {@link #reset()} has been called. * This method is called from the constructor in the class * {@link AbstractKeyProvider}. * The implementation in this class does nothing. * May be overwritten by subclasses. */ protected void onReset() { } /** * Like the super class implementation, but throws an * {@link IllegalStateException} if this instance is already mapped for * another resource identifier. * * @throws IllegalStateException If this instance is already mapped for * another resource identifier or mapping is prohibited * by a constraint in a subclass. In the latter case, please refer * to the subclass documentation for more information. */ protected synchronized KeyProvider addToKeyManager(final String resourceID) throws NullPointerException, IllegalStateException { final String oldResourceID = getResourceID(); if (oldResourceID != null && !oldResourceID.equals(resourceID)) throw new IllegalStateException( "PromptingKeyProvider instances cannot be shared!"); final KeyProvider provider = super.addToKeyManager(resourceID); setResourceID(resourceID); return provider; } protected synchronized KeyProvider removeFromKeyManager(final String resourceID) throws NullPointerException, IllegalStateException { final KeyProvider provider = super.removeFromKeyManager(getResourceID()); setResourceID(null); return provider; } // // Shared (flyweight) state member classes. // private abstract static class State { public abstract Object getCreateKey(PromptingKeyProvider provider) throws UnknownKeyException; public abstract Object getOpenKey(PromptingKeyProvider provider) throws UnknownKeyException; public void invalidOpenKey(PromptingKeyProvider provider) { } public void resetCancelledPrompt(PromptingKeyProvider provider) { } } private static class Reset extends State { private static final State STATE = new Reset(); public Object getCreateKey(PromptingKeyProvider provider) throws UnknownKeyException { return provider.promptCreateKey(); } public Object getOpenKey(PromptingKeyProvider provider) throws UnknownKeyException { return provider.promptOpenKey(false); } public void invalidOpenKey(PromptingKeyProvider provider) { } } private static class KeyInvalidated extends Reset { private static final State STATE = new KeyInvalidated(); public Object getOpenKey(PromptingKeyProvider provider) throws UnknownKeyException { return provider.promptOpenKey(true); } } private static class KeyProvided extends State { private static final State STATE = new KeyProvided(); public Object getCreateKey(PromptingKeyProvider provider) throws UnknownKeyException { return provider.getKey(); } public Object getOpenKey(PromptingKeyProvider provider) { return provider.getKey(); } public void invalidOpenKey(PromptingKeyProvider provider) { provider.setState(KeyInvalidated.STATE); } } private static class KeyChangeRequested extends KeyProvided { private static final State STATE = new KeyChangeRequested(); public Object getCreateKey(PromptingKeyProvider provider) throws UnknownKeyException { return provider.promptCreateKey(); } } private static class KeyChanged extends KeyProvided { private static final State STATE = new KeyChanged(); public void invalidOpenKey(PromptingKeyProvider provider) { } } private static class Cancelled extends State { private static final State STATE = new Cancelled(); public Object getCreateKey(PromptingKeyProvider provider) throws UnknownKeyException { throw new KeyPromptingCancelledException(); } public Object getOpenKey(PromptingKeyProvider provider) throws UnknownKeyException { throw new KeyPromptingCancelledException(); } public void invalidOpenKey(PromptingKeyProvider provider) { } public void resetCancelledPrompt(PromptingKeyProvider provider) { provider.reset(); } } private static class PromptingLock { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy