package com.c2kernel.lifecycle.instance; import java.util.ArrayList; import java.util.Vector; import com.c2kernel.common.AccessRightsException; import com.c2kernel.common.GTimeStamp; import com.c2kernel.common.InvalidDataException; import com.c2kernel.common.InvalidTransitionException; import com.c2kernel.common.ObjectAlreadyExistsException; import com.c2kernel.common.ObjectNotFoundException; import com.c2kernel.entity.agent.Job; import com.c2kernel.events.Event; import com.c2kernel.events.History; import com.c2kernel.graph.model.Vertex; import com.c2kernel.lifecycle.WfCastorHashMap; import com.c2kernel.lifecycle.instance.stateMachine.StateMachine; import com.c2kernel.lifecycle.instance.stateMachine.States; import com.c2kernel.lifecycle.instance.stateMachine.Transitions; import com.c2kernel.lookup.AgentPath; import com.c2kernel.lookup.EntityPath; import com.c2kernel.lookup.InvalidEntityPathException; import com.c2kernel.lookup.LDAPRoleManager; import com.c2kernel.lookup.RolePath; import com.c2kernel.persistency.ClusterStorage; import com.c2kernel.persistency.TransactionManager; import com.c2kernel.persistency.outcome.Outcome; import com.c2kernel.persistency.outcome.Viewpoint; import com.c2kernel.process.Gateway; import com.c2kernel.scripting.ScriptingEngineException; import com.c2kernel.utils.DateUtility; import com.c2kernel.utils.Logger; /** * @version $Revision: 1.222 $ $Date: 2005/10/05 07:39:37 $ * @author $Author: abranson $ */ public class Activity extends WfVertex { /** * vector of errors (Strings) that is constructed each time verify() is launched */ protected Vector mErrors; /** @associates a State machine engine */ private StateMachine machine; /** true is avalaibe to be executed */ public boolean active = false; /** used in verify() */ private boolean loopTested; private GTimeStamp mStartDate; private GTimeStamp mActiveDate; private String mType; private EntityPath mEntityPath; public Activity() { super(); setProperties(new WfCastorHashMap()); mErrors = new Vector(0, 1); machine = new StateMachine(this); mStartDate = new GTimeStamp(); mActiveDate = new GTimeStamp(); DateUtility.setToNow(mActiveDate); DateUtility.setToNow(mStartDate); } /** @return the SystemKey of the item that contains the workflow */ public EntityPath getItemEntityPath() { if (mEntityPath == null) try { Integer i = (Integer) (getWf().getProperties().get("ItemSystemKey")); if (i == null) return null; // no item yet EntityPath entityPath = new EntityPath(i.intValue()); mEntityPath = entityPath; } catch (InvalidEntityPathException ex) { Logger.error("InvalidEntityPathException::Activity::getItemSystemKey() " + ex.toString()); return null; } return mEntityPath; } /** @return the StateMachine */ public StateMachine getMachine() { return machine; } /** sets the StateMachine (Only for Serialisation) */ public void setMachine(StateMachine sm) { machine = sm; } /** add the activity which id is idNext as next of the current one */ void addNext(String idNext) { new Next(this, (WfVertex) getParent().search(idNext)); } /** * adds a New link between the current Activity and the WfVertex passed in param */ @Override public Next addNext(WfVertex vertex) { return new Next(this, vertex); } /** return the current State of the State machine */ public int getCurrentState() { return machine.getCurrentState(); } /** return the current State of the State machine (Used in Serialisation) */ public int getState() { if (machine == null) machine = new StateMachine(this); return getCurrentState(); } /** Sets a new State in a State machine */ public void setState(int stat) { if (machine == null) machine = new StateMachine(this); machine.state = stat; } /** check the abiltity of the agent passed in param to act on the activity */ //return's the agentName public String checkAccessRights(AgentPath agent) throws AccessRightsException { String agentID = getCurrentAgentName(); boolean authorised = agentID.equals(agent.getAgentName()); String actRole = getCurrentAgentRole(); if (!authorised) { authorised = actRole == null || actRole.equals("") || actRole.equals("all"); } if (!authorised) { RolePath[] roles = agent.getRoles(); for (int i = 0; !authorised && i < roles.length; i++) { if (roles[i].getName().equalsIgnoreCase("Admin")) authorised = true; if (roles[i].getName().equalsIgnoreCase(actRole)) authorised = true; if (roles[i].getName().equalsIgnoreCase("Guest")) throw new AccessRightsException("Guest execution forbidden"); } } if (!authorised) throw new AccessRightsException("Activity::checkAccessRights() - Agent does not hold the correct role."); return agent.getAgentName(); } /** cf Item request */ public void request(AgentPath agent, int transitionID, String requestData) throws AccessRightsException, InvalidTransitionException, InvalidDataException, ObjectAlreadyExistsException { int state = getState(); String agentName = checkAccessRights(agent); if (machine.traverse(transitionID)) { setReservation(transitionID, agentName); sendEventStoreOutcome(transitionID, requestData, agent); if (transitionID == Transitions.REPEAT) { setActive(true); if (getIsComposite()) { WfVertex v = (WfVertex) ((CompositeActivity) this).search(getPath() + "/" + ((CompositeActivity) this).getChildGraphModel().getStartVertexId()); v.reinit(getID()); try { runfirst(agent); } catch (ScriptingEngineException e) { Logger.error(e); } } } if ((transitionID == Transitions.COMPLETE || transitionID == Transitions.DONE) && (state == States.RSTARTED || getProperties().get("Breakpoint").equals(Boolean.TRUE))) setActive(false); else if (transitionID == Transitions.START) start(); else if ((transitionID == Transitions.SKIP && getActive()) || transitionID == Transitions.DONE || transitionID == Transitions.COMPLETE || transitionID == Transitions.PROCEED) try { runNext(agent); } catch (ScriptingEngineException e) { Logger.error(e); } // run post execution script now try { String postSubmitScr = (String) getProperties().get("PostExecScriptName"); String postSubmitVer = (String) getProperties().get("PostExecScriptVersion"); if (postSubmitScr != null && (transitionID == Transitions.COMPLETE || transitionID == Transitions.DONE)) evaluateScript(postSubmitScr, postSubmitVer); } catch (ScriptingEngineException ex) { Logger.error(ex); } //refresh all the job lists pushJobsToAgents(); } else throw new InvalidTransitionException("Activity is in the wrong state."); } public void setReservation(int transitionID, String agentName) { String actAgentName = (String) getProperties().get("Agent Name"); switch (transitionID) { // these transition reserve the activity case Transitions.REASSIGN : case Transitions.RESERVE : case Transitions.START : actAgentName = agentName; break; // these clear any current reservation case Transitions.COMPLETE : case Transitions.DONE : case Transitions.IGNORE : case Transitions.SKIP : actAgentName = ""; // other transitions have no effect on the reservations default : } getProperties().put("Agent Name", actAgentName); } public String getTransitions() { String result = ""; int i; for (i = 0; i < machine.possibleTransition().length; i++) { result += machine.possibleTransition()[i] + ","; } //cuts out the last comma(',') if required if (i > 0) { result = result.substring(0, result.length() - 1); } result += ""; return result; } /** launch the verification of the activity */ @Override public boolean verify() { mErrors.removeAllElements(); int nbInEdgres = getInEdges().length; int nbOutEdges = getOutEdges().length; if (nbInEdgres == 0 && this.getID() != getParent().getChildrenGraphModel().getStartVertexId()) { mErrors.add("Unreachable"); return false; } else if (nbInEdgres > 1) { mErrors.add("Bad nb of previous"); return false; } else if (nbOutEdges > 1) { mErrors.add("too many next"); return false; } else if (nbOutEdges == 0) { if (!((CompositeActivity) getParent()).hasGoodNumberOfActivity()) { mErrors.add("too many endpoints"); return false; } } // else // { // Vertex[] outV = getOutGraphables(); // Vertex[] anteVertices = GraphTraversal.getTraversal(getParent().getChildrenGraphModel(), this, GraphTraversal.kUp, false); // boolean errInLoop = false; // for (int i = 0; i < outV.length; i++) // { // for (int j = 0; j < anteVertices.length; j++) // if (!loop() && outV[i].getID() == anteVertices[j].getID()) // errInLoop = true; // } // if (errInLoop) // { // mErrors.add("Error In Loop"); // return false; // } // } return true; } /** Used in verify() */ @Override public boolean loop() { boolean loop2 = false; if (!loopTested) { loopTested = true; if (getOutGraphables().length != 0) loop2 = ((WfVertex) getOutGraphables()[0]).loop(); } loopTested = false; return loop2; } /** sets the next activity available if possible */ @Override public void runNext(AgentPath agent) throws ScriptingEngineException { setActive(false); try { Vertex[] outVertices = getOutGraphables(); Vertex[] outVertices2 = getOutGraphables(); boolean hasNoNext = false; boolean out = false; while (!out) if (outVertices2.length > 0) { if (outVertices2[0] instanceof Join) outVertices2 = ((WfVertex) outVertices2[0]).getOutGraphables(); else out = true; } else { hasNoNext = true; out = true; } Logger.debug(8, outVertices + " " + outVertices2); if (!hasNoNext) ((WfVertex) outVertices[0]).run(agent); else { if (getParent() != null && getParent().getName().equals("domain")) // workflow // finished setActive(true); else { CompositeActivity parent = (CompositeActivity) getParent(); if (parent != null) parent.runNext(agent); } } } catch (ScriptingEngineException s) { setActive(true); throw s; } } /** @return the only Next of the Activity */ public Next getNext() { if (getOutEdges().length > 0) return (Next) getOutEdges()[0]; else return null; } /** reinitialises the Activity and propagate (for Loop) */ @Override public void reinit(int idLoop) { Logger.debug(7, "reinit " + getItemEntityPath().getSysKey() + " " + getPath()); Vertex[] outVertices = getOutGraphables(); machine.state = States.WAITING; if (outVertices.length > 0) { WfVertex nextAct = (WfVertex) outVertices[0]; nextAct.reinit(idLoop); } } /** return the String that identifies the errors found in th activity */ @Override public String getErrors() { if (mErrors.size() == 0) return "No error"; return mErrors.elementAt(0); } /** * called by precedent Activity runNext() for setting the activity able to be executed */ @Override public void run(AgentPath agent) throws ScriptingEngineException { Logger.debug(8, getPath() + " run " + getCurrentState()); if (!getActive()) setActive(true); if (getMachine().getCurrentState() == States.FINISHED) { runNext(agent); } else { DateUtility.setToNow(mActiveDate); pushJobsToAgents(); } } /** * sets the activity available to be executed on start of Workflow or composite activity (when it is the first one of the (sub)process */ @Override public void runfirst(AgentPath agent) throws ScriptingEngineException { Logger.debug(8, getPath() + " runfirst"); run(agent); } /** @return the current ability to be executed */ public boolean getActive() { return active; } /** sets the ability to be executed */ public void setActive(boolean acti) { active = acti; } /** @return the Description field of properties */ public String getDescription() { if (getProperties().containsKey("Description")) return (String) (getProperties().get("Description")); return "No description"; } public String getCurrentAgentName() { return (String) getProperties().get("Agent Name"); } public String getCurrentAgentRole() { return (String) getProperties().get("Agent Role"); } /** * @return an array of Steps that matches the querry * @param agentID * Agent concerned by the query * @param agentRole * Agent concerned by the query @int stateID state to test in the query, use -1 for all * @param filter * if tru will be filtered by agent, else won't */ public Activity[] query(AgentPath agent, int stateID, boolean filter) { if (getCurrentState() == stateID || stateID == -1) { Activity[] steps = { this }; if (!filter) return steps; else { try { checkAccessRights(agent); return steps; } catch (AccessRightsException e) { //case that agent is not allowed Logger.msg(7, "Activity :: AccessRightsException in " + this.getItemEntityPath() + "/" + this.getPath()); } } } return new Activity[0]; } /** * returns the lists of jobs for the activity and children (cf com.c2kernel.entity.Job) */ public ArrayList calculateJobs(AgentPath agent, boolean recurse) { return calculateJobsBase(agent, false); } // public ArrayList calculateAllJobs(AgentPath agent, boolean recurse) { return calculateJobsBase(agent, true); } private ArrayList calculateJobsBase(AgentPath agent, boolean all) { Logger.msg(7, "calculateJobs - " + getPath()); int[] transitions = { }; ArrayList jobs = new ArrayList(); try { String agentName = checkAccessRights(agent); String currentAgentName = getCurrentAgentName(); boolean isCurrent = currentAgentName == null || currentAgentName.equals("") || agentName.equals(currentAgentName); if ((all || getActive()) && !getName().equals("domain")) transitions = machine.possibleTransition(); Logger.msg(7, "Activity.calculateJobs() - Got " + transitions.length + " transitions."); for (int i = 0; i < transitions.length; i++) { Logger.msg(7, "Creating Job object for transition " + transitions[i]); if ((isCurrent && !(transitions[i] == Transitions.REASSIGN && agentName.equals(currentAgentName))) || (transitions[i] == Transitions.REASSIGN && !agentName.equals(currentAgentName))) jobs.add(new Job(getItemEntityPath().getSysKey(), getPath(), transitions[i], getCurrentState(), machine.simulate(transitions[i]), getName(), getProperties(), getType(), agentName)); } } catch (AccessRightsException ex) { Logger.msg(6, "Agent "+ agent.getAgentName() +" is not allowed to interact with "+getItemEntityPath().getSysKey()+":"+getPath()); } // empty joblist then return jobs; } /** Adds an event to the AuditTrail of the Item if any */ private Event auditEvent(int transitionID, AgentPath agent, boolean hasOutcome, boolean isError) { EntityPath entityPath = getItemEntityPath(); if (entityPath != null) { Event event = null; History hist = null; String viewName = hasOutcome?(String)getProperties().get("Viewpoint"):null; try { hist = (History) Gateway.getStorage().get(entityPath.getSysKey(), ClusterStorage.HISTORY, this); if (hasOutcome) { String schemaName = isError?"Errors":(String)getProperties().get("SchemaType"); Integer schemaVersion = isError?0:(Integer)getProperties().get("SchemaVersion"); event = hist.addEvent(agent.getAgentName(), getCurrentAgentRole(), transitionID, getName(), getPath(), getType(), schemaName, schemaVersion, viewName, getCurrentState()); } else event = hist.addEvent(agent.getAgentName(), getCurrentAgentRole(), transitionID, getName(), getPath(), getType(), getCurrentState()); Logger.msg(7, "Activity::auditEvent() - Event:" + event.getName() + " was added to the AuditTrail"); } catch (Exception ex) { Logger.error("Activity::auditEvent() - Item '" + entityPath.toString() + "'!"); Logger.error(ex); } return event; } else return null; } /** * Stores the request data as an outcome of the Item It does a great deal of storing outcomes in different configuration */ //requestdata is xmlstring private String storeOutcome(int eventID, String requestData, boolean isError) throws InvalidDataException { EntityPath entityPath = getItemEntityPath(); if (requestData == null || requestData.length() == 0) throw new InvalidDataException("Empty outcome", ""); if (entityPath != null) { String schemaType; int schemaVersion; if (isError) { schemaType="Errors"; schemaVersion=0; } else { schemaType = (String) getProperties().get("SchemaType"); if (schemaType == null || schemaType.length() == 0) return null; String versionString = (String) getProperties().get("SchemaVersion"); try { schemaVersion = Integer.parseInt(versionString); } catch (Exception e) { throw new InvalidDataException("Activity.storeOutcome() - invalid schemaVersion " + versionString, ""); } } Logger.msg(5, "Activity::storeOutcome() - type:" + schemaType + " version:" + schemaVersion); try { Outcome newOutcome = new Outcome(eventID, requestData, schemaType, schemaVersion); Gateway.getStorage().put(entityPath.getSysKey(), newOutcome, this); // update specific view if defined String specificView = (String) getProperties().get("Viewpoint"); if (specificView != null && !specificView.equals("")) { Viewpoint currentView = new Viewpoint(entityPath.getSysKey(), schemaType, specificView, schemaVersion, eventID); Gateway.getStorage().put(entityPath.getSysKey(), currentView, this); } // update last view Viewpoint currentView = new Viewpoint(entityPath.getSysKey(), schemaType, "last", schemaVersion, eventID); Gateway.getStorage().put(entityPath.getSysKey(), currentView, this); return schemaType + "/" + schemaVersion + "/" + eventID; } catch (Exception ex) { Logger.error("ActivityBase::storeOutcome() - Item '" + entityPath.toString() + "'!"); Logger.error(ex); } return null; } else return null; } /** the method to be called by the requestAction() method * @throws InvalidDataException */ public void sendEventStoreOutcome(int transitionID, String requestData, AgentPath agent) throws InvalidDataException { int eventID = -1; String schemaType = (String) getProperties().get("SchemaType"); boolean hasOutcome = (transitionID == Transitions.DONE || transitionID == Transitions.COMPLETE) && (schemaType != null && schemaType.length() > 0); boolean hasErrorOutcome = transitionID == Transitions.SUSPEND && requestData != null && requestData.length()>0; Event event = auditEvent(transitionID, agent, hasOutcome, hasErrorOutcome); if (event != null) eventID = event.getID(); if (hasOutcome || hasErrorOutcome) storeOutcome(eventID, requestData, hasErrorOutcome); EntityPath entityPath = getItemEntityPath(); TransactionManager storage = Gateway.getStorage(); if (entityPath != null) { storage.commit(this); } } public void pushJobsToAgents() { String agentRole = getCurrentAgentRole(); if (agentRole == null || agentRole.length()==0) return; LDAPRoleManager roleMan = Gateway.getLDAPLookup().getRoleManager(); RolePath myRole; try { myRole = roleMan.getRolePath(agentRole); } catch (ObjectNotFoundException ex) { // non-existent role Logger.msg(7, "Activity.pushJobsToAgents() - Activity role '"+agentRole+" not found."); return; } if (myRole.hasJobList()) new JobPusher(this, myRole).start(); } /** * Returns the activeDate. * * @return GTimeStamp */ public GTimeStamp getActiveDate() { return mActiveDate; } /** * Returns the startDate. * * @return GTimeStamp */ public GTimeStamp getStartDate() { return mStartDate; } /** * Sets the activeDate. * * @param activeDate * The activeDate to set */ public void setActiveDate(GTimeStamp activeDate) { mActiveDate = activeDate; } /** * Sets the startDate. * * @param startDate * The startDate to set */ public void setStartDate(GTimeStamp startDate) { mStartDate = startDate; } /** * Returns the type. * * @return String */ public String getType() { return mType; } /** * Sets the type. * * @param type * The type to set */ public void setType(String type) { mType = type; } private void start() { Logger.debug(8, getPath() + " start"); DateUtility.setToNow(mStartDate); } }