/* * JBoss, Home of Professional Open Source * Copyright 2008-11, Red Hat Middleware LLC, and others contributors as indicated * by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.savara.bpmn2.internal.parser.choreo.rules; import java.util.logging.Logger; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import org.savara.bpmn2.model.TChoreography; import org.savara.bpmn2.model.TChoreographyTask; import org.savara.bpmn2.model.TExclusiveGateway; import org.savara.bpmn2.model.TFlowElement; import org.savara.bpmn2.model.TFlowNode; import org.savara.bpmn2.model.TInterface; import org.savara.bpmn2.model.TParticipant; import org.savara.bpmn2.model.TSequenceFlow; import org.savara.bpmn2.model.TStartEvent; import org.savara.common.logging.MessageFormatter; import org.savara.common.model.annotation.Annotation; import org.savara.common.model.annotation.AnnotationDefinitions; import org.savara.protocol.model.Join; import org.savara.protocol.model.Fork; import org.savara.protocol.model.util.ChoiceUtil; import org.scribble.protocol.model.Activity; import org.scribble.protocol.model.Block; import org.scribble.protocol.model.Choice; import org.scribble.protocol.model.CustomActivity; import org.scribble.protocol.model.ModelObject; import org.scribble.protocol.model.Parallel; import org.scribble.protocol.model.Protocol; import org.scribble.protocol.model.Role; import org.scribble.protocol.util.ActivityUtil; public class TChoreographyParserRule implements BPMN2ParserRule { private static final String PARTICIPANT_NAMESPACE_PREFIX = "pns"; private static Logger LOG=Logger.getLogger(TChoreographyParserRule.class.getName()); /** * This method determines whether the rule supports the * supplied BPMN2 model element. * * @param elem The element * @return Whether the rule parses the supplied element */ public boolean isSupported(Object elem) { return(elem.getClass() == TChoreography.class); } /** * This method parses the supplied element against the supplied * context. * * @param context The context * @param elem The element * @param container The container into which converted objects should be placed */ public void parse(BPMN2ParserContext context, Object elem, Block container) { TChoreography choreo=(TChoreography)elem; // Need to find the 'start event' TStartEvent startEvent=null; for (JAXBElement<? extends TFlowElement> jaxb : choreo.getFlowElement()) { if (jaxb.getValue().getClass() == TStartEvent.class) { if (startEvent != null) { context.getFeedbackHandler().error(MessageFormatter.format( java.util.PropertyResourceBundle.getBundle( "org.savara.bpmn2.Messages"), "SAVARA-BPMN2-00001"), null); } else { startEvent = (TStartEvent)jaxb.getValue(); } } } if (startEvent == null) { context.getFeedbackHandler().error(MessageFormatter.format( java.util.PropertyResourceBundle.getBundle( "org.savara.bpmn2.Messages"), "SAVARA-BPMN2-00002"), null); } else { processNode(context, startEvent, container); cleanUpJoins(context); // Add introduces statements to the container container.getContents().addAll(0, context.getScope().getIntroduces().values()); defineNamespaces(context, container); } } /** * Check whether some of the parallel constructs, added to support the * fork/join, can be removed to leave a simplified choice/parallel. * * @param context The context */ protected void cleanUpJoins(BPMN2ParserContext context) { // Check the join blocks to see whether the choices can be simplified java.util.Iterator<Block> joinBlocks= context.getScope().getJoinBlocks().values().iterator(); // Remove join blocks that converge on other joins while (joinBlocks.hasNext()) { Block joinBlock=joinBlocks.next(); // Check if parallel with only one other block if (joinBlock.getParent() instanceof Parallel && ((Parallel)joinBlock.getParent()).getPaths().size() == 2) { if (joinBlock.getContents().size() == 2 && joinBlock.getContents().get(0) instanceof Join && joinBlock.getContents().get(1) instanceof Fork) { // Check that join and sync are associated with same role Join join=(Join)joinBlock.getContents().get(0); Fork fork=(Fork)joinBlock.getContents().get(1); if ((join.getRoles().size() == 0 && fork.getRoles().size() == 0) || join.getRoles().containsAll(fork.getRoles())) { Parallel par=(Parallel)joinBlock.getParent(); Block parParent=(Block)par.getParent(); int parIndex=parParent.indexOf(par); // Remove join path, so only remaining block is the // normal content par.getPaths().remove(joinBlock); // Extract contents of other path parParent.remove(par); parParent.getContents().addAll(parIndex, par.getPaths().get(0).getContents()); context.getScope().getParallelReviewList().remove(par); // Substitute labels in join with sync label in connected join Join otherJoin=(Join)context.getScope().getJoin(fork.getLabel()); if (otherJoin != null) { otherJoin.getLabels().remove(fork.getLabel()); otherJoin.getLabels().addAll(join.getLabels()); } // Remove join block joinBlocks.remove(); } } } } // Remove join blocks that have no subsequent activities joinBlocks = context.getScope().getJoinBlocks().values().iterator(); while (joinBlocks.hasNext()) { Block joinBlock=joinBlocks.next(); // Check if parallel with only one other block if (joinBlock.getParent() instanceof Parallel && ((Parallel)joinBlock.getParent()).getPaths().size() == 2) { if (joinBlock.getContents().size() == 1 && joinBlock.getContents().get(0) instanceof Join) { Parallel par=(Parallel)joinBlock.getParent(); Block parParent=(Block)par.getParent(); int parIndex=parParent.indexOf(par); // Remove join path, so only remaining block is the // normal content par.getPaths().remove(joinBlock); // Extract contents of other path parParent.remove(par); parParent.getContents().addAll(parIndex, par.getPaths().get(0).getContents()); context.getScope().getParallelReviewList().remove(par); // Need to find and remove sync's for join Join join=(Join)joinBlock.getContents().get(0); for (String label : join.getLabels()) { Fork sync=context.getScope().getFork(label); ((Block)sync.getParent()).remove(sync); } } } } // Check remaining parallels for (Parallel par : context.getScope().getParallelReviewList()) { if (par.getPaths().size() < 2) { Block parParent=(Block)par.getParent(); int parIndex=parParent.indexOf(par); if (par.getPaths().size() == 1) { parParent.getContents().addAll(parIndex, par.getPaths().get(0).getContents()); } // Remove parallel parParent.remove(par); } } } protected void processNode(BPMN2ParserContext context, TFlowNode elem, Block container) { if (elem.getIncoming().size() > 1) { // Check if join block has already been registered Block b=context.getScope().getJoinBlocks().get(elem); if (b == null) { container = createJoin(context, elem, container); } else { // Check if in scope ModelObject parent=container.getParent(); while (parent != null && parent != b && (parent instanceof Protocol) == false) { parent = parent.getParent(); } if (parent != b) { // Find common enclosing block java.util.List<Block> list=new java.util.Vector<Block>(); list.add(container); list.add(b); Block common=ActivityUtil.getEnclosingBlock(list); if (common != null) { if (!isInScopeOfSingleParallel(common, list)) { // Find parallel parent = common.getParent(); while (parent != null && (parent instanceof Parallel) == false && (parent instanceof Protocol) == false) { parent = parent.getParent(); } if (parent instanceof Parallel) { Parallel oldParallel=(Parallel)b.getParent(); oldParallel.getPaths().remove(b); ((Parallel)parent).getPaths().add(b); // Check if old parallel should be removed/simplified if (oldParallel.getPaths().size() == 0) { ((Block)oldParallel.getParent()).remove(oldParallel); } else if (oldParallel.getPaths().size() == 1) { int pos=((Block)oldParallel.getParent()).indexOf(oldParallel); if (pos == -1) { LOG.severe("Could not find position of parallel"); } else { ((Block)oldParallel.getParent()).getContents().addAll(pos, oldParallel.getPaths().get(0).getContents()); } ((Block)oldParallel.getParent()).remove(oldParallel); context.getScope().getParallelReviewList().remove(oldParallel); } } else { LOG.severe("Unable to find a containing parallel construct"); } } } else { LOG.severe("Failed to find common block"); } } // Don't want to process path further return; } } BPMN2ParserRule rule=ParserRuleFactory.getParserRule(elem); if (rule != null) { rule.parse(context, elem, container); } // Check outbound connections to see whether sequence or gateway if (elem.getOutgoing().size() == 1) { // Get link TSequenceFlow seq=(TSequenceFlow) context.getScope().getBPMN2Element(elem.getOutgoing().get(0).getLocalPart()); if (seq != null) { Object target=seq.getTargetRef(); if (target instanceof TFlowNode) { if (((TFlowNode)target).getIncoming().size() > 1) { // Add sync Fork sync=new Fork(); sync.setLabel(getJoinName(elem.getOutgoing().get(0).getLocalPart())); // Get role getRoles(context, (TFlowNode)target, sync); container.add(sync); context.getScope().registerFork(sync); } processNode(context, (TFlowNode)target, container); } } } else if (elem.getOutgoing().size() > 1) { if (elem instanceof TExclusiveGateway) { // Create outer parallel Parallel parallel=new Parallel(); container.add(parallel); // Add to review list context.getScope().getParallelReviewList().add(parallel); Block mainBlock=new Block(); parallel.getPaths().add(mainBlock); Choice choice=new Choice(); mainBlock.add(choice); for (QName seqFlowQName : elem.getOutgoing()) { TSequenceFlow seq=(TSequenceFlow) context.getScope().getBPMN2Element(seqFlowQName.getLocalPart()); Block b=new Block(); choice.getPaths().add(b); if (seq.getTargetRef() instanceof TFlowNode) { if (((TFlowNode)seq.getTargetRef()).getIncoming().size() > 1) { // Add sync Fork sync=new Fork(); sync.setLabel(getJoinName(seqFlowQName.getLocalPart())); // Get role getRoles(context, (TFlowNode)seq.getTargetRef(), sync); b.add(sync); context.getScope().registerFork(sync); } processNode(context, (TFlowNode)seq.getTargetRef(), b); } } // Identify decision making role for choice Role role=ChoiceUtil.getDecisionMaker(choice); if (role != null) { choice.setRole(new Role(role)); } } else { // Create outer parallel Parallel parallel=new Parallel(); container.add(parallel); for (QName seqFlowQName : elem.getOutgoing()) { TSequenceFlow seq=(TSequenceFlow) context.getScope().getBPMN2Element(seqFlowQName.getLocalPart()); Block b=new Block(); parallel.getPaths().add(b); if (seq.getTargetRef() instanceof TFlowNode) { if (((TFlowNode)seq.getTargetRef()).getIncoming().size() > 1) { // Add sync Fork sync=new Fork(); sync.setLabel(getJoinName(seqFlowQName.getLocalPart())); // Get role getRoles(context, (TFlowNode)seq.getTargetRef(), sync); b.add(sync); context.getScope().registerFork(sync); } processNode(context, (TFlowNode)seq.getTargetRef(), b); } } } } } /** * This method identifies the namespaces for the roles associated with the * containing protocol. * * @param context The BPMN2 context * @param container The container */ protected void defineNamespaces(BPMN2ParserContext context, Block container) { Protocol protocol=container.getEnclosingProtocol(); String tns=context.getScope().getDefinitions().getTargetNamespace(); if (protocol != null && tns != null && tns.trim().length() > 0) { int count=1; for (Role role : protocol.getRoles()) { String namespace=tns+"/"+role.getName(); String name=role.getName(); QName intf=getInterfaceName(context, role); if (intf != null) { namespace = intf.getNamespaceURI(); name = intf.getLocalPart(); } String prefix=PARTICIPANT_NAMESPACE_PREFIX+count++; Annotation pann=new Annotation(AnnotationDefinitions.INTERFACE); pann.getProperties().put(AnnotationDefinitions.NAMESPACE_PROPERTY, namespace); pann.getProperties().put(AnnotationDefinitions.NAME_PROPERTY, name); pann.getProperties().put(AnnotationDefinitions.ROLE_PROPERTY, role.getName()); protocol.getAnnotations().add(pann); // Add Type import to define namespace prefix for targetNamespace pann = new Annotation(AnnotationDefinitions.TYPE); pann.getProperties().put(AnnotationDefinitions.PREFIX_PROPERTY, prefix); pann.getProperties().put(AnnotationDefinitions.NAMESPACE_PROPERTY, namespace); protocol.getAnnotations().add(pann); } } } /** * This method determines the interface qname associated with the supplied * role. * * @param context The context * @param role The role * @return The QName for the interface, or null if not found */ protected QName getInterfaceName(BPMN2ParserContext context, Role role) { QName ret=null; for (TParticipant part : context.getScope().getChoreography().getParticipant()) { if (part.getName().equals(role.getName())) { // TODO: What if multiple interfaces?? if (part.getInterfaceRef().size() > 0) { QName intfQName=part.getInterfaceRef().get(0); Object intf=context.getScope().getBPMN2Element(intfQName.getLocalPart()); if (intf instanceof TInterface) { ret = ((TInterface)intf).getImplementationRef(); } } break; } } return(ret); } protected void getRoles(BPMN2ParserContext context, TFlowNode node, CustomActivity activity) { //Role ret=null; if (node instanceof TChoreographyTask) { TChoreographyTask task=(TChoreographyTask)node; for (QName qname : task.getParticipantRef()) { TParticipant p=(TParticipant) context.getScope().getBPMN2Element(qname.getLocalPart()); if (p != null) { // TODO: May need to determine whether role should be added - if // not in scope of the join - could check context, but problem is // a later use may have it in the context, but still be // not relevant to the join Role r=new Role(p.getName()); if (!activity.getRoles().contains(r)) { activity.getRoles().add(r); } } else { LOG.severe("Could not find participant for id '"+qname.getLocalPart()+"'"); } } } else if (node.getOutgoing().size() > 0) { for (QName qname : node.getOutgoing()) { TSequenceFlow seqFlow=(TSequenceFlow) context.getScope().getBPMN2Element(qname.getLocalPart()); TFlowNode otherNode=(TFlowNode)seqFlow.getTargetRef(); if (otherNode != null) { getRoles(context, otherNode, activity); /* if (ret == null) { ret = r; } else if (ret.equals(r) == false) { context.getFeedbackHandler().error("Inconsistent initiating roles after gateway '"+ ret+"' and '"+r+"'", null); } */ } else { LOG.severe("Unable to find node for '"+qname.getLocalPart()+"'"); } } /* if (count > 0 && count < node.getOutgoing().size()) { context.getFeedbackHandler().error("Path does not identify an initiating participant", null); } */ } //return(ret); } /** * This method determines whether the supplied list of blocks are inscope of * a single parallel construct that is contained within the supplied block. * * @param common The block to check * @param list The list of blocks that must be in scope of a single parallel * @return Whether the list of blocks are in the scope of a single parallel */ protected boolean isInScopeOfSingleParallel(Block common, java.util.List<Block> list) { // Check if parallel contained in block // and also has the listed blocks contained // directly or indirectly boolean inscope=false; for (Activity act : common.getContents()) { if (act instanceof Parallel) { // Check if all blocks are indirectly contained in // this parallel java.util.List<Block> blks=new java.util.Vector<Block>(list); for (Block path : ((Parallel)act).getPaths()) { for (int i=blks.size()-1; i >= 0; i--) { Block blk=blks.get(i); java.util.List<Block> sublist= new java.util.Vector<Block>(); sublist.add(blk); sublist.add(path); Block enclosing=ActivityUtil.getEnclosingBlock(sublist); if (enclosing == path) { blks.remove(blk); } } } if (blks.size() == 0) { inscope = true; break; } } } return(inscope); } /** * This method constructs the join path, and returns the new container associated * with it. * * @param context The context * @param elem The flow node * @param container The existing container * @return The new container */ protected Block createJoin(BPMN2ParserContext context, TFlowNode elem, Block container) { // Add join Join join=new Join(); for (QName qname : elem.getIncoming()) { join.getLabels().add(getJoinName(qname.getLocalPart())); context.getScope().registerJoin(join); } getRoles(context, elem, join); join.setXOR(elem instanceof TExclusiveGateway); // Find parent parallel construct ModelObject parent=container.getParent(); while (parent != null && (parent instanceof Parallel) == false && (parent instanceof Protocol) == false) { parent = parent.getParent(); } // Need to create new container for subsequent processing container = new Block(); if (parent instanceof Parallel) { ((Parallel)parent).getPaths().add(container); } else if (parent instanceof Protocol) { // TODO: What if no parallel??? LOG.severe("No enclosing parallel construct in protocol"); } else { LOG.severe("No enclosing parallel construct"); } container.add(join); // Register block context.getScope().getJoinBlocks().put(elem, container); return(container); } protected String getJoinName(String label) { String ret=label; if (ret != null && ret.startsWith("SequenceFlow_")) { ret = ret.replaceAll("SequenceFlow_", "L"); } return(ret); } }