/******************************************************************************* * Copyright (c) 2011, 2012 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 * * @author Innar Made ******************************************************************************/ package org.eclipse.bpmn2.modeler.ui.features.flow; import java.util.List; import org.eclipse.bpmn2.BaseElement; import org.eclipse.bpmn2.Bpmn2Package; import org.eclipse.bpmn2.EndEvent; import org.eclipse.bpmn2.EventDefinition; import org.eclipse.bpmn2.InteractionNode; import org.eclipse.bpmn2.Message; import org.eclipse.bpmn2.MessageEventDefinition; import org.eclipse.bpmn2.MessageFlow; import org.eclipse.bpmn2.Operation; import org.eclipse.bpmn2.Participant; import org.eclipse.bpmn2.ReceiveTask; import org.eclipse.bpmn2.SendTask; import org.eclipse.bpmn2.ServiceTask; import org.eclipse.bpmn2.StartEvent; import org.eclipse.bpmn2.SubProcess; import org.eclipse.bpmn2.modeler.core.adapters.ExtendedPropertiesAdapter; import org.eclipse.bpmn2.modeler.core.di.DIImport; import org.eclipse.bpmn2.modeler.core.features.AbstractBpmn2UpdateFeature; import org.eclipse.bpmn2.modeler.core.features.BaseElementConnectionFeatureContainer; import org.eclipse.bpmn2.modeler.core.features.DefaultDeleteBPMNShapeFeature; import org.eclipse.bpmn2.modeler.core.features.DefaultLayoutBPMNConnectionFeature; import org.eclipse.bpmn2.modeler.core.features.MultiUpdateFeature; import org.eclipse.bpmn2.modeler.core.features.choreography.ChoreographyUtil; import org.eclipse.bpmn2.modeler.core.features.flow.AbstractAddFlowFeature; import org.eclipse.bpmn2.modeler.core.features.flow.AbstractCreateFlowFeature; import org.eclipse.bpmn2.modeler.core.features.flow.AbstractReconnectFlowFeature; import org.eclipse.bpmn2.modeler.core.features.label.UpdateLabelFeature; import org.eclipse.bpmn2.modeler.core.model.ModelHandler; import org.eclipse.bpmn2.modeler.core.utils.BusinessObjectUtil; import org.eclipse.bpmn2.modeler.core.utils.FeatureSupport; import org.eclipse.bpmn2.modeler.core.utils.ModelUtil; import org.eclipse.bpmn2.modeler.core.utils.StyleUtil; import org.eclipse.bpmn2.modeler.ui.ImageProvider; import org.eclipse.bpmn2.modeler.ui.features.data.MessageFeatureContainer; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.graphiti.datatypes.ILocation; import org.eclipse.graphiti.features.IAddFeature; import org.eclipse.graphiti.features.ICreateConnectionFeature; import org.eclipse.graphiti.features.IDeleteFeature; import org.eclipse.graphiti.features.IFeatureProvider; import org.eclipse.graphiti.features.ILayoutFeature; import org.eclipse.graphiti.features.IMoveShapeFeature; import org.eclipse.graphiti.features.IReason; import org.eclipse.graphiti.features.IReconnectionFeature; import org.eclipse.graphiti.features.IUpdateFeature; import org.eclipse.graphiti.features.context.IAddConnectionContext; import org.eclipse.graphiti.features.context.IAddContext; import org.eclipse.graphiti.features.context.IContext; import org.eclipse.graphiti.features.context.ICreateConnectionContext; import org.eclipse.graphiti.features.context.IDeleteContext; import org.eclipse.graphiti.features.context.ILayoutContext; import org.eclipse.graphiti.features.context.IPictogramElementContext; import org.eclipse.graphiti.features.context.IUpdateContext; import org.eclipse.graphiti.features.context.impl.AddContext; import org.eclipse.graphiti.features.context.impl.AreaContext; import org.eclipse.graphiti.features.context.impl.DeleteContext; import org.eclipse.graphiti.features.context.impl.MoveShapeContext; import org.eclipse.graphiti.features.impl.Reason; import org.eclipse.graphiti.mm.algorithms.Ellipse; import org.eclipse.graphiti.mm.algorithms.Polyline; import org.eclipse.graphiti.mm.algorithms.styles.LineStyle; import org.eclipse.graphiti.mm.pictograms.Connection; import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator; import org.eclipse.graphiti.mm.pictograms.ContainerShape; import org.eclipse.graphiti.mm.pictograms.Diagram; import org.eclipse.graphiti.mm.pictograms.PictogramElement; import org.eclipse.graphiti.mm.pictograms.Shape; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.services.IGaService; import org.eclipse.graphiti.services.IPeService; import org.eclipse.graphiti.util.IColorConstant; public class MessageFlowFeatureContainer extends BaseElementConnectionFeatureContainer { public final static String MESSAGE_REF = "message.ref"; //$NON-NLS-1$ final static IPeService peService = Graphiti.getPeService(); final static IGaService gaService = Graphiti.getGaService(); public Object getApplyObject(IContext context) { Object object = super.getApplyObject(context); if (context instanceof IPictogramElementContext) { PictogramElement pe = ((IPictogramElementContext) context).getPictogramElement(); if (ChoreographyUtil.isChoreographyMessageLink(pe)) return null; MessageFlow mf = getMessageFlow(pe); if (mf!=null) return mf; } return object; } @Override public boolean canApplyTo(Object o) { return super.canApplyTo(o) && o instanceof MessageFlow; } @Override public IAddFeature getAddFeature(IFeatureProvider fp) { return new AddMessageFlowFeature(fp); } @Override public ICreateConnectionFeature getCreateConnectionFeature(IFeatureProvider fp) { return new CreateMessageFlowFeature(fp); } @Override public IDeleteFeature getDeleteFeature(final IFeatureProvider fp) { return new DeleteMessageFromMessageFlowFeature(fp); } @Override public IUpdateFeature getUpdateFeature(IFeatureProvider fp) { MultiUpdateFeature multiUpdate = new MultiUpdateFeature(fp); multiUpdate.addFeature(new UpdateMessageFlowFeature(fp)); multiUpdate.addFeature(new UpdateLabelFeature(fp)); return multiUpdate; } @Override public ILayoutFeature getLayoutFeature(IFeatureProvider fp) { return new DefaultLayoutBPMNConnectionFeature(fp) { @Override public boolean layout(ILayoutContext context) { if (super.layout(context)) { // PictogramElement pe = context.getPictogramElement(); // if (pe instanceof Connection) // adjustMessageDecorator(getFeatureProvider(), (Connection)pe); return true; } return false; } }; } @Override public IReconnectionFeature getReconnectionFeature(IFeatureProvider fp) { return new ReconnectMessageFlowFeature(fp); } public static ContainerShape findMessageShape(Connection connection) { ConnectionDecorator d = findMessageDecorator(connection); if (d!=null) { return BusinessObjectUtil.getFirstElementOfType(d, ContainerShape.class); } return null; } public static MessageFlow getMessageFlow(PictogramElement pe) { if (pe instanceof ContainerShape) { String id = FeatureSupport.getPropertyValue(pe, MESSAGE_REF); if (id!=null && !id.isEmpty()) { EObject o = pe.eContainer(); while (!(o instanceof Diagram)) { o = o.eContainer(); } if (o instanceof Diagram) { Diagram diagram = (Diagram) o; for (Connection connection : diagram.getConnections()) { MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(connection, MessageFlow.class); if (messageFlow!=null) { if (id.equals(messageFlow.getId())) return messageFlow; } } } } } else if (pe instanceof Connection) { MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(pe, MessageFlow.class); return messageFlow; } else if (pe instanceof ConnectionDecorator) { pe = ((ConnectionDecorator)pe).getConnection(); MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(pe, MessageFlow.class); return messageFlow; } return null; } protected static String messageToString(Message message) { if (message==null) return ""; //$NON-NLS-1$ return message.getId(); } protected static Connection getMessageFlowConnection(PictogramElement pe) { if (pe instanceof ContainerShape) { String id = FeatureSupport.getPropertyValue(pe, MESSAGE_REF); if (id!=null && !id.isEmpty()) { EObject o = pe.eContainer(); while (!(o instanceof Diagram)) { o = o.eContainer(); } if (o instanceof Diagram) { Diagram diagram = (Diagram) o; for (Connection connection : diagram.getConnections()) { MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(connection, MessageFlow.class); if (messageFlow!=null) { if (id.equals(messageFlow.getId())) return connection; } } } } } else if (pe instanceof Connection) { MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(pe, MessageFlow.class); if (messageFlow!=null) return (Connection)pe; } else if (pe instanceof ConnectionDecorator) { pe = ((ConnectionDecorator)pe).getConnection(); MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(pe, MessageFlow.class); if (messageFlow!=null) return (Connection)pe; } return null; } protected static ConnectionDecorator findMessageDecorator(Connection connection) { for (ConnectionDecorator d : connection.getConnectionDecorators()) { if (FeatureSupport.getPropertyValue(d, MESSAGE_REF) != null) { return d; } } return null; } protected static boolean messageDecoratorMoved(Connection connection) { ContainerShape messageShape = findMessageShape(connection); if (messageShape!=null) { ILocation loc = peService.getConnectionMidpoint(connection, 0.25); int w = MessageFeatureContainer.ENVELOPE_WIDTH / 2; int h = MessageFeatureContainer.ENVELOPE_HEIGHT / 2; int x = loc.getX() - w; int y = loc.getY() - h; ILocation shapeLoc = peService.getLocationRelativeToDiagram(messageShape); return x != shapeLoc.getX() || y != shapeLoc.getY(); } return false; } protected static void adjustMessageDecorator(IFeatureProvider fp, Connection connection) { ContainerShape messageShape = findMessageShape(connection); if (messageShape!=null) { // calculate new location: this will be 1/4 of the distance from start of the connection line ILocation loc = peService.getConnectionMidpoint(connection, 0.25); int w = MessageFeatureContainer.ENVELOPE_WIDTH / 2; int h = MessageFeatureContainer.ENVELOPE_HEIGHT / 2; int x = loc.getX() - w; int y = loc.getY() - h; MoveShapeContext moveContext = new MoveShapeContext(messageShape); moveContext.setX(x); moveContext.setY(y); IMoveShapeFeature moveFeature = fp.getMoveShapeFeature(moveContext); moveFeature.moveShape(moveContext); } } protected static void addMessageDecorator(IFeatureProvider fp, Connection connection, Message message, Shape messageShape) { ILocation loc = peService.getConnectionMidpoint(connection, 0.25); Diagram diagram = peService.getDiagramForPictogramElement(connection); ConnectionDecorator decorator = peService.createConnectionDecorator(connection, true, 0.25, true); MessageFlow messageFlow = BusinessObjectUtil.getFirstElementOfType(connection, MessageFlow.class); int w = MessageFeatureContainer.ENVELOPE_WIDTH / 2; int h = MessageFeatureContainer.ENVELOPE_HEIGHT / 2; int x = loc.getX() - w; int y = loc.getY() - h; if (messageShape==null) { AddContext addContext = new AddContext(new AreaContext(), message); addContext.putProperty(MessageFeatureContainer.IS_REFERENCE, Boolean.TRUE); addContext.setX(x); addContext.setY(y); addContext.setTargetContainer(diagram); // adds the Label to the Message: messageShape = (Shape) fp.addIfPossible(addContext); // use this instead if Message labels are not desired (possible User Preference?) // IAddFeature addFeature = fp.getAddFeature(addContext); // messageShape = (Shape) addFeature.add(addContext); } else { MoveShapeContext moveContext = new MoveShapeContext(messageShape); moveContext.setLocation(x, y); moveContext.setSourceContainer(messageShape.getContainer()); moveContext.setTargetContainer(messageShape.getContainer()); IMoveShapeFeature moveFeature = fp.getMoveShapeFeature(moveContext); moveFeature.moveShape(moveContext); } fp.link(decorator, new Object[] {message, messageShape}); FeatureSupport.setPropertyValue(decorator, MESSAGE_REF, "true"); //$NON-NLS-1$ // Set our MessageFlow ID in the Message shape. Sadly Graphiti shape properties // can only hold Strings, so if the MessageFlow ID is null, we need to assign // a new one to it. String id = messageFlow.getId(); if (id==null || id.isEmpty()) id = ModelUtil.setID(messageFlow); FeatureSupport.setPropertyValue(messageShape, MESSAGE_REF, id); messageFlow.setMessageRef(message); } protected static void removeMessageDecorator(IFeatureProvider fp, Connection connection) { ConnectionDecorator decorator = findMessageDecorator(connection); if (decorator!=null) { ContainerShape messageShape = findMessageShape(connection); if (messageShape!=null) { peService.removeProperty(messageShape, MESSAGE_REF); DeleteContext deleteContext = new DeleteContext(messageShape); IDeleteFeature deleteFeature = fp.getDeleteFeature(deleteContext); deleteFeature.delete(deleteContext); } peService.deletePictogramElement(decorator); MessageFlow mf = BusinessObjectUtil.getFirstElementOfType(connection, MessageFlow.class); mf.setMessageRef(null); } } public static class AddMessageFlowFeature extends AbstractAddFlowFeature<MessageFlow> { public AddMessageFlowFeature(IFeatureProvider fp) { super(fp); } @Override public boolean canAdd(IAddContext context) { if (context instanceof IAddConnectionContext) { IAddConnectionContext acc = (IAddConnectionContext) context; if (acc.getSourceAnchor() != null) { PictogramElement pe = acc.getSourceAnchor().getParent(); Object source = BusinessObjectUtil.getFirstElementOfType(pe, BaseElement.class); if (source instanceof EndEvent) { // End Events can only be the source of Message Flow connections // if the End Event has a Message Event Definition List<EventDefinition> eventDefinitions = ((EndEvent) source).getEventDefinitions(); for (EventDefinition eventDefinition : eventDefinitions) { if (eventDefinition instanceof MessageEventDefinition) { return true; } } return false; } if (source instanceof StartEvent) { return false; } } } return super.canAdd(context); } @Override public PictogramElement add(IAddContext context) { Connection connection = (Connection)super.add(context); if (!DIImport.isImporting(context)) { // take a SWAG at the Message type by looking at the source and target figures: // if the source is a SendTask, use the Message from the SendTask // if the target is a ReceiveTask, use the Message from that // if the source or target is a ServiceTask use the input or output Message from its Operation BaseElement source = BusinessObjectUtil.getFirstBaseElement(connection.getStart().getParent()); BaseElement target = BusinessObjectUtil.getFirstBaseElement(connection.getEnd().getParent()); Message message = null; if (source instanceof SendTask) { message = ((SendTask)source).getMessageRef(); } else if (target instanceof ReceiveTask) { message = ((ReceiveTask)target).getMessageRef(); } else if (source instanceof ServiceTask) { Operation operation = ((ServiceTask)source).getOperationRef(); if (operation!=null) message = operation.getInMessageRef(); } else if (target instanceof ServiceTask) { Operation operation = ((ServiceTask)target).getOperationRef(); if (operation!=null) message = operation.getOutMessageRef(); } if (message!=null) { // Set the Message type. The ExtendedPropertiesAdapter will // handle the setting of Message types in the source and target // nodes as required. MessageFlow mf = BusinessObjectUtil.getFirstElementOfType(connection, MessageFlow.class); ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(mf); adapter.getFeatureDescriptor(Bpmn2Package.eINSTANCE.getMessageFlow_MessageRef()).setValue(message); } } return connection; } @Override protected Polyline createConnectionLine(final Connection connection) { MessageFlow messageFlow = (MessageFlow) BusinessObjectUtil.getFirstBaseElement(connection); Polyline connectionLine = super.createConnectionLine(connection); connectionLine.setLineStyle(LineStyle.DASH); connectionLine.setLineWidth(2); ConnectionDecorator endDecorator = peService.createConnectionDecorator(connection, false, 1.0, true); ConnectionDecorator startDecorator = peService.createConnectionDecorator(connection, false, 0, true); int w = 5; int l = 10; Polyline arrowhead = gaService.createPolygon(endDecorator, new int[] { -l, w, 0, 0, -l, -w, -l, w }); StyleUtil.applyStyle(arrowhead, messageFlow); arrowhead.setBackground(manageColor(IColorConstant.WHITE)); Ellipse circle = gaService.createEllipse(startDecorator); gaService.setSize(circle, 10, 10); StyleUtil.applyStyle(circle, messageFlow); circle.setBackground(manageColor(IColorConstant.WHITE)); return connectionLine; } /* (non-Javadoc) * @see org.eclipse.bpmn2.modeler.core.features.AbstractBpmn2AddFeature#getBusinessObjectType() */ @Override public Class getBusinessObjectType() { return MessageFlow.class; } } public static class CreateMessageFlowFeature extends AbstractCreateFlowFeature<MessageFlow, InteractionNode, InteractionNode> { public CreateMessageFlowFeature(IFeatureProvider fp) { super(fp); } @Override public boolean isAvailable(IContext context) { if (context instanceof ICreateConnectionContext) { ICreateConnectionContext ccc = (ICreateConnectionContext) context; if (ccc.getSourcePictogramElement() != null) { InteractionNode source = getSourceBo(ccc); if (source instanceof SubProcess) { SubProcess subProcess = (SubProcess) source; if (subProcess.isTriggeredByEvent()) return false; } else if (source instanceof EndEvent) { List<EventDefinition> eventDefinitions = ((EndEvent) source) .getEventDefinitions(); for (EventDefinition eventDefinition : eventDefinitions) { if (eventDefinition instanceof MessageEventDefinition) { return true; } } } else if (source instanceof StartEvent){ return false; } } } return super.isAvailable(context); } @Override public boolean canStartConnection(ICreateConnectionContext context) { if (!super.canStartConnection(context)) return false; if (ChoreographyUtil.isChoreographyParticipantBand(context.getSourcePictogramElement())) return false; return true; } @Override public boolean canCreate(ICreateConnectionContext context) { PictogramElement sourcePE = context.getSourcePictogramElement(); PictogramElement targetPE = context.getTargetPictogramElement(); if (ChoreographyUtil.isChoreographyParticipantBand(sourcePE)) return false; if (targetPE!=null) { if (ChoreographyUtil.isChoreographyParticipantBand(targetPE)) return false; } // The source and target of a Message Flow can't be the same if (sourcePE == targetPE) return false; InteractionNode source = getSourceBo(context); // Special case for End Event: only allow Message Flows if the End Event // has a Message Event Definition. if (source instanceof EndEvent) { boolean allow = false; List<EventDefinition> eventDefinitions = ((EndEvent) source).getEventDefinitions(); for (EventDefinition eventDefinition : eventDefinitions) { if (eventDefinition instanceof MessageEventDefinition) { allow = true; break; } } if (!allow) return false; } InteractionNode target = getTargetBo(context); if (source instanceof ReceiveTask) return false; if (target instanceof SendTask) return false; return super.canCreate(context) && isDifferentParticipants(source, target); } @Override protected String getStencilImageId() { return ImageProvider.IMG_16_MESSAGE_FLOW; } @Override protected Class<InteractionNode> getSourceClass() { return InteractionNode.class; } @Override protected Class<InteractionNode> getTargetClass() { return InteractionNode.class; } private boolean isDifferentParticipants(InteractionNode source, InteractionNode target) { if (source == null || target == null) { return true; } boolean different = false; ModelHandler mh = ModelHandler.getInstance(getDiagram()); Participant sourceParticipant = mh.getParticipant(source); Participant targetParticipant = mh.getParticipant(target); if (sourceParticipant==null) { if (targetParticipant==null) return false; return true; } different = !sourceParticipant.equals(targetParticipant); return different; } /* (non-Javadoc) * @see org.eclipse.bpmn2.modeler.core.features.AbstractBpmn2CreateConnectionFeature#getBusinessObjectClass() */ @Override public EClass getBusinessObjectClass() { return Bpmn2Package.eINSTANCE.getMessageFlow(); } } public static class DeleteMessageFromMessageFlowFeature extends DefaultDeleteBPMNShapeFeature { public DeleteMessageFromMessageFlowFeature(IFeatureProvider fp) { super(fp); } boolean canDeleteMessage = true; Connection messageFlowConnection; @Override public boolean canDelete(IDeleteContext context) { PictogramElement pe = context.getPictogramElement(); if (getMessageFlow(pe)!=null) return true; return false; } @Override public void delete(IDeleteContext context) { Message message = null; PictogramElement pe = context.getPictogramElement(); if (pe instanceof ContainerShape) { ContainerShape messageShape = (ContainerShape) pe; messageFlowConnection = getMessageFlowConnection(messageShape); message = BusinessObjectUtil.getFirstElementOfType(messageShape, Message.class); } else if (pe instanceof Connection) { messageFlowConnection = (Connection)pe; MessageFlow messageFlow = (MessageFlow) BusinessObjectUtil.getFirstBaseElement(messageFlowConnection); message = messageFlow.getMessageRef(); } if (message!=null) { List<EObject> list = FeatureSupport.findMessageReferences(getDiagram(), message); if (list.size()>2) canDeleteMessage = false; if (canDeleteMessage) { EcoreUtil.delete(message, true); } removeMessageDecorator(getFeatureProvider(), messageFlowConnection); } super.delete(context); } @Override protected void deleteBusinessObject(Object bo) { if (bo instanceof Message && !canDeleteMessage) return; super.deleteBusinessObject(bo); } @Override public void postDelete(IDeleteContext context) { MessageFlow messageFlow = (MessageFlow) BusinessObjectUtil.getFirstBaseElement(messageFlowConnection); if (messageFlow!=null) { messageFlow.setMessageRef(null); FeatureSupport.setPropertyValue(messageFlowConnection, MESSAGE_REF, ""); //$NON-NLS-1$ } } } public static class UpdateMessageFlowFeature extends AbstractBpmn2UpdateFeature { public UpdateMessageFlowFeature(IFeatureProvider fp) { super(fp); } /* (non-Javadoc) * @see org.eclipse.graphiti.func.IUpdate#canUpdate(org.eclipse.graphiti.features.context.IUpdateContext) */ @Override public boolean canUpdate(IUpdateContext context) { return true; } @Override public IReason updateNeeded(IUpdateContext context) { if (context.getPictogramElement() instanceof Connection) { Connection connection = (Connection) context.getPictogramElement(); MessageFlow messageFlow = (MessageFlow) BusinessObjectUtil.getFirstBaseElement(connection); String oldMessageRef = FeatureSupport.getPropertyValue(connection, MESSAGE_REF); if (oldMessageRef==null) oldMessageRef = ""; //$NON-NLS-1$ String newMessageRef = messageToString(messageFlow.getMessageRef()); if (!oldMessageRef.equals(newMessageRef)) { return Reason.createTrueReason(Messages.MessageFlowFeatureContainer_Ref_Changed); } // check if connection has been moved or reconnected if (messageDecoratorMoved(connection)) return Reason.createTrueReason(Messages.MessageFlowFeatureContainer_Decorator_Moved); } return super.updateNeeded(context); } @Override public boolean update(IUpdateContext context) { Connection connection = (Connection) context.getPictogramElement(); MessageFlow messageFlow = (MessageFlow) BusinessObjectUtil.getFirstBaseElement(connection); Message message = messageFlow.getMessageRef(); String oldMessageRef = FeatureSupport.getPropertyValue(connection, MESSAGE_REF); if (oldMessageRef==null) oldMessageRef = ""; //$NON-NLS-1$ String newMessageRef = messageToString(messageFlow.getMessageRef()); if (!oldMessageRef.equals(newMessageRef)) { removeMessageDecorator(getFeatureProvider(), connection); if (message!=null) { Shape messageShape = (Shape) context.getProperty(MESSAGE_REF); addMessageDecorator(getFeatureProvider(), connection, message, messageShape); } FeatureSupport.setPropertyValue(connection, MESSAGE_REF, newMessageRef); } else { // move the message decorator adjustMessageDecorator(getFeatureProvider(), connection); } return true; } } public static class ReconnectMessageFlowFeature extends AbstractReconnectFlowFeature { public ReconnectMessageFlowFeature(IFeatureProvider fp) { super(fp); } @Override protected Class<? extends EObject> getTargetClass() { return InteractionNode.class; } @Override protected Class<? extends EObject> getSourceClass() { return InteractionNode.class; } } }