tfw.tsm.Initiator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tfw Show documentation
Show all versions of tfw Show documentation
The FrameWork for building highly scalable and maintainable applications
The newest version!
package tfw.tsm;
import java.util.ArrayList;
import java.util.List;
import tfw.check.Argument;
import tfw.tsm.ecd.EventChannelDescription;
import tfw.tsm.ecd.ObjectECD;
import tfw.tsm.ecd.StatelessTriggerECD;
import tfw.value.ValueException;
/**
* This class provides an interface to generate a transaction and to set the
* state of event channels in that transaction. The initiator will queue state
* changes if it is not rooted. The queueing strategy can be set by providing
* the appropriate {@link StateQueueFactory} on the constructor
* {@link #Initiator(String, EventChannelDescription[], StateQueueFactory)}.
* The default queuing strategy employs an unbounded queue which will store up
* state changes until the initiator becomes rooted and can begin to fire its
* state changes. Note that a component is said to be rooted when it is attached
* to a root component or its parent is rooted.
*
* State Change Rules:
*
* - If one of the 'set' or 'trigger' methods is called while the
* Initiator is rooted, the state changes will be immediately
* scheduled with the transaction manager.
* - If one of the 'set' or 'trigger' methods is called while the
* Initiator is not rooted the state changes will be queued,
* according to the queuing strategy supplied at construction, and deferred
* until such time as the Initiator becomes rooted.
* - An Initiator will schedule all of its deferred state changes
* with the transaction manager immediately upon becoming rooted.
* - If the Initiator becomes un-rooted after state changes have
* been scheduled with the transaction manager and before the state changes have
* occurred, the state changes will occur if and only if the associated event
* channel is rooted when the state change transaction is executed.
*
*/
public class Initiator extends TreeComponent {
/**
* The list of state changes which occur while the component is not
* connected.
*/
private List deferredStateChanges = null;
/**
* Constructs an Initiator
with the specified name and source
* event channel.
*
* @param name
* the non-null name for the Initiator
* @param source
* the source event channel for this initiator.
*/
public Initiator(String name, EventChannelDescription source) {
this(name, new EventChannelDescription[] {source});
}
/**
* Constructs an Initiator
with the specified name and set of
* source event channels.
*
* @param name
* the non-null name for the Initiator
* @param sources
* A non-null, non-empty array of source event channels for the
* Initiator
.
*/
public Initiator(String name, EventChannelDescription[] sources) {
this(name, sources, new DefaultStateQueueFactory());
}
/**
* Constructs an Initiator
with the specified name, set of
* source event channels, and state queue.
*
* @param name
* the non-null name for the Initiator
* @param sources
* A non-null, non-empty array of source event channels for the
* Initiator
.
* @param queueFactory
* a factory for creating state queue for each source event
* channel.
*/
public Initiator(String name, EventChannelDescription[] sources, StateQueueFactory queueFactory) {
super(name, null, createSources(name, sources, queueFactory), null);
Argument.assertNotNull(name, "name");
if (sources.length == 0) {
throw new IllegalArgumentException("sources.length == 0 not allowed");
}
}
public static InitiatorBuilder builder() {
return new InitiatorBuilder();
}
synchronized TransactionContainer[] getDeferredStateChangesAndClear() {
if (deferredStateChanges != null) {
TransactionContainer[] sns =
deferredStateChanges.toArray(new TransactionContainer[deferredStateChanges.size()]);
deferredStateChanges = null;
return sns;
} else {
return null;
}
}
private static Source[] createSources(String name, EventChannelDescription[] sources, StateQueueFactory factory) {
Argument.assertNotNull(sources, "sources");
Argument.assertElementNotNull(sources, "sources");
Argument.assertNotNull(factory, "factory");
Source[] srcs = new Source[sources.length];
for (int i = 0; i < sources.length; i++) {
srcs[i] = new InitiatorSource(name, sources[i], factory.create());
}
return srcs;
}
private TransactionState newTransaction(InitiatorSource[] sources, Object[] state) {
Root localImmediateRoot = null;
BranchComponent localImmediateParent = null;
synchronized (this) {
localImmediateRoot = immediateRoot;
localImmediateParent = immediateParent;
}
if (localImmediateRoot == null) {
TransactionState transactionState = new DeferredTransactionState();
if (localImmediateParent == null) {
synchronized (this) {
if (deferredStateChanges == null) {
deferredStateChanges = new ArrayList();
}
deferredStateChanges.add(
new TransactionContainer(new SourceNState(sources, state), transactionState));
}
} else {
localImmediateParent.addStateChange(
new TransactionContainer(new SourceNState(sources, state), transactionState));
}
return transactionState;
} else {
TransactionState transactionState = localImmediateRoot
.getTransactionManager()
.getTransactionQueue()
.createTransactionState();
localImmediateRoot
.getTransactionManager()
.addStateChange(
sources,
state,
transactionState,
TransactionMgr.isTraceLogging() ? new Throwable("StateChange") : null);
return transactionState;
}
}
/**
* Sets, asynchronously, the state of the specified event channel to the
* specified state. Note that the sourceEventChannel
will be
* updated in transaction manager event queue thread regardless of what
* thread calls this method.
*
* @param sourceEventChannel
* The event channel to be updated. It must be one of the event
* channels specified at construction.
*
* @param state
* the new state for the event channel.
*/
private TransactionState unifiedSet(EventChannelDescription sourceEventChannel, final Object state) {
Argument.assertNotNull(sourceEventChannel, "sourceEventChannel");
// Trigger have null values...
// CheckArgument.checkNull(state, "state");
final InitiatorSource source = (InitiatorSource) getSource(sourceEventChannel.getEventChannelName());
if (source == null) {
throw new IllegalArgumentException(sourceEventChannel + " not found");
}
try {
source.ecd.getConstraint().checkValue(state);
} catch (ValueException ve) {
throw new IllegalArgumentException(ve.getMessage());
}
return newTransaction(new InitiatorSource[] {source}, new Object[] {state});
}
/**
* Sets the state of the eventChannels with values specified.
*
* @param state
* the event channel state to set.
*/
public final void set(EventChannelState[] state) {
fset(state);
}
public final TransactionState fset(EventChannelState[] state) {
Argument.assertNotEmpty(state, "state");
InitiatorSource[] sources = new InitiatorSource[state.length];
Object[] stateObjects = new Object[state.length];
for (int i = 0; i < state.length; i++) {
String eventChannel = state[i].getEventChannelName();
InitiatorSource source = (InitiatorSource) getSource(eventChannel);
if (source == null) {
throw new IllegalArgumentException(eventChannel + " not found");
}
sources[i] = source;
stateObjects[i] = state[i].getState();
}
return newTransaction(sources, stateObjects);
}
/**
* Sets, asynchronously, the state of the specified event channel to
* new Object()
. This is intended to be used to activate
* {@link TriggeredConverter}s where the state changes is not relevant.
*
* @param triggerEventChannel
* The event channel to receive the trigger event.
*/
public final void trigger(StatelessTriggerECD triggerEventChannel) {
ftrigger(triggerEventChannel);
}
public final TransactionState ftrigger(StatelessTriggerECD triggerEventChannel) {
return unifiedSet(triggerEventChannel, null);
}
public final void set(ObjectECD objectECD, Object state) {
fset(objectECD, state);
}
public final TransactionState fset(ObjectECD objectECD, Object state) {
return unifiedSet(objectECD, state);
}
static class SourceNState {
public final InitiatorSource[] sources;
public final Object[] state;
public SourceNState(InitiatorSource[] sources, Object[] state) {
this.sources = sources;
this.state = state;
}
}
static class TransactionContainer {
public final SourceNState state;
public final TransactionState transactionState;
public final Throwable setLocation;
public TransactionContainer(SourceNState state, TransactionState transactionState) {
this.state = state;
this.transactionState = transactionState;
this.setLocation = TransactionMgr.isTraceLogging() ? new Throwable("StateChange") : null;
}
}
public static class InitiatorBuilder {
private String name = null;
private List eventChannelDescriptions = new ArrayList<>();
private StateQueueFactory stateQueueFactory = new DefaultStateQueueFactory();
InitiatorBuilder() {}
public InitiatorBuilder setName(final String name) {
this.name = name;
return this;
}
public InitiatorBuilder addEventChannelDescription(final EventChannelDescription eventChannelDescription) {
eventChannelDescriptions.add(eventChannelDescription);
return this;
}
public InitiatorBuilder setStateQueueFactory(final StateQueueFactory stateQueueFactory) {
this.stateQueueFactory = stateQueueFactory;
return this;
}
public Initiator build() {
Argument.assertNotNull(name, "name");
Argument.assertGreaterThan(eventChannelDescriptions.size(), 0, "eventChannelDescriptions.size()");
final EventChannelDescription[] ecds =
eventChannelDescriptions.toArray(new EventChannelDescription[eventChannelDescriptions.size()]);
return new Initiator(name, ecds, stateQueueFactory);
}
}
}