/*
* 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.util;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.savara.bpmn2.model.ObjectFactory;
import org.savara.bpmn2.model.TChoreography;
import org.savara.bpmn2.model.TChoreographyTask;
import org.savara.bpmn2.model.TDefinitions;
import org.savara.bpmn2.model.TError;
import org.savara.bpmn2.model.TFlowElement;
import org.savara.bpmn2.model.TFlowNode;
import org.savara.bpmn2.model.TInterface;
import org.savara.bpmn2.model.TMessage;
import org.savara.bpmn2.model.TMessageFlow;
import org.savara.bpmn2.model.TOperation;
import org.savara.bpmn2.model.TParticipant;
import org.savara.bpmn2.model.TRootElement;
import org.savara.bpmn2.model.TSequenceFlow;
import org.savara.bpmn2.model.TStartEvent;
/**
* This class provides capabilities associated with the service
* (or interface) declarations within BPMN model.
*
*/
public class BPMN2ServiceUtil {
public static final String INTERFACE_ID_SUFFIX = "Interface";
private static final Logger LOG=Logger.getLogger(BPMN2ServiceUtil.class.getName());
/**
* This method identifies the service interfaces (and their operations) associated
* with the participants defined within the supplied BPMN model. This method only
* creates a single interface per participant. The supplied BPMN model is not modified
* by this operation, the service interface details can be applied to the definition
* (after making any necessary changes) using the merge method.
*
* @param defns The BPMN model
* @return The map of participants to interfaces
*/
public static java.util.Map<TParticipant,TInterface> introspect(TDefinitions defns) {
java.util.Map<TParticipant,TInterface> ret=new java.util.HashMap<TParticipant,TInterface>();
for (JAXBElement<? extends TRootElement> elem : defns.getRootElement()) {
if (elem.getDeclaredType() == TChoreography.class) {
TChoreography choreo=(TChoreography)elem.getValue();
// Find initial node
TStartEvent startEvent=null;
for (JAXBElement<? extends TFlowElement> jaxb : choreo.getFlowElement()) {
if (jaxb.getValue().getClass() == TStartEvent.class) {
if (startEvent != null) {
LOG.severe("Multiple start events found in choreography");
} else {
startEvent = (TStartEvent)jaxb.getValue();
}
}
}
if (startEvent != null) {
processNode(defns, startEvent, ret, new java.util.Vector<InteractionInfo>(),
new ModelInfo(choreo.getParticipant(),
choreo.getMessageFlow(), choreo.getFlowElement(),
defns.getRootElement(), defns.getTargetNamespace()),
new java.util.Vector<TFlowNode>());
}
}
}
return (ret);
}
protected static void processNode(TDefinitions defns, TFlowNode node, java.util.Map<TParticipant,TInterface> intfs,
java.util.List<InteractionInfo> ii, ModelInfo modelInfo,
java.util.List<TFlowNode> processedNodes) {
if (!processedNodes.contains(node)) {
processedNodes.add(node);
// Check if node is an interaction
if (node instanceof TChoreographyTask) {
processChoreographyTask(defns, (TChoreographyTask)node, intfs, ii, modelInfo);
}
for (QName outgoing : node.getOutgoing()) {
TSequenceFlow sf=(TSequenceFlow)modelInfo.getFlowElement(outgoing.getLocalPart());
if (sf != null) {
if (sf.getTargetRef() != null) {
processNode(defns, (TFlowNode)sf.getTargetRef(), intfs,
new java.util.Vector<InteractionInfo>(ii), modelInfo, processedNodes);
} else if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node's outgoing flow has no target ref: "+node);
}
}
}
}
}
protected static void processChoreographyTask(TDefinitions defns, TChoreographyTask node,
java.util.Map<TParticipant,TInterface> intfs,
java.util.List<InteractionInfo> ii, ModelInfo modelInfo) {
InteractionInfo initiatingii=null;
InteractionInfo respondingii=null;
for (QName mflowQName : node.getMessageFlowRef()) {
TMessageFlow mflow=modelInfo.getMessageFlow(mflowQName.getLocalPart());
if (mflow != null) {
TParticipant from=modelInfo.getParticipant(mflow.getSourceRef().getLocalPart());
TParticipant to=modelInfo.getParticipant(mflow.getTargetRef().getLocalPart());
TMessage mesg=modelInfo.getMessage(mflow.getMessageRef().getLocalPart());
if (from != null && to != null && mesg != null) {
if (mflow.getSourceRef().equals(node.getInitiatingParticipantRef())) {
initiatingii = new InteractionInfo(from, to, mesg);
} else {
respondingii = new InteractionInfo(to, from, mesg);
}
}
}
}
// Check if both initiating and responding interactions set
if (initiatingii != null && respondingii != null) {
// Assume matched pair
storeMEP(defns, initiatingii, respondingii, intfs, modelInfo);
} else if (initiatingii != null) {
// Attempt to match with previous interaction
InteractionInfo match=null;
for (int i=ii.size()-1; match == null && i >= 0; i--) {
InteractionInfo cur=ii.get(i);
if (cur.getFrom().equals(initiatingii.getTo()) &&
cur.getTo().equals(initiatingii.getFrom())) {
match = cur;
}
}
if (match != null) {
storeMEP(defns, match, initiatingii, intfs, modelInfo);
ii.remove(match);
} else {
for (int i=ii.size()-1; match == null && i >= 0; i--) {
InteractionInfo cur=ii.get(i);
if (cur.getFrom().equals(initiatingii.getTo()) &&
cur.getTo().equals(initiatingii.getFrom())) {
match = cur;
}
}
// If previous interaction found with same from/to, then
// record previous as one-way op
if (match != null) {
storeMEP(defns, match, null, intfs, modelInfo);
ii.remove(match);
}
// Record new interaction
ii.add(initiatingii);
}
} else if (respondingii != null) {
// Error, can't have just a response??
LOG.severe("Only a response interaction has been found for ChoreographyTask: "+node);
} else {
// Error no interactions found on choreography task
LOG.severe("No interactions have been found for ChoreographyTask: "+node);
}
}
protected static void storeMEP(TDefinitions defns, InteractionInfo req, InteractionInfo resp,
java.util.Map<TParticipant,TInterface> intfs, ModelInfo modelInfo) {
TParticipant participant=req.getTo();
TInterface intf=intfs.get(participant);
// Check if interface has been defined - if not, then create
if (intf == null) {
String intfName=getInterfaceName(participant);
intf = new TInterface();
intf.setId(intfName+INTERFACE_ID_SUFFIX);
intf.setName(intfName);
String ns=modelInfo.getTargetNamespace()+"/"+participant.getName();
String prefix=findNamespacePrefix(defns, ns);
QName impl=new QName(ns, participant.getName(), prefix);
intf.setImplementationRef(impl);
intfs.put(participant, intf);
}
// Find operation with request message type
TOperation operation=null;
for (TOperation op : intf.getOperation()) {
if (op.getInMessageRef().getLocalPart().equals(req.getMessage().getId())) {
operation = op;
break;
}
}
if (operation == null) {
operation = new TOperation();
String opname=getOperationName(req.getMessage());
operation.setId(opname);
operation.setName(opname);
operation.setInMessageRef(new QName(modelInfo.getTargetNamespace(), req.getMessage().getId(), "tns"));
intf.getOperation().add(operation);
}
if (resp != null) {
// Check if Error exists for same ItemDefinition as the response Message
TError err=modelInfo.getErrorForItemDefinition(resp.getMessage().getItemRef());
if (err != null) {
QName errQName=new QName(modelInfo.getTargetNamespace(), err.getId(), "tns");
if (!operation.getErrorRef().contains(errQName)) {
operation.getErrorRef().add(errQName);
}
} else {
// Could be normal response, or fault without an error object
// Check if response message already exists on operation
if (operation.getOutMessageRef() == null) {
operation.setOutMessageRef(new QName(modelInfo.getTargetNamespace(),
resp.getMessage().getId(), "tns"));
/* NOTE: Currently don't handle adding error objects, as this would
* modify the model. User has to ensure fault message types have an appropriate
* Error object.
*
} else if (!operation.getOutMessageRef().getLocalPart().equals(resp.getMessage().getId())){
boolean found=false;
for (int i=0; !found && i < operation.getErrorRef().size(); i++) {
found = operation.getErrorRef().get(i).getLocalPart().equals(
resp.getMessage().getId());
}
if (!found) {
err = new TError();
err.setErrorCode(resp.getMessage().getName());
err.setId(resp.getMessage().getName()+"Err");
err.setStructureRef(resp.getMessage().getItemRef());
modelInfo.addError(err);
operation.getErrorRef().add(new QName(modelInfo.getTargetNamespace(), err.getId()));
}
*/
}
}
}
}
protected static String findNamespacePrefix(TDefinitions defns, String namespace) {
String ret=null;
for (QName qname : defns.getOtherAttributes().keySet()) {
String value=defns.getOtherAttributes().get(qname);
if (value.equals(namespace) && qname.getLocalPart().startsWith("xmlns:")) {
ret = qname.getLocalPart().substring(6);
}
}
if (ret == null) {
int i=1;
while (ret == null) {
ret = "intf"+(i++);
QName qname=new QName(null, "xmlns:"+ret);
if (defns.getOtherAttributes().containsKey(qname)) {
ret = null;
}
}
// Add namespace prefix declaration
defns.getOtherAttributes().put(new QName(null, "xmlns:"+ret), namespace);
}
return (ret);
}
/**
* This method returns the default interface name associated
* with the supplied participant.
*
* @param participant The participant
* @return The interface name
*/
public static String getInterfaceName(TParticipant participant) {
return(participant.getName());
}
/**
* This method derives an operation name from the supplied message.
*
* @param message The message
* @return The operation name
*/
public static String getOperationName(TMessage message) {
String ret=Character.toLowerCase(message.getName().charAt(0))+
message.getName().substring(1);
ret = ret.replaceAll("Request", "");
ret = ret.replaceAll("Response", "");
ret = ret.replaceAll("Req", "");
ret = ret.replaceAll("Resp", "");
return(ret);
}
/**
* This method merges the supplied service interface information into the supplied
* BPMN model. If no interfaces exist in the model, then the supplied interfaces will be
* added. If interfaces already exist, then it will attempt to merge the operations
* into the existing one or more interfaces defined per participant.
*
* @param defns The BPMN model
* @param interfaces The map of participants to interfaces
*/
public static void merge(TDefinitions defns, java.util.Map<TParticipant,TInterface> interfaces) {
ObjectFactory factory=new ObjectFactory();
java.util.List<TParticipant> participants=
new java.util.Vector<TParticipant>(interfaces.keySet());
Collections.sort(participants, new Comparator<TParticipant>() {
public int compare(TParticipant o1, TParticipant o2) {
return(o1.getName().compareTo(o2.getName()));
}
});
ModelInfo modelInfo=new ModelInfo(null, null, null, defns.getRootElement(),
defns.getTargetNamespace());
for (TParticipant participant : participants) {
TInterface intf=interfaces.get(participant);
// Check whether participant already has an interface
if (participant.getInterfaceRef().size() == 0) {
// Add interface to model and reference it from participant
defns.getRootElement().add(factory.createInterface(intf));
participant.getInterfaceRef().add(
new QName(modelInfo.getTargetNamespace(), intf.getId(), "tns"));
} else {
for (TOperation op : intf.getOperation()) {
// Find operation
TOperation other=null;
for (QName qname : participant.getInterfaceRef()) {
TInterface otherintf=(TInterface)
modelInfo.getRootElement(qname.getLocalPart());
if (otherintf != null) {
for (TOperation otherOp : otherintf.getOperation()) {
if (op.getName().equals(otherOp.getName())) {
other = otherOp;
break;
}
}
if (other != null) {
break;
}
}
}
if (other == null) {
// Install operation on first interface
TInterface otherintf=(TInterface)
modelInfo.getRootElement(participant.getInterfaceRef().
get(0).getLocalPart());
otherintf.getOperation().add(op);
} else {
// Merge operations
mergeOperation(op, other);
}
}
}
}
}
protected static void mergeOperation(TOperation newOp, TOperation existingOp) {
// If request message already set, then confirm that the request message
// types are the same - otherwise copy
boolean mergeResponse=false;
if (existingOp.getInMessageRef() == null) {
existingOp.setInMessageRef(newOp.getInMessageRef());
mergeResponse = true;
} else if (newOp.getInMessageRef() != null) {
if (newOp.getInMessageRef().equals(existingOp.getInMessageRef())) {
mergeResponse = true;
} else {
LOG.severe("Incompatible request message type for operation '"+
existingOp.getName()+"'");
}
} else {
mergeResponse = true;
}
if (mergeResponse) {
// Check response
if (existingOp.getOutMessageRef() == null) {
existingOp.setOutMessageRef(newOp.getOutMessageRef());
} else if (newOp.getOutMessageRef() != null &&
!newOp.getOutMessageRef().equals(existingOp.getOutMessageRef())) {
LOG.severe("Incompatible response message type for operation '"+
existingOp.getName()+"'");
}
// Check error messages
for (QName errQName : newOp.getErrorRef()) {
if (!existingOp.getErrorRef().contains(errQName)) {
existingOp.getErrorRef().add(errQName);
}
}
}
}
/**
* This class is a wrapper for the pieces of information that
* constitute an interaction.
*
*/
protected static class InteractionInfo {
private TParticipant _from=null;
private TParticipant _to=null;
private TMessage _message=null;
public InteractionInfo(TParticipant from, TParticipant to, TMessage message) {
_from = from;
_to = to;
_message = message;
}
public TParticipant getFrom() {
return (_from);
}
public TParticipant getTo() {
return (_to);
}
public TMessage getMessage() {
return (_message);
}
}
protected static class ModelInfo {
private List<TParticipant> _participants=null;
private List<TMessageFlow> _messageFlows=null;
private List<JAXBElement<? extends TFlowElement>> _flowElements;
private List<JAXBElement<? extends TRootElement>> _rootElements;
private String _targetNamespace=null;
private ObjectFactory _factory=new ObjectFactory();
public ModelInfo(List<TParticipant> participants, List<TMessageFlow> messageFlows,
List<JAXBElement<? extends TFlowElement>> flowElements,
List<JAXBElement<? extends TRootElement>> rootElements,
String targetNamespace) {
_participants = participants;
_messageFlows = messageFlows;
_flowElements = flowElements;
_rootElements = rootElements;
_targetNamespace = targetNamespace;
}
public String getTargetNamespace() {
return (_targetNamespace);
}
public void addError(TError err) {
_rootElements.add(_factory.createError(err));
}
public TError getErrorForItemDefinition(QName itemRef) {
TError ret=null;
for (JAXBElement<? extends TRootElement> fejaxb : _rootElements) {
TRootElement fe=fejaxb.getValue();
if (fe instanceof TError && ((TError)fe).getStructureRef().equals(itemRef)) {
ret = (TError)fe;
break;
}
}
return(ret);
}
public TParticipant getParticipant(String id) {
TParticipant ret=null;
for (TParticipant part : _participants) {
if (part.getId().equals(id)) {
ret = part;
break;
}
}
return (ret);
}
public TMessageFlow getMessageFlow(String id) {
TMessageFlow ret=null;
for (TMessageFlow mflow : _messageFlows) {
if (mflow.getId().equals(id)) {
ret = mflow;
break;
}
}
return (ret);
}
public TMessage getMessage(String id) {
return((TMessage)getRootElement(id));
}
public TRootElement getRootElement(String id) {
TRootElement ret=null;
for (JAXBElement<? extends TRootElement> fejaxb : _rootElements) {
TRootElement fe=fejaxb.getValue();
if (fe.getId().equals(id)) {
ret = fe;
break;
}
}
return (ret);
}
public TFlowElement getFlowElement(String id) {
TFlowElement ret=null;
for (JAXBElement<? extends TFlowElement> fejaxb : _flowElements) {
TFlowElement fe=fejaxb.getValue();
if (fe.getId().equals(id)) {
ret = fe;
break;
}
}
return (ret);
}
public TError getError(String id) {
return((TError)getRootElement(id));
}
}
}