package org.atricore.idbus.kernel.planning.jbpm; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.jbpm.graph.def.Node; import org.jbpm.graph.def.NodeCollection; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.node.NodeTypes; import org.jbpm.graph.node.StartState; import org.jbpm.jpdl.JpdlException; import org.jbpm.jpdl.xml.JpdlParser; import org.jbpm.jpdl.xml.JpdlXmlReader; import org.jbpm.jpdl.xml.Problem; import org.jbpm.jpdl.xml.ProblemListener; import org.xml.sax.InputSource; import java.io.FileWriter; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; public class StatelessJpdlXmlReader extends JpdlXmlReader { private static final Log log = LogFactory.getLog(StatelessJpdlXmlReader.class); private ProcessFragmentResolver fragmentResolver; private ProcessFragmentRegistry fragmentRegistry; private ProcessDescriptor processDescriptor; public StatelessJpdlXmlReader(InputSource inputSource) { super(inputSource); if (log.isTraceEnabled()) log.trace("Using " + getClass().getName() + " JPDL Reader"); } public StatelessJpdlXmlReader(InputSource inputSource, ProblemListener problemListener) { super(inputSource, problemListener); if (log.isTraceEnabled()) log.trace("Using " + getClass().getName() + " JPDL Reader"); } public StatelessJpdlXmlReader(Reader reader) { super(reader); if (log.isTraceEnabled()) log.trace("Using " + getClass().getName() + " JPDL Reader"); } public ProcessFragmentResolver getFragmentResolver() { return fragmentResolver; } public void setFragmentResolver(ProcessFragmentResolver fragmentResolver) { this.fragmentResolver = fragmentResolver; } public ProcessFragmentRegistry getFragmentRegistry() { return fragmentRegistry; } public void setFragmentRegistry(ProcessFragmentRegistry fragmentRegistry) { this.fragmentRegistry = fragmentRegistry; } @Override public ProcessDefinition readProcessDefinition() { // create a new definition processDefinition = ProcessDefinition.createNewProcessDefinition(); // initialize lists problems = new ArrayList(); unresolvedTransitionDestinations = new ArrayList(); unresolvedActionReferences = new ArrayList(); try { // parse the document into a dom tree document = JpdlParser.parse(inputSource, this); Element root = document.getRootElement(); // We have the raw DOM tree here, we're going to replace fragments with the corresponding sub processes nodes // This requires re-wired start/end states resolveFragments(root); // read the process name parseProcessDefinitionAttributes(root); // get the process description String description = root.elementTextTrim("description"); if (description!=null) { processDefinition.setDescription(description); } // first pass: read most content readSwimlanes(root); readActions(root, null, null); readNodes(root, processDefinition); readEvents(root, processDefinition); readExceptionHandlers(root, processDefinition); readTasks(root, null); // second pass processing resolveTransitionDestinations(); resolveActionReferences(); verifySwimlaneAssignments(); // Remove once this works: /* Writer writer = new FileWriter("/tmp/" + processDefinition.getName() + ".jpdl"); OutputFormat outputFormat = new OutputFormat( " ", true ); // OutputFormat outputFormat = OutputFormat.createPrettyPrint(); XMLWriter xmlWriter = new XMLWriter( writer, outputFormat ); xmlWriter.write( document ); xmlWriter.flush(); writer.flush(); */ } catch (Exception e) { log.error("couldn't parse process definition", e); addProblem(new Problem(Problem.LEVEL_ERROR, "couldn't parse process definition", e)); } if (Problem.containsProblemsOfLevel(problems, Problem.LEVEL_ERROR)) { throw new JpdlException(problems); } if (problems!=null) { Iterator iter = problems.iterator(); while (iter.hasNext()) { Problem problem = (Problem) iter.next(); log.warn("process parse warning: "+problem.getDescription()); } } return processDefinition; } @Override public void readNodes(Element element, NodeCollection nodeCollection) { Iterator nodeElementIter = element.elementIterator(); while (nodeElementIter.hasNext()) { Element nodeElement = (Element) nodeElementIter.next(); String nodeName = nodeElement.getName(); // get the node type Class nodeType = NodeTypes.getNodeType(nodeName); if (nodeType!=null) { Node node = null; try { // create a new instance node = (Node) nodeType.newInstance(); } catch (Exception e) { log.error("couldn't instantiate node '"+nodeName+"', of type '"+nodeType.getName()+"'", e); } node.setProcessDefinition(processDefinition); // check for duplicate start-states if ( (node instanceof StartState) && (processDefinition.getStartState()!=null) ) { addError("max one start-state allowed in a process"); } else { // read the common node parts of the element readNode(nodeElement, node, nodeCollection); // if the node is parsable // (meaning: if the node has special configuration to parse, other then the // common node data) node.read(nodeElement, this); } } } } @Override public void readNode(Element nodeElement, Node node, NodeCollection nodeCollection) { // first put the node in its collection. this is done so that the // setName later on will be able to differentiate between nodes contained in // processDefinitions and nodes contained in superstates nodeCollection.addNode(node); // get the node name String name = nodeElement.attributeValue("name"); if (name!=null) { node.setName(name); // check if this is the initial node if ( (initialNodeName!=null) && (initialNodeName.equals(node.getFullyQualifiedName())) ) { processDefinition.setStartState(node); } } // get the node description String description = nodeElement.elementTextTrim("description"); if (description!=null) { node.setDescription(description); } String asyncText = nodeElement.attributeValue("async"); if ("true".equalsIgnoreCase(asyncText)) { node.setAsync(true); } else if ("exclusive".equalsIgnoreCase(asyncText)) { node.setAsync(true); node.setAsyncExclusive(true); } // parse common subelements readNodeTimers(nodeElement, node); readEvents(nodeElement, node); readExceptionHandlers(nodeElement, node); // save the transitions and parse them at the end addUnresolvedTransitionDestination(nodeElement, node); } protected void resolveFragments(Element root) throws Exception { Iterator eIter = root.elementIterator(); while (eIter.hasNext()) { Element child = (Element) eIter.next(); String text = child.getText(); String name = child.getName(); if (!name.equals("process-fragment-state")) continue; Iterator pfIter = child.elementIterator(); while (pfIter.hasNext()) { Element pfElement = (Element) pfIter.next(); if (!pfElement.getName().equals("process-fragment")) continue; Iterator pfAttrIter = pfElement.attributeIterator(); String lifecycle = null; String phase = null; while (pfAttrIter.hasNext()) { Attribute attr = (Attribute) pfAttrIter.next(); String attrName = attr.getName(); attrName.toString(); if (attrName.equals("lifecycle")) lifecycle = attr.getValue(); if (attrName.equals("phase")) phase = attr.getValue(); } Document fragment = loadFragment(lifecycle, phase); if (fragment == null) { if (log.isDebugEnabled()) log.debug("No fragments found for " + lifecycle + ":" + phase); continue; } Element rootFragment = fragment.getRootElement(); if (log.isDebugEnabled()) log.debug("Fragment found for " + lifecycle + ":" + phase); if (!rootFragment .getName().equals("process-definition")) { // This is wrong !!! continue; } injectFragment(root, child, fragment); } } } /** * * @param processDef main process definition element (process-definition) * @param fragmentReference reference to a fragment (process-fragment-state) * @param fragmentDefinition a document defining a process, used as a fragment (process-definition) */ protected void injectFragment(Element processDef, Element fragmentReference, Document fragmentDefinition) { // This must be set as the transition of the last added node String nextStateName = getAttributeValue(getFirstChild(fragmentReference, "transition"), "to"); // This is were we must start adding the nodes at process definition, after fragmentReference: int idx = getChildPosition(processDef, fragmentReference) + 1; // A prefix to avoid name coalitions among fragment state names String fragmentPrefix = getAttributeValue(fragmentReference, "name"); // The first element on the fragment definition, normally a process-definition element Element fragmentRoot = fragmentDefinition.getRootElement(); // Fragment elements, we're looking for state elements Iterator itFrag = fragmentRoot.elementIterator(); // The first and last states on the fragment, needed to connect them with the rest of the process through transitions Element lastFragmentState = null; Element firstFragmentState = null; while (itFrag.hasNext()) { // Only interested in state elements (not in start-state, etc) Element fragmentElement = (Element) itFrag.next(); if (fragmentElement.getName().equals("state")) { // Clone the state to add it to the current tree: Element newState = (Element) fragmentElement.clone(); // Rename the state using the prefix Attribute fragmentStateName = getAttribute(newState, "name"); fragmentStateName.setValue(fragmentPrefix + "-" + fragmentStateName.getValue()); // Rename the transition to the next state using the prefix Element t = getFirstChild(newState, "transition"); Attribute tt = getAttribute(t, "to"); tt.setValue(fragmentPrefix + "-" + tt.getValue()); // Add all variables declarations from fragmentReference to these new states Collection<Element> vars = getChildren(fragmentReference,"variable"); for (Iterator<Element> iterator = vars.iterator(); iterator.hasNext(); ) { Element var = iterator.next(); Element newVar = (Element) var.clone(); newState.elements().add(newVar); } // Keep track of first state if (firstFragmentState == null) firstFragmentState = newState; // Keep track of last state lastFragmentState = newState; // Add the new state and increase the index processDef.elements().add(newState); // Increase the index by 2, one for the new element and another to set it at the end idx += 2; } } // If the fragment was not empty, connect first and last states if (lastFragmentState != null) { Element lastT = getFirstChild(lastFragmentState, "transition"); Attribute lastTAttr = getAttribute(lastT, "to"); // This is taken from the transition of the fragment reference lastTAttr.setValue(nextStateName); } if (firstFragmentState != null) { // Update the fragment reference and point it to the first state of the fragment String firstFragmentStateName = getAttributeValue(firstFragmentState, "name"); Element t = getFirstChild(fragmentReference, "transition"); Attribute tt = getAttribute(t, "to"); tt.setValue(firstFragmentStateName); } } protected int getChildPosition(Element parent, Element child) { List e = parent.elements(); for (int i = 0; i < e.size(); i++) { Element element = (Element) e.get(i); if (element.getQName().equals(child.getQName())) return i; } return -1; } protected String getAttributeValue(Element e, String attrName) { Iterator it = e.attributeIterator(); while (it.hasNext()) { Attribute attribute = (Attribute) it.next(); if (attribute.getName().equals(attrName)) return attribute.getValue(); } return null; } protected Attribute getAttribute(Element e, String attrName) { Iterator it = e.attributeIterator(); while (it.hasNext()) { Attribute attribute = (Attribute) it.next(); if (attribute.getName().equals(attrName)) return attribute; } return null; } protected Element getFirstChild(Element e, String eName) { Iterator it = e.elementIterator(); while (it.hasNext()) { Element child = (Element) it.next(); if (child.getName().equals(eName)) return child; } return null; } protected Collection<Element> getChildren(Element e, String eName) { List<Element> children = new ArrayList<Element>(); Iterator it = e.elementIterator(); while (it.hasNext()) { Element child = (Element) it.next(); if (child.getName().equals(eName)) children.add(child); } return children; } protected Element getChildByAttr(Element e, String attrName, String attrValue) { Iterator it = e.elementIterator(); while (it.hasNext()) { Element child = (Element) it.next(); String v = getAttributeValue(child, attrName); if (v != null && v.equals(attrValue)) return child; } return null; } protected Document loadFragment(String lifecycle, String phase) throws Exception { ProcessFragment processFragment = fragmentResolver.findProcessFragment(lifecycle, phase); if (processFragment == null) return null; boolean active = false; for(Object o : processDescriptor.getActiveProcessFragments()) { String activeFragmentName = (String) o; if (activeFragmentName.equals(processFragment.getName())) { active = true; break; } } if (!active) return null; java.io.InputStream is = processFragment.getProcessFragmentDescriptor().getInputStream(); InputSource isrc = new InputSource(is); Document fragmentDoc = JpdlParser.parse(isrc, new JpdlXmlReader(isrc)); // Now we have a new DOM document for the fragment , we need to include it after child ..,(or replacing child !?) return fragmentDoc; } public void setProcessDescriptor(ProcessDescriptor pd) { this.processDescriptor = pd; } public ProcessDescriptor getProcessDescriptor() { return processDescriptor; } }