Skip to main content
Jump to: navigation, search

BPMN2-Modeler/DeveloperTutorials/Adapters

Versions

This tutorial was developed with Eclipse 4.4 (Luna) and BPMN2-Plugin version 1.1.3.

Introduction

One of the features that allows the BPMN2 Modeler to be extensible is the ability to attach a so-called "adapter" to any EObject. All EObjects contain a list of adapters that are notified during the object's lifecycle. Thus, events like feature changes, proxy resolution, EMF Resource changes, etc. are reported to these adapters.

This tutorial describes two adapters that are used throughout the editor code, what they do and how to use them in your extension plugin.


ExtendedPropertiesAdapter

Background (feel free to skip this)

The EMF genmodel editor, which is used to generate Java interfaces and implementation classes for your ecore model, can optionally create a simple, tree-based editor that can be used during testing of your model. This editor consists of two plugin components: the tree editor itself which implements a MultiPageEditorPart, and a content provider. The editor plugin is usually named something like mymodel.editor and the provider is mymodel.edit.

The EMF model generator creates one provider class for each generated Java class; a Java class roughly corresponds to an EClass in your ecore definition. This provider plugin is used by the editor UI to obtain (among other things) an icon image that represents the EClass, and text labels for the EClass and all of its EStructuralFeatures.

Unfortunately this provider plugin is insufficient to support all of the functionality the BPMN2 Modeler needs to be extensible. For example, these providers are unaware that a feature may be a "one-of-many" valued entity (e.g. the ServiceTask.implementation, or ItemDefinition.structureRef) or that a feature value has a specific text representation that can't be satisfied with a toString() method.

This is where the ExtendedPropertiesAdapter comes in. We could have simply extended the generated provider classes, but we wanted to decouple the BPMN2 Modeler from the underlying model.

When to use an ExtendedPropertiesAdapter

The BPMN2 Modeler UI uses this adapter as a provider for all aspects of the Property Views. Here is a short list of some of its uses:

  • text labels for editing widgets
  • descriptive text used in tooltips and the Description area of the "General" property tab
  • value-to-string and string-to-value conversions for editing widgets
  • combo-box values
  • editing widget-specific flags to indicate:
    • multiline edit box
    • multi-valued field (combo box)
    • inline editing (for text and combo box widgets)
    • visibility of a "can create" button for text-and-button widgets
    • visibility of a "can edit" button for text-and-button widgets
  • the list of EStructuralFeatures that are BPMN2 language extensions provided by your plugin
  • feature value getter and setter methods. The setter method is wrapped in a transaction whenever necessary, to prevent the mysterious IllegalStateException.
  • object and feature value creation methods that can be used to perform additional initialization
  • custom editing widget provided by your extension plugin to a specific feature. This allows you to provide your own ObjectEditor for a feature if none of the included widgets works for your needs.

The core UI of BPMN2 Modeler itself defines ExtendedPropertiesAdapters for most of the BPMN2 model elements. These adapters can be extended by your plugin if desired.

How to add an ExtendedPropertiesAdapter to your plugin

Start by editing your plugin.xml: in the "org.eclipse.bpmn2.modeler.runtime" extension point, add a new propertyAdapter:

BPMN2-Modeler-Adapters-propertyAdapter.png

Give it a unique ID, and the model type to which this adapter applies. Note that you can specify types declared in your own (or an external) model as well, so you must provide the fully-qualified Java name. The runtime ID should be the same as that defined in the "org.eclipse.bpmn2.modeler.runtime" extension point, unless you wish to provide behavior for another plugin. Note that if your intention is to override a propertyExtension provided by a different plugin, there is no guarantee this will work because the order in which plugins are loaded is indeterminate.

Finally, provide an implementation class. Clicking the "class" link in the plugin.xml editor will allow you to create a new Java class.

How to extend your ExtendedPropertiesAdapter

There are essentially three areas of the ExtendedPropertiesAdapter definition you need to be concerned about:

  1. Setting flags for the UI editing widgets
  2. Creating an optional ObjectDescriptor
  3. Creating one or more optional FeatureDescriptors

Setting UI Flags

UI flags are set on a feature because each feature will cause a separate editing widget to be rendered in the Property View. To set a flag, call the setProperty(EStructuralFeature feature, String key, Object value) method in your ExtendedPropertiesAdapter constructor, for example:

    EStructuralFeature feature = Bpmn2Package.eINSTANCE.getAssociation_SourceRef();
    setProperty(feature, UI_CAN_EDIT_INLINE, Boolean.FALSE);

The flags and their meanings are as follows. Note that the type of object to set for each property is indicated in parentheses:

  • LONG_DESCRIPTION (String) - verbose description of the model object that owns this ExtendedPropertiesAdapter.
  • UI_CAN_EDIT (Boolean) - indicates if this object can be edited, or is read-only.
  • UI_CAN_EDIT_INLINE (Boolean) - used only by the ComboObjectEditor widget. Any adapter that uses this must also override the feature's setValue() and getValue() which must be able to convert an arbitrary String to the required type of the feature's value (set) and back (get).
  • UI_CAN_CREATE_NEW (Boolean) - controls the visibility of a "Create" button in some editing widgets.
  • UI_CAN_SET_NULL (Boolean) - For Combo boxes (ComboObjectEditor), this indicates that an empty selection should be added to the list of possible choices; For Text fields (TextObjectEditor), this indicates that the actual value of a feature should be used as the edit field text instead of its textual representation. In this case, if the value is null, it will be replaced with an empty string.
  • UI_IS_MULTI_CHOICE (Boolean) - indicates that this feature is multi-valued and requires a ComboObjectEditor widget. In this case, you may also want to create a FeatureDescriptor and override the getChoiceOfValues() method that can provide a list of all valid selections for the combo box.
  • UI_OBJECT_EDITOR_CLASS (ObjectEditor) - defines the ObjectEditor class that should be used for the given feature instead of the default based on the feature type. A new instance of this ObjectEditor type is constructed by the bind* methods in AbstractDetailComposite and displayed in the Property sheets to edit the feature value.
  • LINE_NUMBER (int) - the line number in the XML document where this object is defined. This is a read-only property.
  • IS_EXTENSION_FEATURE (Boolean) - indicates the feature is a BPMN2 extension defined by your plugin. This should also be treated as a read-only property.

Creating the ObjectDescriptor

The ObjectDescriptor controls certain UI aspects of the object itself, such as the text label that appears on Section and List headers. Use the setObjectDescriptor(ObjectDescriptor<T> od) method to create and set an ObjectDescriptor, for example:

setObjectDescriptor(new ObjectDescriptor<DataObjectReference>(this, object) {
    @Override
    public String getTextValue() {
        // If the Data Object has a state, include it in [] brackets
        String text = super.getTextValue();
        text += " ["; //$NON-NLS-1$
        if (object.getDataState()!=null) {
            text += object.getDataState().getName();
        }
        text += "]"; //$NON-NLS-1$
        return text;
    }
});

See the Javadoc for a complete description of ObjectDescriptor and its functionality.

Creating one or more FeatureDescriptors

You must create a FeatureDescriptor for each EStructuralFeature whose behavior you want to change. The feature can be either from a BPMN2 model object, or from an external model. The FeatureDescriptors must be set in your ExtendedPropertiesAdapter constructor. Here is an example taken from the editor's ActivityPropertiesAdapter:

EStructuralFeature feature = Bpmn2Package.eINSTANCE.getActivity_IsForCompensation();
setFeatureDescriptor(feature,
    new FeatureDescriptor<T>(this,object,feature) {
        @Override
        protected void internalSet(T object, EStructuralFeature feature, Object value, int index) {
            if (value instanceof Boolean) {
                if (!(Boolean)value) {
                    // This Activity is no longer being used for Compensation.
                    // If any Compensation Boundary Events reference this
                    // Activity, set their Activity References to null.
                    TreeIterator<EObject> iter = ModelUtil.getDefinitions(object).eAllContents();
                    while (iter.hasNext()) {
                        EObject o = iter.next();
                        if (o instanceof CompensateEventDefinition) {
                            CompensateEventDefinition ced = (CompensateEventDefinition) o;
                            if (ced.getActivityRef() == object) {
                                ExtendedPropertiesProvider.setValue(ced, Bpmn2Package.eINSTANCE.getCompensateEventDefinition_ActivityRef(), null);
                            }
                        }
                    }
                }
            }
            super.internalSet(object, feature, value, index);
        }
    }
);

InsertionAdapter

This adapter will insert a new value into its container feature when the owning object's content changes. This allows the UI to construct new objects without inserting them into their container unless the user changes some feature in the new object. Thus, an empty EObject is available for use by the UI for rendering only, without creating an EMF transaction, and hence, a useless entry on the undo stack. For more information about why this adapter exists, please see the Custom Property Tabs tutorial.

Here is a typical use of InsertionAdapter:

// fetch the ioSpec from activity
InputOutputSpecification ioSpec = activity.getIoSpecification();
if (ioSpec==null) {
    // if it is null, create a new one
    ioSpec = Bpmn2ModelerFactory.create_InputOutputSpecification();
}
if (ioSpec.getInputSets().size()==0) {
    // the ioSpec does not have any inputSets, create one and insert it
    InputSet inputSet = Bpmn2ModelerFactory.create_InputSet();
    InsertionAdapter.add(ioSpec, Bpmn2Package.eINSTANCE.getInputOutputSpecification_InputSets(), inputSet);
}
// Set the new ioSpec into the activity. If the activity already has one, this has no effect
InsertionAdapter.add(activity, Bpmn2Package.eINSTANCE.getActivity_IoSpecification(), ioSpec);

CustomLayoutAdapter

Adapters are a good entry point to style a custom BPMN element. The adapter pattern can be used to change the layout or style of a custom task element when an attribute of the corresponding model object changed. To react on changes the method 'notifyChanged' can be overwritten. The type '

@Override
public void notifyChanged(Notification notification) 
	if ( notification.getEventType() == Notification.SET) {
		// your code goes here...
	}
	// finally call the super method!
	super.notifyChanged(notification);
}


The following code example shows an adapter class reacting on changes of the model objects. If the property 'warning_message' changed the fill color of the shape will be changed:

import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.modeler.core.model.ModelDecorator;
import org.eclipse.bpmn2.modeler.core.preferences.ShapeStyle;
import org.eclipse.bpmn2.modeler.core.utils.BusinessObjectUtil;
import org.eclipse.bpmn2.modeler.core.utils.StyleUtil;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.util.ColorConstant;
import org.eclipse.graphiti.util.IColorConstant;

/**
 * The EventAdapter chnages the fill color of the ContaienrShape depending on
 * the value of a EObject.
 * 
 */
public class ColorChangeAdapter extends EContentAdapter {

	private static final IColorConstant ACTIVITYENTITY_BACKGROUND = new ColorConstant(255, 217, 64);

	private static final IColorConstant ACTIVITYENTITY_BACKGROUND_WARNING = new ColorConstant(155, 117, 164);

	ContainerShape containerShape = null;

	public ColorChangeAdapter(ContainerShape containerShape) {
		super();
		this.containerShape = containerShape;
	}

	@Override
	public void setTarget(Notifier newTarget) {
		super.setTarget(newTarget);
		setFillColor();
	}

	@Override
	public void notifyChanged(Notification notification) {
		int type = notification.getEventType();
		if (type == Notification.SET) {
		  EStructuralFeature feature = (msg.getFeature() instanceof EStructuralFeature ? (EStructuralFeature)msg.getFeature() : null);
		  if (msg.getEventType()==Notification.SET && feature!=null && feature.getName().contains("warning_message")) {
			setFillColor();
 		  }
		}
		super.notifyChanged(notification);
	}

	/**
	 * Common method used to set the fill color
	 */
	private void setFillColor() {
		BaseElement be = BusinessObjectUtil.getFirstBaseElement(containerShape);
		EStructuralFeature feature = ModelDecorator.getAnyAttribute(be, "warning_message");
		Shape shape = containerShape.getChildren().get(0);
		ShapeStyle shapeStyle = new ShapeStyle();
		if (feature != null) {
			shapeStyle.setDefaultColors(ACTIVITYENTITY_BACKGROUND);
		} else {
			shapeStyle.setDefaultColors(ACTIVITYENTITY_BACKGROUND_WARNING);
		}
		StyleUtil.applyStyle(shape.getGraphicsAlgorithm(), be, shapeStyle);
	}
}

To activate the adapter just add the adapter during the 'getAddFeature' call of your custom task element:


....
/**
 * override the Add Feature from the chosen Feature Container base
 * class . Typically you will want to override the decorateShape()
 * method which allows you to customize the graphical representation
 * of this Custom Task figure.
 */
@Override public IAddFeature getAddFeature(IFeatureProvider fp) {
	return new AddIntermediateCatchEventFeature(fp) {
		@Override
		protected void decorateShape(IAddContext context, ContainerShape containerShape,
				IntermediateCatchEvent businessObject) {
			super.decorateShape(context, containerShape, businessObject);
			// add a notifyChangeAdapter to change the color...
			businessObject.eAdapters().add(new ColorChangeAdapter(containerShape));
		}
	};
}
....

StyleChangeAdapter

Copyright © Eclipse Foundation, Inc. All Rights Reserved.