/* * JBoss, Home of Professional Open Source * Copyright 2008-12, 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.generation.choreo; import java.util.logging.Logger; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import org.savara.bpmn2.internal.generation.BPMN2GenerationException; import org.savara.bpmn2.internal.generation.BPMN2ModelFactory; import org.savara.bpmn2.internal.generation.BPMN2NotationFactory; import org.savara.bpmn2.internal.generation.components.AbstractBPMNActivity; import org.savara.bpmn2.internal.generation.components.BPMNActivity; import org.savara.bpmn2.internal.generation.components.BPMNDiagram; import org.savara.bpmn2.internal.generation.components.ChoiceActivity; import org.savara.bpmn2.internal.generation.components.Choreography; import org.savara.bpmn2.internal.generation.components.ChoreographyTask; import org.savara.bpmn2.internal.generation.components.DoActivity; import org.savara.bpmn2.internal.generation.components.DoBlockActivity; import org.savara.bpmn2.internal.generation.components.ForkActivity; import org.savara.bpmn2.internal.generation.components.JoinActivity; import org.savara.bpmn2.internal.generation.components.ParallelActivity; import org.savara.bpmn2.internal.generation.components.RepeatActivity; import org.savara.bpmn2.internal.generation.components.RunActivity; import org.savara.bpmn2.internal.generation.components.SequenceActivity; import org.savara.bpmn2.model.TDefinitions; import org.savara.bpmn2.model.TError; import org.savara.bpmn2.model.TImport; import org.savara.bpmn2.model.TInterface; import org.savara.bpmn2.model.TItemDefinition; import org.savara.bpmn2.model.TMessage; import org.savara.bpmn2.model.TOperation; import org.savara.bpmn2.model.TRootElement; import org.savara.bpmn2.util.BPMN2ServiceUtil; import org.savara.common.logging.FeedbackHandler; import org.savara.common.model.annotation.AnnotationDefinitions; import org.savara.common.model.annotation.Annotation; import org.savara.common.model.generator.ModelGenerator; import org.savara.common.resources.ResourceLocator; import org.savara.protocol.model.Join; import org.savara.protocol.model.Fork; import org.savara.protocol.model.util.InteractionUtil; import org.scribble.protocol.model.*; /** * This class represents the Protocol to BPMN2 Choreography implementation of the model * generator interface. */ public class ProtocolToBPMN2ChoreoModelGenerator implements ModelGenerator { private static final String XSD_NAMESPACE = "http://www.w3.org/2001/XMLSchema"; private static final String BPMN_FILE_EXTENSION = ".bpmn"; private boolean _consecutiveIds=false; private org.savara.bpmn2.model.ObjectFactory _objectFactory=new org.savara.bpmn2.model.ObjectFactory(); private static final Logger logger=Logger.getLogger(ProtocolToBPMN2ChoreoModelGenerator.class.getName()); /** * This method determines whether consecutive ids should be used in the model and * notation. If false (default), then random unique ids will be used. * * @param b Whether to use consecutive ids */ public void setUseConsecutiveIds(boolean b) { _consecutiveIds = b; } /** * This method determines whether the generator is appropriate for * the specified source and target types. * * @param source The source * @param targetType The target type * @return Whether the specified types are supported */ public boolean isSupported(Object source, String targetType) { return(source instanceof ProtocolModel && !((ProtocolModel)source).isLocated() && (targetType.equals("bpmn2") || targetType.equals("bpmn"))); } /** * {@inheritDoc} */ public java.util.Map<String,Object> generate(Object source, FeedbackHandler handler, ResourceLocator locator) { java.util.Map<String,Object> ret=new java.util.HashMap<String,Object>(); if (source instanceof ProtocolModel) { ProtocolModel pm=(ProtocolModel)source; // Obtain any namespace prefix map java.util.Map<String, String> prefixes= new java.util.HashMap<String, String>(); java.util.List<Annotation> list= AnnotationDefinitions.getAnnotations(pm.getProtocol().getAnnotations(), AnnotationDefinitions.TYPE); for (Annotation annotation : list) { if (annotation.getProperties().containsKey(AnnotationDefinitions.NAMESPACE_PROPERTY) && annotation.getProperties().containsKey(AnnotationDefinitions.PREFIX_PROPERTY)) { prefixes.put((String)annotation.getProperties().get(AnnotationDefinitions.NAMESPACE_PROPERTY), (String)annotation.getProperties().get(AnnotationDefinitions.PREFIX_PROPERTY)); } } processProtocol(pm, pm.getProtocol(), ret, handler, locator, prefixes); } return(ret); } protected String getProtocolName(Protocol p) { String ret=p.getName(); if (p.getParent() instanceof Protocol) { ret = getProtocolName((Protocol)p.getParent())+"_"+ret; } return(ret); } /** * This method processes a protocol to derive the BPMN2 choreography model for the * top level or nested protocols. * * @param pm The protocol model * @param p The protocol (top level or nested) * @param modelMap The model map * @param handler The handler * @param locator The resource locator * @param prefixes The prefix map * @return The model file */ protected String processProtocol(ProtocolModel pm, Protocol p, java.util.Map<String,Object> modelMap, FeedbackHandler handler, ResourceLocator locator, java.util.Map<String,String> prefixes) { TDefinitions defns=new TDefinitions(); // Setup prefixes for (String ns : prefixes.keySet()) { String prefix=prefixes.get(ns); defns.getOtherAttributes().put(new QName(null,"xmlns:"+prefix), ns); } // Set an id on the definition - not strictly required, // although the BPMN2 modeler currently seems to want // it defns.setId("id-"+pm.getProtocol().getName()); org.savara.bpmn2.internal.generation.BPMN2ModelFactory model= new org.savara.bpmn2.internal.generation.BPMN2ModelFactory(defns); org.savara.bpmn2.internal.generation.BPMN2NotationFactory notation= new org.savara.bpmn2.internal.generation.BPMN2NotationFactory(model); model.setUseConsecutiveIds(_consecutiveIds); notation.setUseConsecutiveIds(_consecutiveIds); // Find namespace for role initNamespace(defns, pm, p); initImports(defns, pm); initMessages(defns, pm, prefixes); String modelName=getProtocolName(p); BPMN2ModelVisitor visitor= new BPMN2ModelVisitor(modelName, p, model, notation); generateChoreography(p, visitor, handler, locator); visitor.completeModels(); // Define interfaces for the choreography /* java.util.Map<TParticipant,TInterface> intfs= BPMN2ServiceUtil.introspect(defns); if (intfs.size() > 0) { BPMN2ServiceUtil.merge(defns, intfs); } else if (logger.isLoggable(Level.FINE)) { logger.fine("No interfaces detected in generated BPMN2 choreography"); } */ String ret=modelName+BPMN_FILE_EXTENSION; modelMap.put(ret, defns); // Check if nested protocols have been defined for (Protocol nested : p.getNestedProtocols()) { String nestedModelFile=processProtocol(pm, nested, modelMap, handler, locator, prefixes); TDefinitions nestedDefn=(TDefinitions)modelMap.get(nestedModelFile); // Define import for nested protocol TImport imp=new TImport(); imp.setImportType("http://www.omg.org/spec/BPMN/20100524/MODEL"); imp.setLocation(nestedModelFile); imp.setNamespace(nestedDefn.getTargetNamespace()); defns.getImport().add(imp); } return(ret); } protected void initNamespace(TDefinitions defns, ProtocolModel pm, Protocol p) { Annotation ann=AnnotationDefinitions.getAnnotation(p.getAnnotations(), AnnotationDefinitions.PROTOCOL); if (ann != null) { defns.setTargetNamespace((String)ann.getProperties().get(AnnotationDefinitions.NAMESPACE_PROPERTY)); // Make sure a namespace prefix is defined for the target namespace defns.getOtherAttributes().put(new QName(null, "xmlns:tns"), defns.getTargetNamespace()); } } protected void initImports(TDefinitions defns, ProtocolModel pm) { java.util.List<Annotation> anns=AnnotationDefinitions.getAnnotations(pm.getProtocol().getAnnotations(), AnnotationDefinitions.TYPE); for (Annotation ann : anns) { String ns=(String)ann.getProperties().get(AnnotationDefinitions.NAMESPACE_PROPERTY); String loc=(String)ann.getProperties().get(AnnotationDefinitions.LOCATION_PROPERTY); if (!ns.equals(XSD_NAMESPACE) && loc != null) { // Add import TImport imp=new TImport(); imp.setImportType(XSD_NAMESPACE); // Assume xsd for now imp.setLocation(loc); imp.setNamespace(ns); defns.getImport().add(imp); } } } protected void initMessages(TDefinitions defns, ProtocolModel pm, java.util.Map<String,String> prefixes) { for (ImportList il : pm.getImports()) { if (il instanceof TypeImportList) { TypeImportList til=(TypeImportList)il; for (TypeImport ti : til.getTypeImports()) { TItemDefinition itemDef=new TItemDefinition(); itemDef.setId("ITEM"+ti.getName()); itemDef.setStructureRef(createQName(ti.getDataType().getDetails(), prefixes)); defns.getRootElement().add(_objectFactory.createItemDefinition(itemDef)); TMessage mesg=new TMessage(); mesg.setId("ID"+ti.getName()); mesg.setName(ti.getName()); mesg.setItemRef(new QName(defns.getTargetNamespace(),itemDef.getId(), "tns")); defns.getRootElement().add(_objectFactory.createMessage(mesg)); } } } } protected QName createQName(String qname, java.util.Map<String,String> prefixes) { QName ret=QName.valueOf(qname); String prefix=prefixes.get(ret.getNamespaceURI()); if (prefix != null) { ret = new QName(ret.getNamespaceURI(), ret.getLocalPart(), prefix); } return (ret); } protected TInterface getInterface(TDefinitions defns, Role role) { TInterface ret=null; String intfName=role.getName(); for (JAXBElement<? extends TRootElement> rootElem : defns.getRootElement()) { if (rootElem.getValue() instanceof TInterface && ((TInterface)rootElem.getValue()).getName().equals(intfName)) { ret = (TInterface)rootElem.getValue(); break; } } if (ret == null) { ret = new TInterface(); ret.setId(intfName+BPMN2ServiceUtil.INTERFACE_ID_SUFFIX); ret.setName(intfName); defns.getRootElement().add(_objectFactory.createInterface(ret)); } return(ret); } protected void generateChoreography(Protocol p, BPMN2ModelVisitor visitor, FeedbackHandler handler, ResourceLocator locator) { p.visit(visitor); } public class BPMN2ModelVisitor extends DefaultVisitor { private static final String TARGET_NAMESPACE_PREFIX = "tns"; private BPMN2ModelFactory _modelFactory=null; private BPMN2NotationFactory _notationFactory=null; private String _choreoName=null; private Protocol _protocol=null; private java.util.List<BPMNActivity> _bpmnActivityStack=new java.util.ArrayList<BPMNActivity>(); private java.util.Map<String,BPMNDiagram> _activityModels= new java.util.HashMap<String,BPMNDiagram>(); /** * The constructor the BPMN model visitor. * */ public BPMN2ModelVisitor(String choreoName, Protocol p, BPMN2ModelFactory model, BPMN2NotationFactory notation) { _choreoName = choreoName; _protocol = p; _modelFactory = model; _notationFactory = notation; } /** * This method starts visiting the behavior description element. * * @param elem The behavior description */ public boolean start(Protocol elem) { if (elem == _protocol) { try { BPMNDiagram diagram=getBPMNModel(elem); Choreography choreo=diagram.createChoreography(_choreoName); pushBPMNActivity(choreo); } catch(Exception e) { logger.severe("Failed to get state machine " + "for behavior '"+elem+"': "+e); } return (true); } return (false); } /** * This method ends visiting the behavior description element. * * @param elem The behavior description */ public void end(Protocol elem) { if (elem == _protocol) { BPMNActivity umls=getBPMNActivity(); if (umls != null) { umls.childrenComplete(); } popBPMNActivity(); } } /** * This method starts visiting the choice element. * * @param elem The choice */ public boolean start(Choice elem) { try { pushBPMNActivity(new ChoiceActivity(elem, getBPMNActivity(), _modelFactory, _notationFactory)); } catch(Exception e) { logger.severe("Failed to create choice state: "+e); } return(true); } /** * This method ends visiting the choice element. * * @param elem The choice */ public void end(Choice elem) { popBPMNActivity(); } /** * This method starts visiting the parallel element. * * @param elem The parallel */ public boolean start(Parallel elem) { try { pushBPMNActivity(new ParallelActivity(elem, getBPMNActivity(), _modelFactory, _notationFactory)); } catch(Exception e) { logger.severe("Failed to create parallel state: "+e); } return(true); } /** * This method ends visiting the parallel element. * * @param elem The parallel */ public void end(Parallel elem) { popBPMNActivity(); } /** * This method visits the perform activity. * * @param elem The perform */ public void accept(Run elem) { //AbstractBPMNActivity state=null; BPMNActivity umls=getBPMNActivity(); if (umls != null) { new RunActivity(elem, umls, _modelFactory, _notationFactory); } } /** * This method indicates the start of a * global escape. * * @param elem The global escape * @return Whether to process the contents */ public boolean start(Do elem) { AbstractBPMNActivity state=null; BPMNActivity umls=getBPMNActivity(); if (umls != null) { state = new DoActivity(elem, umls, _modelFactory, _notationFactory); pushBPMNActivity(state); DoBlockActivity inline=new DoBlockActivity(state, _modelFactory, _notationFactory); pushBPMNActivity(inline); } return(true); } /** * This method indicates the end of a * try escape. * * @param elem The global escape */ public void end(Do elem) { if (getBPMNActivity() instanceof DoBlockActivity) { popBPMNActivity(); } popBPMNActivity(); } /** * This method indicates the start of a * catch block. * * @param elem The catch block * @return Whether to process the contents */ public boolean start(Interrupt elem) { if (getBPMNActivity() instanceof DoBlockActivity) { popBPMNActivity(); } return(true); } /** * This method indicates the end of a * catch block. * * @param elem The catch block */ public void end(Interrupt elem) { //popBPMNActivity(); } /** * This method visits the receive activity. * * @param elem The receive */ public void accept(Interaction elem) { BPMNActivity umls=getBPMNActivity(); if (umls != null) { new ChoreographyTask(elem, umls, _modelFactory, _notationFactory); } } /** * This method starts visiting the sequence element. * * @param elem The sequence */ public boolean start(Block elem) { try { pushBPMNActivity(new SequenceActivity(getBPMNActivity(), _modelFactory, _notationFactory)); } catch(Exception e) { logger.severe("Failed to create sequence state: "+e); } return(true); } /** * This method ends visiting the sequence element. * * @param elem The sequence */ public void end(Block elem) { popBPMNActivity(); } /** * This method starts visiting the while element. * * @param elem The while */ public boolean start(Repeat elem) { try { pushBPMNActivity(new RepeatActivity(elem, getBPMNActivity(), _modelFactory, _notationFactory)); pushBPMNActivity(new SequenceActivity(getBPMNActivity(), _modelFactory, _notationFactory)); } catch(Exception e) { logger.severe("Failed to create while Activity: "+e); } return(true); } /** * This method ends visiting the while element. * * @param elem The while */ public void end(Repeat elem) { popBPMNActivity(); popBPMNActivity(); } /** * {@inheritDoc} */ public void accept(CustomActivity act) { if (act instanceof Fork) { BPMNActivity umls=getBPMNActivity(); if (umls != null) { new ForkActivity((Fork)act, umls, _modelFactory, _notationFactory); } } else if (act instanceof Join) { BPMNActivity umls=getBPMNActivity(); if (umls != null) { new JoinActivity((Join)act, umls, _modelFactory, _notationFactory); } } } protected BPMNDiagram getBPMNModel(Protocol elem) throws BPMN2GenerationException { String name=elem.getName(); BPMNDiagram ret=(BPMNDiagram) _activityModels.get(name); if (ret == null) { ret = new BPMNDiagram(_choreoName, name, null, _modelFactory, _notationFactory); _activityModels.put(name, ret); } return(ret); } /** * This method pushes the supplied UML activity * onto a stack. * * @param act The activity */ protected void pushBPMNActivity(BPMNActivity act) { _bpmnActivityStack.add(0, act); } /** * This method returns the UML activity found at the * top of the stack. * * @return The activity */ protected BPMNActivity getBPMNActivity() { BPMNActivity ret=null; if (_bpmnActivityStack.size() > 0) { ret = (BPMNActivity)_bpmnActivityStack.get(0); } return(ret); } /** * This method returns the UML activity from the * top of the stack. * */ protected void popBPMNActivity() { BPMNActivity umls=getBPMNActivity(); if (umls != null) { umls.childrenComplete(); } if (_bpmnActivityStack.size() > 0) { _bpmnActivityStack.remove(0); } } /** * This method completes the construction of the activity * models. * */ public void completeModels() { java.util.Iterator<BPMNDiagram> iter=_activityModels.values().iterator(); while (iter.hasNext()) { BPMNDiagram amodel=iter.next(); amodel.completeModel(); } } /** * This method establishes the associated operation details, * if not defined, and returns a reference to it. * * @param interaction The interaction * @return The operation reference */ protected QName getOperationReference(Interaction interaction, QName messageRef) { QName ret=null; // Check that interaction has an operation name if (interaction.getMessageSignature().getOperation() == null) { return(null); } // Find interface Role serverRole=null; if (InteractionUtil.isRequest(interaction)) { if (interaction.getToRoles().size() > 0) { serverRole = interaction.getToRoles().get(0); } if (serverRole == null) { serverRole = interaction.getEnclosingProtocol().getLocatedRole(); } } else { serverRole = interaction.getFromRole(); if (serverRole == null) { serverRole = interaction.getEnclosingProtocol().getLocatedRole(); } } if (serverRole == null) { // TODO: REPORT ERROR return (null); } TInterface intf=getInterface(_modelFactory.getDefinitions(), serverRole); if (intf == null) { // TODO: REPORT ERROR return (null); } // Find operation TOperation op=null; for (TOperation curop : intf.getOperation()) { if (interaction.getMessageSignature().getOperation() != null && curop.getName().equals(interaction.getMessageSignature().getOperation())) { op = curop; break; } } if (op == null) { op = new TOperation(); op.setName(interaction.getMessageSignature().getOperation()); op.setId("OP_"+serverRole.getName()+"_"+op.getName()); intf.getOperation().add(op); } ret = new QName(_modelFactory.getDefinitions().getTargetNamespace(), op.getId(), TARGET_NAMESPACE_PREFIX); if (InteractionUtil.isRequest(interaction)) { if (op.getInMessageRef() == null) { op.setInMessageRef(messageRef); } else if (!op.getInMessageRef().equals(messageRef)) { // TODO: Report mismatch } } else if (InteractionUtil.isFaultResponse(interaction)) { String faultName=InteractionUtil.getFaultName(interaction); TError error=null; // Check for Error definition with associated fault name and message ref for (JAXBElement<? extends TRootElement> rootElem : _modelFactory.getDefinitions().getRootElement()) { if (rootElem.getValue() instanceof TError) { TError cur=(TError)rootElem.getValue(); // Check if error has same fault name // TODO: may need to also check same item def?? if (cur.getErrorCode().equals(faultName)) { error = cur; break; } } } if (error != null) { QName qname=new QName(_modelFactory.getDefinitions().getTargetNamespace(), error.getId(), TARGET_NAMESPACE_PREFIX); if (!op.getErrorRef().contains(qname)) { op.getErrorRef().add(qname); } } else { // TODO: Report unable to find Error for fault name } } else { // Normal response if (op.getOutMessageRef() == null) { op.setOutMessageRef(messageRef); } else if (!op.getOutMessageRef().equals(messageRef)) { // TODO: Report mismatch } } return(ret); } } }