From d43164830403245353080f5d6f838ed9f56d9a35 Mon Sep 17 00:00:00 2001 From: Andrew Branson Date: Mon, 18 Nov 2013 09:48:03 +0100 Subject: 3.0-SNAPSHOT (Will be first open source version) New StateMachine desc IssueID #28 --- .../lifecycle/instance/stateMachine/State.java | 60 +++++ .../instance/stateMachine/StateMachine.java | 247 +++++++++-------- .../lifecycle/instance/stateMachine/States.java | 40 --- .../instance/stateMachine/Transition.java | 292 +++++++++++++++++++++ .../instance/stateMachine/TransitionOutcome.java | 36 +++ .../instance/stateMachine/TransitionResource.java | 9 + .../instance/stateMachine/TransitionScript.java | 27 ++ .../instance/stateMachine/Transitions.java | 41 --- 8 files changed, 557 insertions(+), 195 deletions(-) create mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/State.java delete mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/States.java create mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transition.java create mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionOutcome.java create mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionResource.java create mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionScript.java delete mode 100644 src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transitions.java (limited to 'src/main/java/com/c2kernel/lifecycle/instance/stateMachine') diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/State.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/State.java new file mode 100644 index 0000000..fd712f4 --- /dev/null +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/State.java @@ -0,0 +1,60 @@ +package com.c2kernel.lifecycle.instance.stateMachine; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Set; + +public class State implements Serializable { + + int id; + String name; + boolean finished = false; // If true, this state deactivates the current activity and the lifecycle proceeds + + HashMap possibleTransitions; + + public State() { + possibleTransitions = new HashMap(); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return id+": "+name; + } + + public void setName(String name) { + this.name = name; + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public boolean isFinished() { + return finished; + } + + public void setFinished(boolean finished) { + this.finished = finished; + } + + public HashMap getPossibleTransitions() { + return possibleTransitions; + } + + protected void addPossibleTransition(Transition possibleTransition) { + possibleTransitions.put(possibleTransition.getId(), possibleTransition); + } + + public Set getPossibleTransitionIds() { + return possibleTransitions.keySet(); + } +} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/StateMachine.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/StateMachine.java index ddfc838..c165e6f 100644 --- a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/StateMachine.java +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/StateMachine.java @@ -1,137 +1,156 @@ package com.c2kernel.lifecycle.instance.stateMachine; -import java.io.Serializable; - +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.c2kernel.common.AccessRightsException; +import com.c2kernel.common.InvalidDataException; +import com.c2kernel.common.InvalidTransitionException; +import com.c2kernel.common.ObjectNotFoundException; import com.c2kernel.lifecycle.instance.Activity; +import com.c2kernel.lookup.AgentPath; +import com.c2kernel.utils.DescriptionObject; import com.c2kernel.utils.Logger; -/** - * @version $Revision: 1.30 $ $Date: 2004/06/04 09:39:19 $ - * @author $Author: sgaspard $ - */ -/** this class represents the link between 2 successive activities */ -public class StateMachine implements Serializable +public class StateMachine implements DescriptionObject { - public int state = 0; - private Activity activity; - - public static final String SKIPPABLE = "Skippable"; - public static final String REPEATABLE = "Repeatable"; - public static final String IGNORABLE = "Ignorable"; - - - /** - * Method StateMachine. - * @param act - */ - public StateMachine(Activity act) - { - activity = act; + public String name; + public int version; + + private ArrayList states; + private ArrayList transitions; + private final HashMap stateCodes; + private final HashMap transitionCodes; + + State initialState; + int initialStateCode; + boolean isCoherent = false; + + public StateMachine() { + states = new ArrayList(); + transitions = new ArrayList(); + stateCodes = new HashMap(); + transitionCodes = new HashMap(); } - - /** row : States from (WAITING,RESERVED,STARTED,SUSPENDED,FINISHED,RWAITING,RRESERVED,RSTARTED,RSUSPENDED) - * collumn : transition (RESERVE,START,SKIP,DONE,COMPLETE,SUSPEND,REASIGN,RESUME,REPEAT,IGNORE,PROCEED) - * cell : State that is reached (-1 if transition not allowed) - */ - private int[][] getCurrentMachine() - { - int [][] returnArray = - { /*RESERVE, START, SKIP, DONE,COMPLETE,SUSPEND,REASSIGN,RESUME, REPEAT, IGNORE, PROCEED*/ - /*0 WAITING*/ { 1,getActive()?2:-1,getSkippable()?4:-1,getActive()?4:-1, -1, -1, -1, -1, -1, -1, -1},/*0 WAITING*/ - /*1 RESERVED*/ { -1,getActive()?2:-1,getSkippable()?4:-1,getActive()?4:-1, -1, -1, -1, -1, -1, 0, -1},/*1 RESERVED*/ - /*2 STARTED*/ { -1, -1, -1, -1, 4, 3, -1, -1, -1,getIgnorable()?0:-1, -1},/*2 STARTED*/ - /*3 SUSPENDED*/ { -1, -1, -1, -1, -1, -1, 2, 2, -1,getIgnorable()?0:-1, -1},/*3 SUSPENDED*/ - /*4 FINISHED*/ { -1, -1, -1, -1, -1, -1, -1, -1,getRepeatable()?5:-1, -1,getActive()?4:-1},/*4 FINISHED*/ - /*5 RWAITING*/ { 6,getActive()?7:-1,getSkippable()?4:-1,getActive()?4:-1, -1, -1, -1, -1, -1, -1, -1},/*5 RWAITING*/ - /*6 RRESERVED*/ { -1,getActive()?7:-1,getSkippable()?4:-1,getActive()?4:-1, -1, -1, -1, -1, -1, -1, -1},/*6 RRESERVED*/ - /*7 RSTARTED*/ { -1, -1, -1, -1, 4, 8, -1, -1, -1,getIgnorable()?5:-1, -1},/*7 RSTARTED*/ - /*8 RSUSPENDED*/ { -1, -1, -1, -1, -1, -1, 8, 7, -1, -1, -1} /*8 RSUSPENDED*/ - }; - return returnArray; + + public void setStates(ArrayList newStates) { + this.states = newStates; + validate(); } - - /** - * @see java.lang.Object#Object() - */ - public StateMachine() - { + + public void setTransitions(ArrayList newTransitions) { + this.transitions = newTransitions; + validate(); + } + + public void validate() { + stateCodes.clear(); + transitionCodes.clear(); + isCoherent = true; + + for (State state : states) { + Logger.debug(6, "State "+state.id+": "+state.name); + stateCodes.put(state.getId(), state); + } + + if (stateCodes.containsKey(initialStateCode)) + initialState = stateCodes.get(initialStateCode); + else + isCoherent = false; + + for (Transition trans : transitions) { + Logger.debug(6, "Transition "+trans.id+": "+trans.name); + transitionCodes.put(trans.getId(), trans); + isCoherent &= trans.resolveStates(stateCodes); + } + + } + + public ArrayList getStates() { + return states; + } + + public ArrayList getTransitions() { + return transitions; + } + + public State getInitialState() { + return initialState; } - /** - * Method getCurrentState. - * @return String - */ - public int getCurrentState() - { - return state; + public void setInitialState(State initialState) { + this.initialState = initialState; + initialStateCode = initialState.getId(); } - /** - * Method possibleTransition. - * @return String[] - */ - public int[] possibleTransition() - { - int[] trans = new int[9]; - int cmpt = 0; - for (int i=0; i< getCurrentMachine()[state].length;i++) - if (getCurrentMachine()[state][i]!=-1) trans[cmpt++]=i; + public int getInitialStateCode() { + return initialStateCode; + } - int [] result = new int[cmpt]; - for (int i=0;i -1) { - state=newState; - return true; - } - Logger.msg("StateMachine.traverse() - Illegal transition "+Transitions.getTransitionName(transition)+" from "+States.getStateName(state)); - return false; - } - public int simulate(int transition) - { - return getCurrentMachine()[state][transition]; - } - /** - * Returns the ignorable. - * @return boolean - */ - public boolean getIgnorable() - { - return ((Boolean)activity.getProperties().get(IGNORABLE)).booleanValue(); + @Override + public String getName() { + return name; } - /** - * Returns the repeatable. - * @return boolean - */ - public boolean getRepeatable() - { - return ((Boolean)activity.getProperties().get(REPEATABLE)).booleanValue(); + @Override + public int getVersion() { + return version; + } + + public void setName(String name) { + this.name = name; } - /** - * Returns the skippable. - * @return boolean - */ - public boolean getSkippable() - { - return ((Boolean)activity.getProperties().get(SKIPPABLE)).booleanValue(); + public void setVersion(int version) { + this.version = version; } - public boolean getActive() - { - return activity.getActive(); - } + public Transition getTransition(int transitionID) { + return transitionCodes.get(transitionID); + } + + public State getState(int stateID) { + return stateCodes.get(stateID); + } + + public Map getPossibleTransitions(Activity act, AgentPath agent) throws ObjectNotFoundException, InvalidDataException { + HashMap returnList = new HashMap(); + State currentState = getState(act.getState()); + for (Integer transCode : currentState.getPossibleTransitionIds()) { + Transition possTrans = currentState.getPossibleTransitions().get(transCode); + try { + String role = possTrans.getPerformingRole(act, agent); + returnList.put(possTrans, role); + } catch (AccessRightsException ex) { + if (Logger.doLog(5)) + Logger.msg(5, "Transition '"+possTrans+"' not possible for "+agent.getAgentName()+": "+ex.getMessage()); + } + } + return returnList; + } -} + public State traverse(Activity act, Transition transition, AgentPath agent) throws InvalidTransitionException, AccessRightsException, ObjectNotFoundException, InvalidDataException { + State currentState = getState(act.getState()); + if (transition.originState.equals(currentState)) { + transition.getPerformingRole(act, agent); + return transition.targetState; + } + else + throw new InvalidTransitionException("Transition '"+transition.getName()+"' not valid from state '"+currentState.getName(), ""); + + } + + public boolean isCoherent() { + return isCoherent; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/States.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/States.java deleted file mode 100644 index b142e35..0000000 --- a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/States.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.c2kernel.lifecycle.instance.stateMachine; - - -/** - * @author XSeb74 - * - * To change this generated comment edit the template variable "typecomment": - * Window>Preferences>Java>Templates. - * To enable and disable the creation of type comments go to - * Window>Preferences>Java>Code Generation. - */ -public class States -{ - public final static int WAITING = 0; - public final static int RESERVED = 1; - public final static int STARTED = 2; - public final static int SUSPENDED = 3; - public final static int FINISHED = 4; - public final static int RWAITING = 5; - public final static int RRESERVED = 6; - public final static int RSTARTED = 7; - public final static int RSUSPENDED = 8; - - //everything less that this constant is NOT a repeating state - public final static int REPEATSTATESTART = 5; - - public static final String[] states = { "Waiting", "Reserved", "Started", "Suspended", "Finished", "Waiting(R)", "Reserved(R)", "Started(R)", "Suspended(R)" }; - - public static String getStateName(int state) - { - try - { - return states[state]; - } - catch (ArrayIndexOutOfBoundsException ex) - { - return "Invalid State: " + state; - } - } -} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transition.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transition.java new file mode 100644 index 0000000..9922c7c --- /dev/null +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transition.java @@ -0,0 +1,292 @@ +package com.c2kernel.lifecycle.instance.stateMachine; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.c2kernel.common.AccessRightsException; +import com.c2kernel.common.InvalidDataException; +import com.c2kernel.common.ObjectNotFoundException; +import com.c2kernel.lifecycle.instance.Activity; +import com.c2kernel.lookup.AgentPath; +import com.c2kernel.lookup.RolePath; +import com.c2kernel.persistency.outcome.Schema; +import com.c2kernel.process.Gateway; +import com.c2kernel.utils.CastorHashMap; +import com.c2kernel.utils.LocalObjectLoader; +import com.c2kernel.utils.Logger; + +public class Transition implements Serializable { + + int id; + String name; + + int originStateId; + int targetStateId; + State originState; + State targetState; + String reservation; + + String enabledProp; // Boolean property that permits this transition e.g. 'Skippable' + + // activation properties + boolean requiresActive = true; // Whether the activity must be active for this transition to be available + boolean finishing; // whether the target state is a finishing state; + + // permissions + String roleOverride; + + TransitionOutcome outcome; + TransitionScript script; + + public Transition() { + } + + + public Transition(int id, String name, int originStateId, int targetStateId) { + super(); + this.id = id; + this.name = name; + this.originStateId = originStateId; + this.targetStateId = targetStateId; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public State getOriginState() { + return originState; + } + + public void setOriginState(State originState) { + this.originState = originState; + } + + public State getTargetState() { + return targetState; + } + + public void setTargetState(State targetState) { + this.targetState = targetState; + finishing = targetState.finished; + } + + public String getEnabledProp() { + return enabledProp; + } + + public void setEnabledProp(String enabledProp) { + this.enabledProp = enabledProp; + } + + public boolean isRequiresActive() { + return requiresActive; + } + + public boolean isFinishing() { + return finishing; + } + + public void setRequiresActive(boolean requiresActive) { + this.requiresActive = requiresActive; + } + + public String getRoleOverride() { + return roleOverride; + } + + public void setRoleOverride(String roleOverride) { + this.roleOverride = roleOverride; + } + + public TransitionOutcome getOutcome() { + return outcome; + } + + public void setOutcome(TransitionOutcome outcome) { + this.outcome = outcome; + } + + public TransitionScript getScript() { + return script; + } + + public void setScript(TransitionScript script) { + this.script = script; + } + + public String getReservation() { + return reservation; + } + + public void setReservation(String reservation) { + this.reservation = reservation; + } + + protected boolean resolveStates(HashMap states) { + boolean allFound = true; + if (states.keySet().contains(originStateId)) { + originState = states.get(originStateId); + originState.addPossibleTransition(this); + } + else + allFound = false; + if (states.keySet().contains(targetStateId)) + targetState = states.get(targetStateId); + else + allFound = false; + return allFound; + } + + public int getOriginStateId() { + return originStateId; + } + + public void setOriginStateId(int originStateId) { + this.originStateId = originStateId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getTargetStateId() { + return targetStateId; + } + + public void setTargetStateId(int targetStateId) { + this.targetStateId = targetStateId; + } + + public String getPerformingRole(Activity act, AgentPath agent) throws ObjectNotFoundException, AccessRightsException { + + // check available + if (!isEnabled(act.getProperties())) + throw new AccessRightsException("Transition '"+name+"' is disabled by the '"+enabledProp+"' property.", ""); + + // check active + if (isRequiresActive() && !act.getActive()) + throw new AccessRightsException("Activity must be active to perform this transition", ""); + + RolePath role = null; + String overridingRole = resolveValue(roleOverride, act.getProperties()); + boolean override = overridingRole != null; + boolean isOwner = false, isOwned = true; + + // Check agent name + String agentName = act.getCurrentAgentName(); + if (agentName != null && agentName.length() >0) { + if (agent.getAgentName().equals(agentName)) + isOwner = true; + } + else isOwned = false; + + // determine transition role + if (override) { + role = Gateway.getLDAPLookup().getRoleManager().getRolePath(overridingRole); + } + else { + String actRole = act.getCurrentAgentRole(); + if (actRole != null && actRole.length() > 0) + role = Gateway.getLDAPLookup().getRoleManager().getRolePath(actRole); + } + + // Decide the access + if (isOwned && !override && !isOwner) + throw new AccessRightsException("Agent '"+agent.getAgentName() + +"' cannot perform this transition because the activity '"+act.getName()+"' is currently owned by "+agentName, null); + + if (role != null) { + if (agent.hasRole(role)) + return role.getName(); + else if (agent.hasRole("Admin")) + return "Admin"; + else + throw new AccessRightsException("Agent '"+agent.getAgentName() + +"' does not hold a suitable role '"+role.getName()+"' for the activity "+act.getName(), null); + } + else + return null; + } + + public String getReservation(Activity act, AgentPath agent) { + if (reservation == null || reservation.length() == 0) + reservation = targetState.finished?"clear":"set"; + + String reservedAgent = act.getCurrentAgentName(); + if (reservation.equals("set")) + reservedAgent = agent.getAgentName(); + else if (reservation.equals("clear")) + reservedAgent = ""; + return reservedAgent; + + } + + private static String resolveValue(String key, CastorHashMap props) { + if (key==null) return null; + String result = key; + Pattern propField = Pattern.compile("\\$\\{(.+?)\\}"); + Matcher propMatcher = propField.matcher(result); + while (propMatcher.find()) { + String propName = propMatcher.group(1); + Object propValue = props.get(propName); + Logger.debug("Replacing Property "+propName+" as "+propValue); + String propValString = propValue==null?"":propValue.toString(); + result = result.replace("${"+propName+"}", propValString); + } + return result; + } + + public boolean isEnabled(CastorHashMap props) { + if (enabledProp == null) + return true; + return (Boolean)props.get(enabledProp); + } + + public boolean hasOutcome() { + return outcome!=null + && outcome.schemaName!=null && outcome.schemaName.length()>0 + && outcome.schemaVersion!=null && outcome.schemaVersion.length()>0; + } + + public Schema getSchema(CastorHashMap actProps) throws InvalidDataException, ObjectNotFoundException { + if (hasOutcome()) + try { + return LocalObjectLoader.getSchema(resolveValue(outcome.schemaName, actProps), + Integer.parseInt(resolveValue(outcome.schemaVersion, actProps))); + } catch (NumberFormatException ex) { + throw new InvalidDataException("Bad schema version number: "+outcome.schemaVersion+" ("+resolveValue(outcome.schemaVersion, actProps), ""); + } + else + return null; + } + + public String getScriptName(CastorHashMap actProps) { + return resolveValue(script.scriptName, actProps); + } + + public int getScriptVersion(CastorHashMap actProps) throws InvalidDataException { + try { + return Integer.parseInt(resolveValue(script.scriptVersion, actProps)); + } catch (NumberFormatException ex) { + throw new InvalidDataException("Bad Script version number: "+script.scriptVersion+" ("+resolveValue(script.scriptVersion, actProps)); + } + } + + public boolean hasScript() { + return script!=null + && script.scriptName!=null && script.scriptName.length()>0 + && script.scriptVersion!=null && script.scriptVersion.length()>0; + } +} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionOutcome.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionOutcome.java new file mode 100644 index 0000000..71ba3b3 --- /dev/null +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionOutcome.java @@ -0,0 +1,36 @@ +package com.c2kernel.lifecycle.instance.stateMachine; + +public class TransitionOutcome extends TransitionResource { + + // schema properties + String schemaName, schemaVersion; // Name & version of the schema of the data required for this transition. + boolean required = true; // If true, then the data must be supplied to perform the transition, otherwise it is optional + + public TransitionOutcome() { + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + +} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionResource.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionResource.java new file mode 100644 index 0000000..f644a4c --- /dev/null +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionResource.java @@ -0,0 +1,9 @@ +package com.c2kernel.lifecycle.instance.stateMachine; + +public class TransitionResource { + + public TransitionResource() { + // TODO Auto-generated constructor stub + } + +} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionScript.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionScript.java new file mode 100644 index 0000000..9585102 --- /dev/null +++ b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/TransitionScript.java @@ -0,0 +1,27 @@ +package com.c2kernel.lifecycle.instance.stateMachine; + +public class TransitionScript extends TransitionResource { + + // script properties + String scriptName, scriptVersion; // Name & version of the script to be run by the agent during this transition + + public TransitionScript() { + } + + public String getScriptName() { + return scriptName; + } + + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public String getScriptVersion() { + return scriptVersion; + } + + public void setScriptVersion(String scriptVersion) { + this.scriptVersion = scriptVersion; + } + +} diff --git a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transitions.java b/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transitions.java deleted file mode 100644 index 4239baa..0000000 --- a/src/main/java/com/c2kernel/lifecycle/instance/stateMachine/Transitions.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.c2kernel.lifecycle.instance.stateMachine; - -import com.c2kernel.utils.Language; - -/** - * @author XSeb74 - * - * To change this generated comment edit the template variable "typecomment": - * Window>Preferences>Java>Templates. - * To enable and disable the creation of type comments go to - * Window>Preferences>Java>Code Generation. - */ -public class Transitions -{ - public final static int RESERVE = 0; - public final static int START = 1; - public final static int SKIP = 2; - public final static int DONE = 3; - public final static int COMPLETE = 4; - public final static int SUSPEND = 5; - public final static int REASSIGN = 6; - public final static int RESUME = 7; - public final static int REPEAT = 8; - public final static int IGNORE = 9; - public final static int PROCEED = 10; - public final static int ACTIVATION = 11; - - private static String[] transitions = { "reserve", "start", "skip", "done", "complete", "suspend", "reassign", "resume", "repeat","ignore","proceed","activation" }; - - public static String getTransitionName(int trans) - { - try - { - return Language.translate(transitions[trans]); - } - catch (ArrayIndexOutOfBoundsException ex) - { - return "Invalid Transition: " + trans; - } - } -} -- cgit v1.2.3