| /******************************************************************************* |
| * 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 Bob Brodt |
| ******************************************************************************/ |
| |
| package org.eclipse.bpmn2.modeler.core.features; |
| |
| import org.eclipse.bpmn2.Activity; |
| import org.eclipse.bpmn2.BaseElement; |
| import org.eclipse.bpmn2.Bpmn2Package; |
| import org.eclipse.bpmn2.EndEvent; |
| import org.eclipse.bpmn2.Event; |
| import org.eclipse.bpmn2.Group; |
| import org.eclipse.bpmn2.Lane; |
| import org.eclipse.bpmn2.SequenceFlow; |
| import org.eclipse.bpmn2.modeler.core.LifecycleEvent; |
| import org.eclipse.bpmn2.modeler.core.LifecycleEvent.EventType; |
| import org.eclipse.bpmn2.modeler.core.adapters.ExtendedPropertiesAdapter; |
| import org.eclipse.bpmn2.modeler.core.merrimac.dialogs.ObjectEditingDialog; |
| import org.eclipse.bpmn2.modeler.core.preferences.Bpmn2Preferences; |
| import org.eclipse.bpmn2.modeler.core.preferences.ModelEnablements; |
| import org.eclipse.bpmn2.modeler.core.runtime.CustomTaskDescriptor; |
| import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntime; |
| 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.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.graphiti.IExecutionInfo; |
| import org.eclipse.graphiti.features.IFeatureAndContext; |
| import org.eclipse.graphiti.features.IFeatureProvider; |
| import org.eclipse.graphiti.features.context.IContext; |
| import org.eclipse.graphiti.features.context.ICreateConnectionContext; |
| import org.eclipse.graphiti.features.context.IReconnectionContext; |
| import org.eclipse.graphiti.features.context.impl.AddConnectionContext; |
| import org.eclipse.graphiti.features.context.impl.CreateConnectionContext; |
| import org.eclipse.graphiti.features.context.impl.ReconnectionContext; |
| import org.eclipse.graphiti.features.impl.AbstractCreateConnectionFeature; |
| import org.eclipse.graphiti.mm.pictograms.Anchor; |
| import org.eclipse.graphiti.mm.pictograms.AnchorContainer; |
| import org.eclipse.graphiti.mm.pictograms.Connection; |
| import org.eclipse.graphiti.mm.pictograms.Diagram; |
| import org.eclipse.graphiti.mm.pictograms.PictogramElement; |
| import org.eclipse.graphiti.services.Graphiti; |
| import org.eclipse.graphiti.ui.editor.DiagramEditor; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * This is the Graphiti CreateFeature base class for all BPMN2 model elements which are |
| * considered "connections" e.g. {@link org.eclipse.bpmn2.SequenceFlow}, {@link org.eclipse.bpmn2.Association}, |
| * {@link org.eclipse.bpmn2.MessageFlow} and {@link org.eclipse.bpmn2.ConversationLink} |
| * |
| * The Type Parameter "CONNECTION" is the BPMN2 element class, "SOURCE" is the |
| * BPMN2 class of the source object of the connection, "TARGET" is the BPMN2 |
| * class of the connection target object. |
| * |
| * @param <CONNECTION> the generic type for the BPMN2 connection |
| * @param <SOURCE> the generic type for the BPMN2 source element |
| * @param <TARGET> the generic type for the BPMN2 target element |
| */ |
| public abstract class AbstractBpmn2CreateConnectionFeature< |
| CONNECTION extends BaseElement, |
| SOURCE extends EObject, |
| TARGET extends EObject> |
| extends AbstractCreateConnectionFeature |
| implements IBpmn2CreateFeature<CONNECTION, ICreateConnectionContext> { |
| |
| /** The changes done. */ |
| protected boolean changesDone = true; |
| |
| /** |
| * Default constructor for this Create Feature. |
| * |
| * @param fp - the BPMN2 Modeler Feature Provider |
| * @param name - name of the type of object being created |
| * @param description - description of the object being created |
| * @link org.eclipse.bpmn2.modeler.ui.diagram.BPMNFeatureProvider |
| */ |
| public AbstractBpmn2CreateConnectionFeature(IFeatureProvider fp) { |
| super(fp, "", ""); |
| } |
| |
| public String getCreateName() { |
| // TODO: get name from Messages by generating a field name using the business object class |
| return ModelUtil.toCanonicalString(getFeatureClass().getName()); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.graphiti.features.impl.AbstractCreateFeature#getCreateDescription() |
| * This is displayed in the Edit -> Undo/Redo menu |
| */ |
| @Override |
| public String getCreateDescription() { |
| return NLS.bind(Messages.AbstractBpmn2CreateConnectionFeature_Create, |
| ModelUtil.toCanonicalString( getFeatureClass().getName())); |
| } |
| |
| @Override |
| public String getName() { |
| return getCreateName(); |
| } |
| |
| @Override |
| public String getDescription() { |
| return getCreateDescription(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.graphiti.features.impl.AbstractFeature#isAvailable(org.eclipse.graphiti.features.context.IContext) |
| * Returns true if this type of connection is available in the tool palette and context menus. |
| */ |
| @Override |
| public boolean isAvailable(IContext context) { |
| Object o = null; |
| if (context instanceof ICreateConnectionContext) { |
| ICreateConnectionContext ccc = (ICreateConnectionContext)context; |
| if (ccc.getTargetPictogramElement()!=null) { |
| o = BusinessObjectUtil.getFirstElementOfType( |
| ccc.getTargetPictogramElement(), BaseElement.class); |
| } |
| else if (ccc.getSourcePictogramElement()!=null) { |
| o = BusinessObjectUtil.getFirstElementOfType( |
| ccc.getSourcePictogramElement(), BaseElement.class); |
| } |
| } |
| else if (context instanceof IReconnectionContext) { |
| IReconnectionContext rc = (IReconnectionContext)context; |
| if (rc.getTargetPictogramElement()!=null) { |
| o = BusinessObjectUtil.getFirstElementOfType( |
| rc.getTargetPictogramElement(), BaseElement.class); |
| } |
| } |
| |
| if (o instanceof EndEvent || o instanceof Group) |
| return false; |
| |
| if (o instanceof EObject) { |
| return isModelObjectEnabled((EObject)o); |
| } |
| return false; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.graphiti.func.ICreateConnection#canStartConnection(org.eclipse.graphiti.features.context.ICreateConnectionContext) |
| * Returns true if the source object is valid for this type of connection. |
| */ |
| @Override |
| public boolean canStartConnection(ICreateConnectionContext context) { |
| EObject o = getSourceBo(context); |
| return o != null && !(o instanceof Lane); |
| } |
| |
| /** |
| * Check if the connection is allowed. |
| * Only one connection of each type is allowed between the same source and target objects. |
| * Also enforce User Preference if only one incoming or outgoing connection is allowed. |
| * |
| * @param sourceContainer the source container |
| * @param targetContainer the target container |
| * @param connectionClass the connection class |
| * @param reconnectType the reconnect type |
| * @return true, if the connection is allowed |
| */ |
| public static boolean canCreateConnection(AnchorContainer sourceContainer, AnchorContainer targetContainer, EClass connectionClass, String reconnectType) { |
| if (sourceContainer instanceof Diagram || targetContainer instanceof Diagram) |
| return false; |
| if (sourceContainer == targetContainer) |
| return true; |
| if (sourceContainer!=null && targetContainer!=null) { |
| // Make sure only one connection of each type is created for the same |
| // source and target objects, i.e. you can't have two SequenceFlows |
| // with the same source and target objects. |
| // for (Anchor sourceAnchor : sourceContainer.getAnchors()) { |
| // for (Connection sourceConnection : sourceAnchor.getOutgoingConnections()) { |
| // EObject sourceObject = BusinessObjectUtil.getBusinessObjectForPictogramElement(sourceConnection); |
| // if (connectionClass==Bpmn2Package.eINSTANCE.getDataAssociation()) { |
| // // Ugh! Special case for DataAssociations: we may have |
| // // an Activity with both a DataOutputAssociation and a |
| // // DataInputAssociation to the same ItemAwareElement |
| // EObject o = BusinessObjectUtil.getBusinessObjectForPictogramElement(sourceContainer); |
| // if (o instanceof ItemAwareElement) |
| // connectionClass = Bpmn2Package.eINSTANCE.getDataInputAssociation(); |
| // else |
| // connectionClass = Bpmn2Package.eINSTANCE.getDataOutputAssociation(); |
| // } |
| // if (sourceObject!=null && sourceObject.eClass() == connectionClass) { |
| // if (sourceConnection.getEnd().getParent() == targetContainer) |
| // return false; |
| // } |
| // } |
| // } |
| EObject sourceObject = BusinessObjectUtil.getBusinessObjectForPictogramElement(sourceContainer); |
| if (sourceObject instanceof Lane) |
| return false; |
| EObject targetObject = BusinessObjectUtil.getBusinessObjectForPictogramElement(targetContainer); |
| if (targetObject instanceof Lane) |
| return false; |
| |
| Bpmn2Preferences prefs = Bpmn2Preferences.getInstance(sourceContainer); |
| if (!prefs.getAllowMultipleConnections() && connectionClass==Bpmn2Package.eINSTANCE.getSequenceFlow()) { |
| // if User Preferences don't allow multiple incoming/outgoing |
| // connections on Activities, enforce it here. |
| if (!ReconnectionContext.RECONNECT_TARGET.equals(reconnectType)) { |
| sourceObject = BusinessObjectUtil.getBusinessObjectForPictogramElement(sourceContainer); |
| if (sourceObject instanceof Activity || sourceObject instanceof Event) { |
| for (Anchor a : sourceContainer.getAnchors()) { |
| for (Connection c : a.getOutgoingConnections()) { |
| EObject o = BusinessObjectUtil.getBusinessObjectForPictogramElement(c); |
| if (o instanceof SequenceFlow) { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!ReconnectionContext.RECONNECT_SOURCE.equals(reconnectType)) { |
| targetObject = BusinessObjectUtil.getBusinessObjectForPictogramElement(targetContainer); |
| if (targetObject instanceof Activity || targetObject instanceof Event) { |
| for (Anchor a : targetContainer.getAnchors()) { |
| for (Connection c : a.getIncomingConnections()) { |
| EObject o = BusinessObjectUtil.getBusinessObjectForPictogramElement(c); |
| if (o instanceof SequenceFlow) { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| return false; |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.bpmn2.modeler.core.features.IBpmn2CreateFeature#createBusinessObject(org.eclipse.graphiti.features.context.IContext) |
| * Creates the business object, i.e. the BPMN2 element |
| */ |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public CONNECTION createBusinessObject(ICreateConnectionContext context) { |
| Resource resource = getResource(context); |
| EClass eclass = getBusinessObjectClass(); |
| ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(eclass); |
| CONNECTION businessObject = (CONNECTION)adapter.getObjectDescriptor().createObject(resource,eclass); |
| EStructuralFeature nameFeature = businessObject.eClass().getEStructuralFeature("name"); //$NON-NLS-1$ |
| if (nameFeature!=null) { |
| businessObject.eUnset(nameFeature); |
| } |
| SOURCE source = getSourceBo(context); |
| TARGET target = getTargetBo(context); |
| EStructuralFeature sourceRefFeature = businessObject.eClass().getEStructuralFeature("sourceRef"); //$NON-NLS-1$ |
| EStructuralFeature targetRefFeature = businessObject.eClass().getEStructuralFeature("targetRef"); //$NON-NLS-1$ |
| if (sourceRefFeature!=null && targetRefFeature!=null) { |
| businessObject.eSet(sourceRefFeature, source); |
| businessObject.eSet(targetRefFeature, target); |
| } |
| putBusinessObject(context, businessObject); |
| changesDone = true; |
| return businessObject; |
| } |
| |
| protected Resource getResource(ICreateConnectionContext context) { |
| PictogramElement pe = context.getSourcePictogramElement(); |
| if (pe==null) |
| pe = context.getTargetPictogramElement(); |
| if (pe==null) |
| pe = context.getSourceAnchor(); |
| if (pe==null) |
| pe = context.getTargetAnchor(); |
| EObject bo = BusinessObjectUtil.getBusinessObjectForPictogramElement(pe); |
| return bo.eResource(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.bpmn2.modeler.core.features.IBpmn2CreateFeature#getBusinessObject(org.eclipse.graphiti.features.context.IContext) |
| * Fetches the business object from the Create Context |
| */ |
| @SuppressWarnings("unchecked") |
| public CONNECTION getBusinessObject(ICreateConnectionContext context) { |
| return (CONNECTION) context.getProperty(GraphitiConstants.BUSINESS_OBJECT); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.bpmn2.modeler.core.features.IBpmn2CreateFeature#putBusinessObject(org.eclipse.graphiti.features.context.IContext, org.eclipse.emf.ecore.EObject) |
| * Saves the business object in the Create Context. |
| * If the object is a Custom Element, it is initialized as defined in the extension plugin's plugin.xml |
| */ |
| public void putBusinessObject(ICreateConnectionContext context, CONNECTION businessObject) { |
| context.putProperty(GraphitiConstants.BUSINESS_OBJECT, businessObject); |
| String id = (String)context.getProperty(GraphitiConstants.CUSTOM_ELEMENT_ID); |
| if (id!=null) { |
| TargetRuntime rt = TargetRuntime.getCurrentRuntime(); |
| CustomTaskDescriptor ctd = rt.getCustomTask(id); |
| ctd.populateObject(businessObject, getResource(context), true); |
| } |
| |
| TargetRuntime.getCurrentRuntime().notify(new LifecycleEvent(EventType.BUSINESSOBJECT_INITIALIZED, |
| getFeatureProvider(), context, businessObject)); |
| } |
| |
| public EClass getFeatureClass() { |
| return getBusinessObjectClass(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.bpmn2.modeler.core.features.IBpmn2CreateFeature#postExecute(org.eclipse.graphiti.IExecutionInfo) |
| * Invoked after the graphic has been created to display an optional configuration dialog. |
| * The configuration dialog popup is enabled/disabled in the user Preferences for BPMN2 Editor. |
| */ |
| public void postExecute(IExecutionInfo executionInfo) { |
| for (IFeatureAndContext fc : executionInfo.getExecutionList()) { |
| IContext context = fc.getContext(); |
| if (context instanceof ICreateConnectionContext) { |
| ICreateConnectionContext cc = (ICreateConnectionContext)context; |
| CONNECTION businessObject = getBusinessObject(cc); |
| Bpmn2Preferences prefs = (Bpmn2Preferences) ((DiagramEditor) getDiagramEditor()).getAdapter(Bpmn2Preferences.class); |
| if (prefs!=null && prefs.getShowPopupConfigDialog(businessObject)) { |
| ObjectEditingDialog dialog = |
| new ObjectEditingDialog((DiagramEditor)getDiagramEditor(), businessObject); |
| dialog.open(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates and prepares a new AddConnectionContext from a CreateConnectionContext. |
| * |
| * @param context the CreateConnectionContext |
| * @param newObject the new object, must be a BPMN2 connection object (see class description) |
| * @return a new AddConnectionContext |
| */ |
| protected AddConnectionContext createAddConnectionContext(ICreateConnectionContext context, Object newObject) { |
| AddConnectionContext newContext = new AddConnectionContext(context.getSourceAnchor(), context.getTargetAnchor()); |
| newContext.setNewObject(newObject); |
| |
| // copy properties into the new context |
| Object value = context.getProperty(GraphitiConstants.CUSTOM_ELEMENT_ID); |
| newContext.putProperty(GraphitiConstants.CUSTOM_ELEMENT_ID, value); |
| value = context.getProperty(GraphitiConstants.IMPORT_PROPERTY); |
| newContext.putProperty(GraphitiConstants.IMPORT_PROPERTY, value); |
| value = context.getProperty(GraphitiConstants.BUSINESS_OBJECT); |
| newContext.putProperty(GraphitiConstants.BUSINESS_OBJECT, value); |
| return newContext; |
| } |
| |
| /** |
| * Convenience method to check if a model object was disabled in the extension plugin. |
| * |
| * @return true/false depending on if the model object is enabled or disabled. |
| * If disabled, the object will not be available and will not appear in the tool palette |
| * or context menus. |
| */ |
| protected boolean isModelObjectEnabled() { |
| ModelEnablements me = getModelEnablements(); |
| if (me!=null) |
| return me.isEnabled(getBusinessObjectClass()); |
| return false; |
| } |
| |
| /** |
| * Checks if is model object enabled. |
| * |
| * @param o the o |
| * @return true, if is model object enabled |
| */ |
| protected boolean isModelObjectEnabled(EObject o) { |
| ModelEnablements me = getModelEnablements(); |
| if (me!=null) { |
| EClass eclass = (o instanceof EClass) ? (EClass)o : o.eClass(); |
| return me.isEnabled(eclass); |
| } |
| return false; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.graphiti.features.impl.AbstractFeature#hasDoneChanges() |
| */ |
| @Override |
| public boolean hasDoneChanges() { |
| return changesDone; |
| } |
| |
| /** |
| * Gets the model enablements. |
| * |
| * @return the model enablements |
| */ |
| protected ModelEnablements getModelEnablements() { |
| DiagramEditor editor = (DiagramEditor) getDiagramEditor(); |
| return (ModelEnablements) editor.getAdapter(ModelEnablements.class); |
| } |
| |
| /** |
| * Returns the business object for the connection source shape. If the source object is not valid |
| * for this connection type, return null. |
| * |
| * @param context - connection create context |
| * @return true if the source is valid, false if not. |
| */ |
| protected SOURCE getSourceBo(ICreateConnectionContext context) { |
| Anchor a = getSourceAnchor(context); |
| if (a != null) { |
| return BusinessObjectUtil.getFirstElementOfType(a.getParent(), getSourceClass()); |
| } |
| PictogramElement pe = context.getSourcePictogramElement(); |
| if (pe != null) { |
| return BusinessObjectUtil.getFirstElementOfType(pe, getSourceClass()); |
| } |
| return null; |
| } |
| |
| protected Anchor getSourceAnchor(ICreateConnectionContext context) { |
| Anchor a = context.getSourceAnchor(); |
| PictogramElement pe = context.getSourcePictogramElement(); |
| if (a==null && FeatureSupport.isLabelShape(pe)) { |
| pe = FeatureSupport.getLabelOwner(pe); |
| ((CreateConnectionContext)context).setSourcePictogramElement(pe); |
| a = Graphiti.getPeService().getChopboxAnchor((AnchorContainer) pe); |
| ((CreateConnectionContext)context).setSourceAnchor(a); |
| } |
| return a; |
| } |
| |
| /** |
| * Returns the business object for the connection target shape. If the target object is not valid |
| * for this connection type, return null. |
| * |
| * @param context - connection create context |
| * @return true if the target is valid, false if not. |
| */ |
| protected TARGET getTargetBo(ICreateConnectionContext context) { |
| Anchor a = getTargetAnchor(context); |
| if (a != null) { |
| return BusinessObjectUtil.getFirstElementOfType(a.getParent(), getTargetClass()); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the Target Anchor. This does the translation from a Label shape to its owner. |
| * |
| * @param context |
| * @return |
| */ |
| protected Anchor getTargetAnchor(ICreateConnectionContext context) { |
| Anchor a = context.getTargetAnchor(); |
| PictogramElement pe = context.getTargetPictogramElement(); |
| if (a==null && FeatureSupport.isLabelShape(pe)) { |
| pe = FeatureSupport.getLabelOwner(pe); |
| ((CreateConnectionContext)context).setTargetPictogramElement(pe); |
| a = Graphiti.getPeService().getChopboxAnchor((AnchorContainer) pe); |
| ((CreateConnectionContext)context).setTargetAnchor(a); |
| } |
| return a; |
| } |
| |
| /** |
| * Gets the source object type. |
| * Implementation classes must override this method to provide the BPMN2 |
| * object source and target classes that are valid for this connection. |
| * |
| * @return the source class |
| */ |
| protected abstract Class<SOURCE> getSourceClass(); |
| |
| /** |
| * Gets the target object type. |
| * Implementation classes must override this method to provide the BPMN2 |
| * object source and target classes that are valid for this connection. |
| * |
| * @return the target class |
| */ |
| protected abstract Class<TARGET> getTargetClass(); |
| |
| |
| protected DiagramEditor getDiagramEditor() { |
| return (DiagramEditor)getFeatureProvider().getDiagramTypeProvider().getDiagramBehavior().getDiagramContainer(); |
| } |
| } |