net.java.truecommons.key.spec.prompting.SharedKeyProvider Maven / Gradle / Ivy
Show all versions of truecommons-key-spec Show documentation
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package net.java.truecommons.key.spec.prompting;
import net.java.truecommons.key.spec.PersistentUnknownKeyException;
import net.java.truecommons.key.spec.UnknownKeyException;
import net.java.truecommons.key.spec.util.SuspensionPenalty;
import net.java.truecommons.shed.UniqueObject;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* @param the type of the prompting keys.
* @see SharedKeyManager
* @since TrueCommons 2.2
* @author Christian Schlichtherle
*/
@ThreadSafe
final class SharedKeyProvider>
extends UniqueObject {
private final ThreadLocal invalidated = new ThreadLocal<>();
private @Nullable K key;
private @Nullable PersistentUnknownKeyException exception;
private volatile State state = State.RESET;
private int count;
synchronized void link() {
++count;
assert 0 <= count;
}
synchronized void unlink() {
if (0 >= --count)
resetUnconditionally();
assert 0 <= count;
}
synchronized void release() {
resetCancelledKey();
}
/**
* {@inheritDoc}
*
* The implementation in {@link SharedKeyProvider} forwards the call to
* {@link #setupKeyForWriting}.
*
* @throws UnknownKeyException If {@code setupKeyForWriting} throws
* this exception or the key is still {@code null}.
*/
K getKeyCloneForWriting(final PromptingKeyProvider provider)
throws UnknownKeyException {
setupKeyForWriting(provider);
return getNonNullKeyClone();
}
/**
* Sets up the key for (over)writing a protected resource.
*
* @throws UnknownKeyException If the key is unknown.
* At the subclasses discretion, this may mean that prompting for
* the key has been disabled or cancelled by the user.
* @see #getKeyCloneForWriting
*/
private void setupKeyForWriting(final PromptingKeyProvider provider)
throws UnknownKeyException {
State os, ns = this.state;
do {
os = ns;
os.setupKeyForWriting(provider);
ns = this.state;
} while (ns != os);
}
/**
* {@inheritDoc}
*
* The implementation in {@link SharedKeyProvider} forwards the call to
* {@link #setupKeyForReading} and enforces a three seconds suspension penalty
* if {@code invalid} is {@code true} before returning.
* Because this method is final, this qualifies the implementation in
* this class as a "safe" {@code KeyProvider} implementation,
* even when subclassed.
*
* @throws UnknownKeyException If {@code setupKeyForReading} throws
* this exception or the key is still {@code null}.
*/
K getKeyCloneForReading(
final PromptingKeyProvider provider,
final boolean invalid)
throws UnknownKeyException {
if (invalid)
invalidated.set(System.currentTimeMillis());
try {
setupKeyForReading(provider, invalid);
} finally {
final Long invalidated = this.invalidated.get();
SuspensionPenalty.enforce(null == invalidated ? 0 : invalidated);
}
return getNonNullKeyClone();
}
/**
* Sets up the key for reading a protected resource.
*
* @throws UnknownKeyException If the key is unknown.
* At the subclasses discretion, this may mean that prompting for
* the key has been disabled or cancelled by the user.
* @see #getKeyCloneForReading
*/
private void setupKeyForReading(
final PromptingKeyProvider provider,
boolean invalid)
throws UnknownKeyException {
State os, ns = this.state;
do {
os = ns;
invalid = os.setupKeyForReading(provider, invalid);
ns = this.state;
} while (ns != os);
}
private K getNonNullKeyClone() throws UnknownKeyException {
final K key = getKeyClone();
if (null == key)
throw new UnknownKeyException();
return key;
}
synchronized @Nullable K getKeyClone() {
final K key = this.key;
return null == key ? null : key.clone();
}
synchronized void setKeyClone(final @Nullable K key) {
final K old = this.key;
final boolean set = null != key;
this.key = set ? key.clone() : null;
this.state = set ? State.SET : State.CANCELLED;
if (null != old)
old.reset();
}
synchronized @Nullable PersistentUnknownKeyException getException() {
return exception;
}
synchronized void setException(
final @Nullable PersistentUnknownKeyException exception) {
if (null != (this.exception = exception))
setKeyClone(null);
}
/**
* Resets the state of this key provider
* if and only if prompting for a key has been cancelled.
*/
void resetCancelledKey() { state.resetCancelledKey(this); }
synchronized void resetUnconditionally() {
setKeyClone(null);
setException(null);
this.state = State.RESET;
}
synchronized boolean isChangeRequested() {
return null != key && key.isChangeRequested();
}
/** Implements the behavior strategy for prompting key providers. */
private enum State {
RESET {
@Override
> void setupKeyForWriting(
final PromptingKeyProvider provider)
throws UnknownKeyException {
provider.promptKeyForWriting();
}
@Override
> boolean setupKeyForReading(
final PromptingKeyProvider provider,
final boolean invalid)
throws UnknownKeyException {
provider.promptKeyForReading(invalid);
return false;
}
@Override
> void resetCancelledKey(
SharedKeyProvider provider) {
}
},
SET {
@Override
> void setupKeyForWriting(
final PromptingKeyProvider provider)
throws UnknownKeyException {
if (provider.isChangeRequested())
provider.resetUnconditionally();
}
@Override
> boolean setupKeyForReading(
final PromptingKeyProvider provider,
final boolean invalid)
throws UnknownKeyException {
if (invalid)
provider.resetUnconditionally();
return invalid;
}
@Override
> void resetCancelledKey(
SharedKeyProvider provider) {
}
},
CANCELLED {
@Override
> void setupKeyForWriting(
PromptingKeyProvider provider)
throws UnknownKeyException {
throw exception(provider);
}
@Override
> boolean setupKeyForReading(
PromptingKeyProvider provider,
boolean invalid)
throws UnknownKeyException {
throw exception(provider);
}
> PersistentUnknownKeyException exception(
PromptingKeyProvider provider) {
PersistentUnknownKeyException ex = provider.getException();
if (null == ex)
provider.setException(ex = new KeyPromptingCancelledException());
return ex;
}
@Override
> void resetCancelledKey(
SharedKeyProvider provider) {
provider.resetUnconditionally();
}
};
abstract > void setupKeyForWriting(
PromptingKeyProvider provider)
throws UnknownKeyException;
abstract > boolean setupKeyForReading(
PromptingKeyProvider provider,
boolean invalid)
throws UnknownKeyException;
abstract > void resetCancelledKey(
SharedKeyProvider provider);
}
}