Java tutorial
/** * ******************************************************* * Copyright (C) 2013 catify <info@catify.com> * ******************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.catify.processengine.core; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBElement; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import com.catify.processengine.core.data.model.entities.ArchiveNode; import com.catify.processengine.core.data.model.entities.ClientNode; import com.catify.processengine.core.data.model.entities.FlowNode; import com.catify.processengine.core.data.model.entities.FlowNodeFactory; import com.catify.processengine.core.data.model.entities.ProcessInstanceNode; import com.catify.processengine.core.data.model.entities.ProcessNode; import com.catify.processengine.core.data.model.entities.RootNode; import com.catify.processengine.core.data.model.entities.RunningNode; import com.catify.processengine.core.data.services.ArchivedNodeRepositoryService; import com.catify.processengine.core.data.services.ClientNodeRepositoryService; import com.catify.processengine.core.data.services.FlowNodeRepositoryService; import com.catify.processengine.core.data.services.IdService; import com.catify.processengine.core.data.services.ProcessNodeRepositoryService; import com.catify.processengine.core.data.services.RootNodeRepositoryService; import com.catify.processengine.core.data.services.RunningNodeRepositoryService; import com.catify.processengine.core.nodes.NodeFactory; import com.catify.processengine.core.nodes.ServiceNodeBridge; import com.catify.processengine.core.processdefinition.jaxb.TFlowElement; import com.catify.processengine.core.processdefinition.jaxb.TFlowNode; import com.catify.processengine.core.processdefinition.jaxb.TProcess; import com.catify.processengine.core.processdefinition.jaxb.TSequenceFlow; import com.catify.processengine.core.processdefinition.jaxb.TSubProcess; import com.catify.processengine.core.processdefinition.jaxb.services.ExtensionService; import com.catify.processengine.core.services.ActorReferenceService; /** * EntityInitialization creates the data representation of a bpmn process and the runtime logic service node actors. <p> * It will create the basic node entities ({@link ClientNode}, {@link ArchiveNode}, {@link RunningNode}) and the process specific node entities ({@link ProcessNode}, * {@link ProcessInstanceNode} and {@link FlowNode}) in the database via the repository services. <p> * The service node actor implementation is handled by the {@link NodeFactory}. * * @author christopher kster * */ @Configurable public class EntityInitialization { static final Logger LOG = LoggerFactory.getLogger(EntityInitialization.class); /** The actor system. */ @Autowired private ActorSystem actorSystem; /** The neo4j template. */ @Autowired private Neo4jTemplate neo4jTemplate; /** The root node repository service. */ @Autowired private RootNodeRepositoryService rootNodeRepositoryService; /** The client node repository service. */ @Autowired private ClientNodeRepositoryService clientNodeRepositoryService; /** The process repository service. */ @Autowired private ProcessNodeRepositoryService processRepositoryService; /** The flow node repository service. */ @Autowired private FlowNodeRepositoryService flowNodeRepositoryService; /** The archived node repository service. */ @Autowired private ArchivedNodeRepositoryService archivedNodeRepositoryService; /** The running node repository service. */ @Autowired private RunningNodeRepositoryService runningNodeRepositoryService; /** * Initialize neo4j data beans. * * @param clientId the client id * @param processJaxb the process object generated by jaxb */ @Transactional public synchronized void initializeProcess(String clientId, TProcess processJaxb) { List<TFlowNode> flowNodesJaxb = new ArrayList<TFlowNode>(); List<TSequenceFlow> sequenceFlowsJaxb = new ArrayList<TSequenceFlow>(); // iterate through process elements and separate flow nodes and // sequenceFlows (because they need to be activated after each other) for (JAXBElement<? extends TFlowElement> flowElementJaxb : processJaxb.getFlowElement()) { LOG.debug(String.format("Instantiating %s:%s as a neo4j data node", flowElementJaxb.getDeclaredType().getSimpleName(), flowElementJaxb.getValue().getId())); if (flowElementJaxb.getDeclaredType().equals(TSequenceFlow.class)) { sequenceFlowsJaxb.add((TSequenceFlow) flowElementJaxb.getValue()); } else if (flowElementJaxb.getValue() instanceof TFlowNode) { flowNodesJaxb.add((TFlowNode) flowElementJaxb.getValue()); } } // Save the process to the neo4j db or retrieve it if it already exists this.createEntities(clientId, processJaxb, flowNodesJaxb, sequenceFlowsJaxb); } /** * Save jaxb objects to the database. * * @param processJaxb * the process generated by jaxb * @param flowNodesJaxb * the list of jaxb flow nodes in that process * @param sequenceFlowsJaxb * the list of jaxb sequence flows in that process */ private void createEntities(String clientId, TProcess processJaxb, List<TFlowNode> flowNodesJaxb, List<TSequenceFlow> sequenceFlowsJaxb) { // create the client context ClientNode clientNode = this.createClientContext(clientId); // create the running process node or get it from the db (eg. restart of the process engine) ProcessNode runningProcess = this.createRunningProcessNode(clientId, clientNode, processJaxb); // create the archived process node or get it from the db (eg. restart of the process engine) ProcessNode archivedProcess = this.createArchivedProcessNode(clientId, clientNode, processJaxb); // create the flow nodes (database and runtime) this.createFlowNodes(clientId, processJaxb, new ArrayList<TSubProcess>(), flowNodesJaxb, sequenceFlowsJaxb, runningProcess, archivedProcess); // persist process nodes processRepositoryService.save(runningProcess); processRepositoryService.save(archivedProcess); // save changes made to this client clientNodeRepositoryService.save(clientNode); } /** * Creates the flow nodes (database) and service nodes (runtime) of a process. * * @param clientId the client id * @param processJaxb the process jaxb * @param flowNodesJaxb the flow nodes jaxb * @param sequenceFlowsJaxb the sequence flows jaxb * @param runningParentNode the neo4j process * @param runningPath true, if working the running process path, false if working on the archive process path */ private void createFlowNodes(String clientId, TProcess processJaxb, ArrayList<TSubProcess> subProcessesJaxb, List<TFlowNode> flowNodesJaxb, List<TSequenceFlow> sequenceFlowsJaxb, Object runningParentNode, Object archiveParentNode) { // map between jaxb flow elements and neo4j flow nodes (to be able to later connect these nodes) Map<TFlowNode, FlowNode> jaxbToNeo4jRunningProcess = new HashMap<TFlowNode, FlowNode>(); Map<TFlowNode, FlowNode> jaxbToNeo4jArchiveProcess = new HashMap<TFlowNode, FlowNode>(); // create the top level flow nodes and connect them to the process for (TFlowNode flowNodeJaxb : flowNodesJaxb) { // create running nodes (database) FlowNode runningFlowNode = createRunningFlowNode(clientId, processJaxb, flowNodeJaxb, subProcessesJaxb, runningParentNode); jaxbToNeo4jRunningProcess.put(flowNodeJaxb, runningFlowNode); // create archive nodes (database) FlowNode archiveFlowNode = createArchiveFlowNode(clientId, processJaxb, flowNodeJaxb, subProcessesJaxb, archiveParentNode); jaxbToNeo4jArchiveProcess.put(flowNodeJaxb, archiveFlowNode); // create service nodes (runtime) createNodeServiceActor(clientId, processJaxb, subProcessesJaxb, flowNodeJaxb, sequenceFlowsJaxb); // create the sub process flow nodes and connect them to their parent nodes if (flowNodeJaxb instanceof TSubProcess) { // We need an ordered list of sub processes, because there again could be nested sub // processes in sub processes. We therefore need to recursively resolve them. // For this a new variable needs to be created for every recursive call which is // then filled with the list of previous sub processes. ArrayList<TSubProcess> recursiveSubProcessesJaxb = new ArrayList<TSubProcess>(subProcessesJaxb); recursiveSubProcessesJaxb.add((TSubProcess) flowNodeJaxb); this.createSubProcessNodes(clientId, processJaxb, recursiveSubProcessesJaxb, runningFlowNode, archiveFlowNode); } } // create sequence flows between the flow nodes for (TSequenceFlow sequenceFlowJaxb : sequenceFlowsJaxb) { this.connectFlowNodes(sequenceFlowJaxb, jaxbToNeo4jRunningProcess); this.connectFlowNodes(sequenceFlowJaxb, jaxbToNeo4jArchiveProcess); } // persist sub process flow nodes if (runningParentNode instanceof FlowNode && archiveParentNode instanceof FlowNode) { flowNodeRepositoryService.save((FlowNode) runningParentNode); flowNodeRepositoryService.save((FlowNode) archiveParentNode); } } /** * Creates a sub process. * <p> * Can be called recursively to create nested sub processes. * * @param clientId the client id * @param processJaxb the process jaxb * @param subProcessesJaxb the sub processes jaxb * @param parentNodeRunning the parent node * @param runningPath true, if working the running process path, false if working on the archive process path */ private void createSubProcessNodes(String clientId, TProcess processJaxb, ArrayList<TSubProcess> subProcessesJaxb, FlowNode parentNodeRunning, FlowNode parentNodeArchive) { List<TFlowNode> flowNodesJaxb = new ArrayList<TFlowNode>(); List<TSequenceFlow> sequenceFlowsJaxb = new ArrayList<TSequenceFlow>(); // only check the (currently) last iteration of the list sub processes if (subProcessesJaxb.size() > 0) { TSubProcess subProcessJaxb = subProcessesJaxb.get(subProcessesJaxb.size() - 1); for (JAXBElement<? extends TFlowElement> flowElementJaxb : subProcessJaxb.getFlowElement()) { if (flowElementJaxb.getDeclaredType().equals(TSequenceFlow.class)) { sequenceFlowsJaxb.add((TSequenceFlow) flowElementJaxb.getValue()); } else if (flowElementJaxb.getValue() instanceof TFlowNode) { flowNodesJaxb.add((TFlowNode) flowElementJaxb.getValue()); } } // create the flow nodes that belong to this sub process this.createFlowNodes(clientId, processJaxb, subProcessesJaxb, flowNodesJaxb, sequenceFlowsJaxb, parentNodeRunning, parentNodeArchive); } } /** * Creates the running flow node. * TODO --> what is a running flow node? * * @param clientId the client id * @param processJaxb the process jaxb * @param flowNodeJaxb the flow node jaxb * @param subProcessesJaxb the sub processes jaxb * @param runningParentNode the running parent node * @return the flow node */ private FlowNode createRunningFlowNode(String clientId, TProcess processJaxb, TFlowNode flowNodeJaxb, ArrayList<TSubProcess> subProcessesJaxb, Object runningParentNode) { // create running flow node FlowNode runningFlowNode = flowNodeRepositoryService .getOrCreateFlowNode(FlowNodeFactory.createFlowNode(flowNodeJaxb, IdService.getUniqueFlowNodeId(clientId, processJaxb, subProcessesJaxb, flowNodeJaxb))); LOG.debug(String.format("Added %s as %s with grapId: %s to neo4j db (running node)", flowNodeJaxb.getName(), runningFlowNode, runningFlowNode.getGraphId())); // depending on the type of the parent node, add a relationship between the new flow node and its parent if (runningParentNode instanceof ProcessNode) { ((ProcessNode) runningParentNode).addRelationshipToFlowNode(runningFlowNode); } else if (runningParentNode instanceof FlowNode) { ((FlowNode) runningParentNode).addRelationshipToSubProcessNode(runningFlowNode); } return runningFlowNode; } /** * Creates the archive flow node. * * @param clientId the client id * @param processJaxb the process jaxb * @param flowNodeJaxb the flow node jaxb * @param subProcessesJaxb the sub processes jaxb * @param archiveParentNode the archive parent node * @return the flow node */ private FlowNode createArchiveFlowNode(String clientId, TProcess processJaxb, TFlowNode flowNodeJaxb, ArrayList<TSubProcess> subProcessesJaxb, Object archiveParentNode) { // create archive node FlowNode archiveFlowNode = flowNodeRepositoryService .getOrCreateFlowNode(FlowNodeFactory.createFlowNode(flowNodeJaxb, IdService.ARCHIVEPREFIX + IdService.getUniqueFlowNodeId(clientId, processJaxb, subProcessesJaxb, flowNodeJaxb))); LOG.debug(String.format("Added %s as %s with grapId: %s to neo4j db (archive node)", flowNodeJaxb.getName(), archiveFlowNode, archiveFlowNode.getGraphId())); // depending on the type of the parent node, add a relationship between the new flow node and its parent if (archiveParentNode instanceof ProcessNode) { ((ProcessNode) archiveParentNode).addRelationshipToFlowNode(archiveFlowNode); } else if (archiveParentNode instanceof FlowNode) { ((FlowNode) archiveParentNode).addRelationshipToSubProcessNode(archiveFlowNode); } return archiveFlowNode; } /** * Creates the client context. * <p> * Gets or creates the {@link RootNode} and {@link ClientNode} and creates a * relationship to the given process node. * * @param clientId * the client id * @return the client node */ private ClientNode createClientContext(String clientId) { // FIXME: provide RootNode implementation // create root node or get it from the db RootNode rootNode = createRootContext(); // create client node or get it from the db ClientNode clientNode = clientNodeRepositoryService.getOrCreateClientNode(clientId); LOG.debug(String.format("Added %s with grapId: %s to neo4j db", clientNode, clientNode.getGraphId())); // create relationship between root and client rootNode.addRelationshipToClientNode(clientNode); rootNodeRepositoryService.save(rootNode); return clientNode; } /** * Creates the root context. * * @return the root node */ private RootNode createRootContext() { RootNode rootNode = rootNodeRepositoryService.getOrCreateRootNode("secretRootId"); LOG.debug(String.format("Added %s with grapId: %s to neo4j db", rootNode, rootNode.getGraphId())); RelationshipType referenceNodeRelationship = new RelationshipType() { @Override public String name() { return "REFERENCE_NODE"; } }; Node n = neo4jTemplate.getPersistentState(rootNode); neo4jTemplate.getReferenceNode().createRelationshipTo(n, referenceNodeRelationship); rootNodeRepositoryService.save(rootNode); return rootNode; } /** * Creates the running process node. * * @param clientId the client id * @param clientNode the client node * @param processJaxb the process jaxb * @return the process node */ private ProcessNode createRunningProcessNode(String clientId, ClientNode clientNode, TProcess processJaxb) { // create the running node RunningNode runningNode = runningNodeRepositoryService.getOrCreateRunningNode(clientId); clientNode.addRelationshipToRunningProcessNode(runningNode); clientNodeRepositoryService.save(clientNode); LOG.debug(String.format("Added %s with grapId: %s to neo4j db", runningNode, runningNode.getGraphId())); // create the running process node or get it from the db (eg. restart of // the process engine) ProcessNode runningProcess = processRepositoryService.getOrCreateProcessNode( new ProcessNode(IdService.getUniqueProcessId(clientId, processJaxb), processJaxb.getId(), processJaxb.getName(), ExtensionService.getTVersion(processJaxb).getVersion())); runningNode.addRelationshipToProcessNode(runningProcess); runningNodeRepositoryService.save(runningNode); LOG.debug( String.format("Added %s with grapId: %s to neo4j db", runningProcess, runningProcess.getGraphId())); return runningProcess; } /** * Creates the archived process node. * * @param clientId the client id * @param clientNode the client node * @param processJaxb the process jaxb * @return the process node */ private ProcessNode createArchivedProcessNode(String clientId, ClientNode clientNode, TProcess processJaxb) { // create the archive node ArchiveNode archiveNode = archivedNodeRepositoryService.getOrCreateArchivedNode(clientId); clientNode.addRelationshipToArchivedProcessNode(archiveNode); LOG.debug(String.format("Added %s with grapId: %s to neo4j db", archiveNode, archiveNode.getGraphId())); // create the archived process node or get it from the db (eg. restart of // the process engine) ProcessNode archivedProcess = processRepositoryService.getOrCreateProcessNode(new ProcessNode( IdService.ARCHIVEPREFIX + IdService.getUniqueProcessId(clientId, processJaxb), processJaxb.getId(), processJaxb.getName(), ExtensionService.getTVersion(processJaxb).getVersion())); archiveNode.addRelationshipToProcessNode(archivedProcess); archivedNodeRepositoryService.save(archiveNode); LOG.debug(String.format("Added %s with grapId: %s to neo4j db", archivedProcess, archivedProcess.getGraphId())); return archivedProcess; } /** * Connect flow nodes via sequence flows. * * @param sequenceFlowJaxb * the jaxb sequence flow holding the source and target to * connect * @param jaxbToNeo4j * the map that maps jaxb objects to their corresponding neo4j * node objects */ private void connectFlowNodes(TSequenceFlow sequenceFlowJaxb, Map<TFlowNode, FlowNode> jaxbToNeo4j) { FlowNode sourceNode = jaxbToNeo4j.get(sequenceFlowJaxb.getSourceRef()); FlowNode targetNode = jaxbToNeo4j.get(sequenceFlowJaxb.getTargetRef()); sourceNode.addFollowingFlowNodes(neo4jTemplate, targetNode); LOG.debug(String.format("Connecting %s:%s and %s:%s in neo4j db", sourceNode.getClass().getSimpleName(), sourceNode.getUniqueFlowNodeId(), targetNode.getClass().getSimpleName(), targetNode.getUniqueFlowNodeId())); } /** * Creates the node service actor (runtime representation of a flow node). * * @param clientId the client id * @param processJaxb the process jaxb * @param subProcessesJaxb the sub processes jaxb * @param flowNodeJaxb the flow node jaxb * @param sequenceFlowsJaxb the sequence flows jaxb * @return the actor reference */ private ActorRef createNodeServiceActor(String clientId, TProcess processJaxb, ArrayList<TSubProcess> subProcessesJaxb, TFlowNode flowNodeJaxb, List<TSequenceFlow> sequenceFlowsJaxb) { // create flow node actors (a bridge factory is used to be able to pass parameters to the UntypedActorFactory) ActorRef nodeServiceActor = this.actorSystem.actorOf( new Props(new ServiceNodeBridge(clientId, processJaxb, subProcessesJaxb, flowNodeJaxb, sequenceFlowsJaxb)).withDispatcher("file-mailbox-dispatcher"), ActorReferenceService.getActorReferenceString( IdService.getUniqueFlowNodeId(clientId, processJaxb, subProcessesJaxb, flowNodeJaxb))); LOG.debug(String.format("%s --> resulting akka object: %s", flowNodeJaxb, nodeServiceActor.toString())); return nodeServiceActor; } }