/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.di;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.bpmn2.Association;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.ConversationLink;
import org.eclipse.bpmn2.DataAssociation;
import org.eclipse.bpmn2.Event;
import org.eclipse.bpmn2.FlowNode;
import org.eclipse.bpmn2.InteractionNode;
import org.eclipse.bpmn2.ItemAwareElement;
import org.eclipse.bpmn2.Lane;
import org.eclipse.bpmn2.MessageFlow;
import org.eclipse.bpmn2.Participant;
import org.eclipse.bpmn2.Process;
import org.eclipse.bpmn2.SequenceFlow;
import org.eclipse.bpmn2.SubChoreography;
import org.eclipse.bpmn2.SubProcess;
import org.eclipse.bpmn2.di.BPMNDiagram;
import org.eclipse.bpmn2.di.BPMNEdge;
import org.eclipse.bpmn2.di.BPMNPlane;
import org.eclipse.bpmn2.di.BPMNShape;
import org.eclipse.bpmn2.di.BpmnDiFactory;
import org.eclipse.bpmn2.modeler.core.Activator;
import org.eclipse.bpmn2.modeler.core.ModelHandler;
import org.eclipse.bpmn2.modeler.core.features.BusinessObjectUtil;
import org.eclipse.bpmn2.modeler.core.utils.FeatureSupport;
import org.eclipse.bpmn2.modeler.core.utils.Tuple;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.bpmn2.modeler.core.validation.LiveValidationContentAdapter;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.dd.dc.Point;
import org.eclipse.dd.di.DiagramElement;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.graphiti.datatypes.ILocation;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.features.context.impl.AddContext;
import org.eclipse.graphiti.features.context.impl.AreaContext;
import org.eclipse.graphiti.mm.algorithms.Rectangle;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.mm.pictograms.impl.FreeFormConnectionImpl;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeService;
@SuppressWarnings("restriction")
public class DIImport {
public static final String IMPORT_PROPERTY = DIImport.class.getSimpleName().concat(".import");
private Diagram diagram;
private TransactionalEditingDomain domain;
private ModelHandler modelHandler;
private IFeatureProvider featureProvider;
private HashMap<BaseElement, PictogramElement> elements;
private final IPeService peService = Graphiti.getPeService();
private final IGaService gaService = Graphiti.getGaService();
private final LiveValidationContentAdapter liveValidationContentAdapter = new LiveValidationContentAdapter();
/**
* Look for model diagram interchange information and generate all shapes for the diagrams.
*
* NB! Currently only first found diagram is generated.
*/
public void generateFromDI() {
final List<BPMNDiagram> diagrams = modelHandler.getAll(BPMNDiagram.class);
elements = new HashMap<BaseElement, PictogramElement>();
domain.getCommandStack().execute(new RecordingCommand(domain) {
@Override
protected void doExecute() {
if (diagrams.size() == 0) {
BPMNPlane plane = BpmnDiFactory.eINSTANCE.createBPMNPlane();
plane.setBpmnElement(modelHandler.getOrCreateProcess(modelHandler.getInternalParticipant()));
BPMNDiagram d = BpmnDiFactory.eINSTANCE.createBPMNDiagram();
d.setPlane(plane);
modelHandler.getDefinitions().getDiagrams().add(d);
featureProvider.link(diagram, d);
}
// First: add all IDs to our ID mapping table
for (BPMNDiagram d : diagrams) {
TreeIterator<EObject> iter = d.eAllContents();
while (iter.hasNext()) {
ModelUtil.addID( iter.next() );
}
}
for (BPMNDiagram d : diagrams) {
featureProvider.link(diagram, d);
BPMNPlane plane = d.getPlane();
if (plane.getBpmnElement() == null) {
plane.setBpmnElement(modelHandler.getOrCreateProcess(modelHandler.getInternalParticipant()));
}
List<DiagramElement> ownedElement = plane.getPlaneElement();
// FIXME: here we should create a new diagram and an editor page
importShapes(ownedElement);
importConnections(ownedElement);
relayoutLanes(ownedElement);
// FIXME: we don't really want to leave, but we also don't want all diagrams mixed together
return;
}
}
});
}
public void setDiagram(Diagram diagram) {
this.diagram = diagram;
}
public void setDomain(TransactionalEditingDomain editingDomain) {
this.domain = editingDomain;
}
public void setModelHandler(ModelHandler modelHandler) {
this.modelHandler = modelHandler;
}
public void setFeatureProvider(IFeatureProvider featureProvider) {
this.featureProvider = featureProvider;
}
private void importShapes(List<DiagramElement> ownedElement) {
for (DiagramElement diagramElement : ownedElement) {
if (diagramElement instanceof BPMNShape) {
createShape((BPMNShape) diagramElement);
}
}
}
private void importConnections(List<DiagramElement> ownedElement) {
for (DiagramElement diagramElement : ownedElement) {
if (diagramElement instanceof BPMNEdge) {
createEdge((BPMNEdge) diagramElement);
}
}
}
private void relayoutLanes(List<DiagramElement> ownedElement) {
for (DiagramElement diagramElement : ownedElement) {
if (diagramElement instanceof BPMNShape && ((BPMNShape) diagramElement).getBpmnElement() instanceof Lane) {
BaseElement lane = ((BPMNShape) diagramElement).getBpmnElement();
ContainerShape shape = (ContainerShape) BusinessObjectUtil.getFirstBaseElementFromDiagram(diagram, lane);
FeatureSupport.redraw(shape);
}
}
}
/**
* Find a Graphiti feature for given shape and generate necessary diagram elements.
*
* @param shape
*/
private void createShape(BPMNShape shape) {
BaseElement bpmnElement = shape.getBpmnElement();
if (shape.getChoreographyActivityShape() != null) {
// FIXME: we currently generate participant bands automatically
return;
}
IAddFeature addFeature = featureProvider.getAddFeature(new AddContext(new AreaContext(), bpmnElement));
if (addFeature == null) {
Activator.logStatus(new Status(IStatus.WARNING, Activator.PLUGIN_ID, "Element not supported: "
+ bpmnElement.eClass().getName()));
return;
}
AddContext context = new AddContext();
context.putProperty(IMPORT_PROPERTY, true);
context.setNewObject(bpmnElement);
context.setSize((int) shape.getBounds().getWidth(), (int) shape.getBounds().getHeight());
if (bpmnElement instanceof Lane) {
handleLane(bpmnElement, context, shape);
} else if (bpmnElement instanceof FlowNode) {
handleFlowNode((FlowNode) bpmnElement, context, shape);
} else {
context.setTargetContainer(diagram);
context.setLocation((int) shape.getBounds().getX(), (int) shape.getBounds().getY());
}
if (addFeature.canAdd(context)) {
PictogramElement newContainer = addFeature.add(context);
featureProvider.link(newContainer, new Object[] { bpmnElement, shape });
if (bpmnElement instanceof Participant) {
elements.put(((Participant) bpmnElement).getProcessRef(), newContainer);
}
elements.put(bpmnElement, newContainer);
handleEvents(bpmnElement, newContainer);
}
ModelUtil.addID(bpmnElement);
String id = bpmnElement.getId();
if (shape.getId() == null)
shape.setId(id);
if (!bpmnElement.eAdapters().contains(liveValidationContentAdapter)) {
bpmnElement.eAdapters().add(liveValidationContentAdapter);
}
}
private void handleEvents(BaseElement bpmnElement, PictogramElement newContainer) {
if (bpmnElement instanceof Event) {
EList<EObject> contents = bpmnElement.eContents();
for (EObject obj : contents) {
AddContext context = new AddContext();
context.setTargetContainer((ContainerShape) newContainer);
context.setNewObject(obj);
IAddFeature aFeat = featureProvider.getAddFeature(context);
if (aFeat != null && aFeat.canAdd(context)) {
aFeat.add(context);
}
}
}
}
private void handleLane(BaseElement bpmnElement, AddContext context, BPMNShape shape) {
BaseElement parent = (BaseElement) ((Lane) bpmnElement).eContainer().eContainer();
ContainerShape cont = diagram;
// find the process this lane belongs to
for (BaseElement be : elements.keySet()) {
if (be instanceof Participant) {
Process processRef = ((Participant) be).getProcessRef();
if (processRef != null && parent.getId().equals(processRef.getId())) {
cont = (ContainerShape) elements.get(be);
break;
}
} else if (be instanceof Process) {
if (be.getId().equals(parent.getId())) {
cont = (ContainerShape) elements.get(be);
break;
}
} else if (be instanceof Lane) {
if (be.getId().equals(parent.getId())) {
cont = (ContainerShape) elements.get(be);
break;
}
}
}
context.setTargetContainer(cont);
context.setLocation((int) shape.getBounds().getX(), (int) shape.getBounds().getY());
}
private void handleFlowNode(FlowNode node, AddContext context, BPMNShape shape) {
ContainerShape target = diagram;
int x = (int) shape.getBounds().getX();
int y = (int) shape.getBounds().getY();
// find a correct container element
List<Lane> lanes = node.getLanes();
if ((node.eContainer() instanceof SubProcess || (node.eContainer() instanceof Process || node.eContainer() instanceof SubChoreography)
&& lanes.isEmpty())) {
ContainerShape containerShape = (ContainerShape) elements.get(node.eContainer());
if (containerShape != null) {
target = containerShape;
ILocation loc = Graphiti.getPeLayoutService().getLocationRelativeToDiagram(target);
x -= loc.getX();
y -= loc.getY();
}
} else if (!lanes.isEmpty()) {
for (Lane lane : lanes) {
target = (ContainerShape) elements.get(lane);
ILocation loc = Graphiti.getPeLayoutService().getLocationRelativeToDiagram(target);
x -= loc.getX();
y -= loc.getY();
}
}
context.setTargetContainer(target);
context.setLocation(x, y);
}
private BaseElement findElementById ( String id ) {
if (elements.keySet().size() > 0) {
Iterator<BaseElement> keys = elements.keySet().iterator();
while (keys.hasNext()) {
BaseElement key = keys.next();
if (key != null && key.getId().equals(id))
return key;
}
}
return null;
}
private Tuple<BaseElement, BaseElement> getSourceAndTargetByID ( String id ) {
BaseElement source = null;
BaseElement target = null;
int indexOfDash = id.indexOf('-');
if (indexOfDash > -1) {
String srcRefFromId = id.substring(0, indexOfDash);
String tgtRefFromId = id.substring(indexOfDash + 1, id.length());
source = findElementById(srcRefFromId);
target = findElementById(tgtRefFromId);
}
return new Tuple<BaseElement, BaseElement>(source, target);
}
/**
* Find a Graphiti feature for given edge and generate necessary connections and bendpoints.
*
* @param shape
*/
private void createEdge(BPMNEdge bpmnEdge) {
BaseElement bpmnElement = bpmnEdge.getBpmnElement();
EObject source = null;
EObject target = null;
PictogramElement se = null;
PictogramElement te = null;
String id = bpmnElement.getId();
// for some reason connectors don't have a common interface
if (bpmnElement instanceof MessageFlow) {
source = ((MessageFlow) bpmnElement).getSourceRef();
target = ((MessageFlow) bpmnElement).getTargetRef();
// for jbpm-3241, if we're importing from jbpm5, we won't have
// the sourceref & targetref set initially - have to get it from
// the id
if (source == null && target == null) {
Tuple<BaseElement, BaseElement> sourceAndTargetFromID =
getSourceAndTargetByID(id);
if (sourceAndTargetFromID.getFirst() != null) {
source = sourceAndTargetFromID.getFirst();
((MessageFlow) bpmnElement).setSourceRef((InteractionNode) source);
}
if (sourceAndTargetFromID.getSecond() != null) {
target = sourceAndTargetFromID.getSecond();
((MessageFlow) bpmnElement).setTargetRef((InteractionNode) target);
}
}
se = elements.get(source);
te = elements.get(target);
} else if (bpmnElement instanceof SequenceFlow) {
source = ((SequenceFlow) bpmnElement).getSourceRef();
target = ((SequenceFlow) bpmnElement).getTargetRef();
// for jbpm-3241, if we're importing from jbpm5, we won't have
// the sourceref & targetref set initially - have to get it from
// the id
if (source == null && target == null) {
Tuple<BaseElement, BaseElement> sourceAndTargetFromID =
getSourceAndTargetByID(id);
if (sourceAndTargetFromID.getFirst() != null) {
source = sourceAndTargetFromID.getFirst();
((SequenceFlow) bpmnElement).setSourceRef((FlowNode) source);
}
if (sourceAndTargetFromID.getSecond() != null) {
target = sourceAndTargetFromID.getSecond();
((SequenceFlow) bpmnElement).setTargetRef((FlowNode) target);
}
}
se = elements.get(source);
te = elements.get(target);
} else if (bpmnElement instanceof Association) {
source = ((Association) bpmnElement).getSourceRef();
target = ((Association) bpmnElement).getTargetRef();
// for jbpm-3241, if we're importing from jbpm5, we won't have
// the sourceref & targetref set initially - have to get it from
// the id
if (source == null && target == null) {
Tuple<BaseElement, BaseElement> sourceAndTargetFromID =
getSourceAndTargetByID(id);
if (sourceAndTargetFromID.getFirst() != null) {
source = sourceAndTargetFromID.getFirst();
((Association) bpmnElement).setSourceRef((FlowNode) source);
}
if (sourceAndTargetFromID.getSecond() != null) {
target = sourceAndTargetFromID.getSecond();
((Association) bpmnElement).setTargetRef((FlowNode) target);
}
}
se = elements.get(source);
te = elements.get(target);
} else if (bpmnElement instanceof ConversationLink) {
source = ((ConversationLink) bpmnElement).getSourceRef();
target = ((ConversationLink) bpmnElement).getTargetRef();
// for jbpm-3241, if we're importing from jbpm5, we won't have
// the sourceref & targetref set initially - have to get it from
// the id
if (source == null && target == null) {
Tuple<BaseElement, BaseElement> sourceAndTargetFromID =
getSourceAndTargetByID(id);
if (sourceAndTargetFromID.getFirst() != null) {
source = sourceAndTargetFromID.getFirst();
((ConversationLink) bpmnElement).setSourceRef((InteractionNode) source);
}
if (sourceAndTargetFromID.getSecond() != null) {
target = sourceAndTargetFromID.getSecond();
((ConversationLink) bpmnElement).setTargetRef((InteractionNode) target);
}
}
se = elements.get(source);
te = elements.get(target);
} else if (bpmnElement instanceof DataAssociation) {
// Data Association allows connections for multiple starting points, we don't support it yet
List<ItemAwareElement> sourceRef = ((DataAssociation) bpmnElement).getSourceRef();
ItemAwareElement targetRef = ((DataAssociation) bpmnElement).getTargetRef();
if (sourceRef != null) {
source = sourceRef.get(0);
}
target = targetRef;
do {
se = elements.get(source);
source = source.eContainer();
} while (se == null && source.eContainer() != null);
do {
te = elements.get(target);
target = target.eContainer();
} while (te == null && target.eContainer() != null);
}
ModelUtil.addID(bpmnElement);
if (source != null && target != null) {
addSourceAndTargetToEdge(bpmnEdge, source, target);
}
if (se != null && te != null) {
createConnectionAndSetBendpoints(bpmnEdge, se, te);
} else {
Activator.logStatus(new Status(IStatus.WARNING, Activator.PLUGIN_ID,
"Couldn't find target element, probably not supported! Source: " + source + " Target: " + target
+ " Element: " + bpmnElement));
}
}
private void addSourceAndTargetToEdge(BPMNEdge bpmnEdge, EObject source, EObject target) {
// We get most of the information from the BpmnEdge, not from the referencing business object. Because of this
// we must ensure, that the edge contains necessary information.
if (bpmnEdge.getSourceElement() == null) {
bpmnEdge.setSourceElement(modelHandler.findDIElement(diagram, (BaseElement) source));
}
if (bpmnEdge.getTargetElement() == null) {
bpmnEdge.setTargetElement(modelHandler.findDIElement(diagram, (BaseElement) target));
}
}
private Connection createConnectionAndSetBendpoints(BPMNEdge bpmnEdge, PictogramElement sourceElement,
PictogramElement targetElement) {
FixPointAnchor sourceAnchor = createAnchor(sourceElement);
FixPointAnchor targetAnchor = createAnchor(targetElement);
AddConnectionContext context = new AddConnectionContext(sourceAnchor, targetAnchor);
context.setNewObject(bpmnEdge.getBpmnElement());
IAddFeature addFeature = featureProvider.getAddFeature(context);
if (addFeature != null && addFeature.canAdd(context)) {
context.putProperty(IMPORT_PROPERTY, true);
Connection connection = (Connection) addFeature.add(context);
if (connection instanceof FreeFormConnectionImpl) {
FreeFormConnectionImpl freeForm = (FreeFormConnectionImpl) connection;
List<Point> waypoint = bpmnEdge.getWaypoint();
int size = waypoint.size() - 1;
setAnchorLocation(sourceElement, sourceAnchor, waypoint.get(0));
setAnchorLocation(targetElement, targetAnchor, waypoint.get(size));
for (int i = 1; i < size; i++) {
DIUtils.addBendPoint(freeForm, waypoint.get(i));
}
}
featureProvider.link(connection, new Object[] { bpmnEdge.getBpmnElement(), bpmnEdge });
return connection;
} else {
Activator.logStatus(new Status(IStatus.WARNING, Activator.PLUGIN_ID, "Unsupported feature "
+ ((EObject) context.getNewObject()).eClass().getName()));
}
return null;
}
private FixPointAnchor createAnchor(PictogramElement elem) {
FixPointAnchor sa = peService.createFixPointAnchor((AnchorContainer) elem);
sa.setReferencedGraphicsAlgorithm(elem.getGraphicsAlgorithm());
Rectangle rect = gaService.createInvisibleRectangle(sa);
gaService.setSize(rect, 1, 1);
return sa;
}
private void setAnchorLocation(PictogramElement elem, FixPointAnchor anchor, Point point) {
org.eclipse.graphiti.mm.algorithms.styles.Point p = gaService.createPoint((int) point.getX(),
(int) point.getY());
ILocation loc = Graphiti.getPeLayoutService().getLocationRelativeToDiagram((Shape) elem);
int x = p.getX() - loc.getX();
int y = p.getY() - loc.getY();
p.setX(x);
p.setY(y);
anchor.setLocation(p);
}
}