This is the second part of my tutorial about how to extend the Eclipse BMN2 Plugin. See also:
In the second part I will explain how to create a custom model extension and how to define a custom property section for the Task Element created in Part-I.
Define a BPMN model extension
To define a BPMN model extension you can use the Eclipse Modeling Framework. Eclipse will create the necessary java classes out of the model description. So you have to follow these steps:
- Create a new EMF Model
- Generate a ‘genmodel’ file
- Generate Java Classes based on the genmodel file
- Define the BPMN Extension Points
Create a new EMF model
So in the beginning you need in to create a new EMF model file:
- Create a new Folder in your plugin project named ‘model’
- add a new Ecore Model file in the new model folder – e.g. ‘mymodel.ecore’
In EMF Model File you need to define the “name” and “namespace” and add a new Child Element of type ‘Class’ into the model with the name ‘DocumentRoot’. (note: the root name should not include special characters!). Below the DocumentRoot add a new Child element “EAnotation” with the source URL ‘http:///org/eclipse/emf/ecore/util/ExtendedMetaData’. So far your model file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="model" nsURI="http://www.imixs.org/bpmn2" nsPrefix="imixs"> <eClassifiers xsi:type="ecore:EClass" name="DocumentRoot"> <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData"> <details key="name" value=""/> <details key="kind" value="mixed"/> </eAnnotations> </eClassifiers> </ecore:EPackage>
Note: the nsURI and the nsPrefix is from the Imixs-BPMN project. You should change these values.
This model file will now be the container for your extension features.
Create Extension classes
The next step is to define the additional properties for your model extension. In the following example I add a new taskConfig element.
The ‘taskConfig’ element will be used by my custom Task created in Part-I. of the tutorial. The ‘taskConfig’ element contains a list of “Parameter” elements, each having a “name” and a “value” attribute. So this is a kind of Map Object:
<?xml version="1.0" encoding="UTF-8"?> <ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="model" nsURI="http://www.imixs.org/bpmn2" nsPrefix="imixs"> <eClassifiers xsi:type="ecore:EClass" name="DocumentRoot"> <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData"> <details key="name" value="" /> <details key="kind" value="mixed" /> </eAnnotations> <eStructuralFeatures xsi:type="ecore:EReference" name="taskConfig" upperBound="-2" eType="#//TaskConfig" containment="true"> <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData"> <details key="name" value="taskConfig" /> <details key="kind" value="element" /> <details key="namespace" value="##targetNamespace" /> </eAnnotations> </eStructuralFeatures> </eClassifiers> <eClassifiers xsi:type="ecore:EClass" name="Parameter"> <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" /> <eStructuralFeatures xsi:type="ecore:EAttribute" name="value" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString" /> </eClassifiers> <eClassifiers xsi:type="ecore:EClass" name="TaskConfig"> <eStructuralFeatures xsi:type="ecore:EReference" name="parameters" upperBound="-1" eType="#//Parameter" containment="true"> <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData"> <details key="name" value="parameter" /> <details key="kind" value="element" /> <details key="namespace" value="##targetNamespace" /> </eAnnotations> </eStructuralFeatures> </eClassifiers> </ecore:EPackage>
Create a genmodel file
When the EMF file is defined you can create the genmodel file. Choose from the context menu of the EMF file:
‘New->Other->EMF Generator Model’
and create a new file ‘mymodel.genmodel’. Choose the importer ‘ecoremodel’. Click on load will verify the EMF model file created before.
Now you need to change some of the details of the new genfile. Open the genfile and choose the root element to change the property ‘Template & Merge – >Force Overwrite’ to ‘true’. Next select the package node (first child element) and add a package name to the ‘All -> Base Package’ Element in the package node. This will be the package for the generated model classes. Finally set ‘Model -> File Extention’ to ‘bpmn’ and the ‘Model -> Resource Type’ to ‘XML’. Save these changes. Your genmodel file now should looks like this:
<?xml version="1.0" encoding="UTF-8"?> <genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" modelDirectory="/Imixs-BPMN2/src" modelPluginID="Imixs-BPMN2" forceOverwrite="true" modelName="Imixs" rootExtendsClass="org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container" importerID="org.eclipse.emf.importer.ecore" complianceLevel="7.0" copyrightFields="false" operationReflection="true" importOrganizing="true"> <foreignModel>imixs.ecore</foreignModel> <genPackages prefix="Model" basePackage="org.imixs.bpmn" resource="XML" disposableProviderFactory="true" fileExtensions="bpmn" ecorePackage="imixs.ecore#/"> <genClasses ecoreClass="imixs.ecore#//DocumentRoot"> <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference imixs.ecore#//DocumentRoot/taskConfig"/> </genClasses> <genClasses ecoreClass="imixs.ecore#//Parameter"> <genFeatures createChild="false" ecoreFeature="ecore:EAttribute imixs.ecore#//Parameter/name"/> <genFeatures createChild="false" ecoreFeature="ecore:EAttribute imixs.ecore#//Parameter/value"/> </genClasses> <genClasses ecoreClass="imixs.ecore#//TaskConfig"> <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference imixs.ecore#//TaskConfig/parameters"/> </genClasses> </genPackages> </genmodel:GenModel>
Generate java classes
Now you can generate the java classes. Click on the root element of the genfile and choose ‘Generate Model Code’. This will create the class files in the predefined package.
Note: to avoid that classes will be overwritten later when you change the model definition, you can modify the @generated marker in the class comments with ‘NOT’. See:
/** * <!-- begin-user-doc --> * The <b>Resource </b> associated with the package. * <!-- end-user-doc --> * @see org.imixs.bpmn.model.util.ModelResourceFactoryImpl * @generated NOT */ public class ModelResourceImpl extends XMLResourceImpl { .....
This is necessary for the generated classes ‘MyModelResourceFactoryImpl’ and ‘MyModelResourceImpl’ which need to be changed like in the following example:
/** * <!-- begin-user-doc --> * The <b>Resource </b> associated with the package. * <!-- end-user-doc --> * @see org.eclipse.bpmn2.modeler.examples.customtask.MyModel.util.MyModelResourceFactoryImpl * @generated NOT */ public class MyModelResourceImpl extends Bpmn2ModelerResourceImpl { /** * Creates an instance of the resource. * <!-- begin-user-doc --> * <!-- end-user-doc --> * @param uri the URI of the new resource. * @generated */ public MyModelResourceImpl(URI uri) { super(uri); } } //MyModelResourceImpl
/** * <!-- begin-user-doc --> * The <b>Resource Factory</b> associated with the package. * <!-- end-user-doc --> * @see org.eclipse.bpmn2.modeler.examples.customtask.MyModel.util.MyModelResourceImpl * @generated NOT */ public class MyModelResourceFactoryImpl extends Bpmn2ModelerResourceFactoryImpl { /** * Creates an instance of the resource factory. * <!-- begin-user-doc --> * <!-- end-user-doc --> * @generated */ public MyModelResourceFactoryImpl() { super(); } /** * Creates an instance of the resource. * <!-- begin-user-doc --> * <!-- end-user-doc --> * @generated */ @Override public Resource createResource(URI uri) { Bpmn2ModelerResourceImpl resource = new Bpmn2ModelerResourceImpl(uri); return resource; } } //MyModelResourceFactoryImpl
Define the Extension points
Now you can add the extension points to the runtime extension for the new model and the property section for the customTask element.
Create a model extension point
First add the model extension into the plugin.xml definition. Open the plugin.xml with the Eclipse Plugin Editor and go to the tab ‘extensions’. On the bpmn runtime extension point add a ‘model’ node and add there the generated ResourceFactory class. The RuntimeID has to match the predefined ID from the runtime.
Create a new Property extension point
Finally you add a ‘propertyTab’ node. The “runtime ID” is again the id form the runtime extension. Into the field ‘features’ add the value ‘extensionValues#TaskConfig.parameters’ to refer to your custom model extension. Into the field type add ‘org.eclipse.bpmn2.Task’.
The features property is a space-separated list of features that you want to display in the property tab. In this case I only want one feature. If the feature is a list (or in this case a ‘Feature Map’) a “#” object type selector can be used to only display list items of a specific type. Since this is a list of objects, select only elements from the list that are “TaskConfig” objects; for each of those objects display the “parameters” feature, which is again a list. This notation can be extended as deeply as required by the model, but it is rarely necessary. For example, if the “parameters” list contained different types of objects, we could have specified a class of feature, for example:
extensionValues#TaskConfig.parameters#SomeOhterClass.someFeature
Every customTask extension must provide some method of identifying a given object using model one or more model features. This is done by defining a extension property that is added to the customTask object when it is constructed. In my example the feature name will be “type”. This dos not have be defined in my extension model because it will be created dynamically (in our model) at runtime.
The attribute ‘value’ should also have a value in case I want to define more than one customTask with different extensions. This value essentially identifies the FeatureContainerClass that will manager this object; if I have customTasks with different requirements, I may need to define different FeatureContainers for each. Look at the ‘TestTaskElementFeatureContainer1’ class in Part-I. of the tutorial.
This is the property should look like in the plugin.xml now:
.... <property name="extensionValues"> <value> <property name="taskConfig"> <value> <property name="parameters"> <value> <property name="name" value="taskName"> </property> <property name="value" value="My custom task"> </property> </value> </property> <property name="parameters"> <value> <property name="name" value="processing time"> </property> <property name="value" value="1 h"> </property> </value> </property> </value> </property> </value> </property> ....
Create a propertySection class
The java class for the porpertyTab can be extended from the ‘org.eclipse.bpmn2.modeler.core.merrimac.clad.DefaultPropertySection’. See the following example:
public class ImixsTaskPropertySection extends DefaultPropertySection { public ImixsTaskPropertySection() { super(); } @Override protected AbstractDetailComposite createSectionRoot() { // This constructor is used to create the detail composite for use in the Property Viewer. return new MyTaskDetailComposite(this); } @Override public AbstractDetailComposite createSectionRoot(Composite parent, int style) { // This constructor is used to create the detail composite for use in the popup Property Dialog. return new MyTaskDetailComposite(parent, style); } public class MyTaskDetailComposite extends DefaultDetailComposite { public MyTaskDetailComposite(AbstractBpmn2PropertySection section) { super(section); } public MyTaskDetailComposite(Composite parent, int style) { super(parent, style); } @Override public void createBindings(EObject be) { // This must be a Task because this Property Tab is only active for Tasks. // The Property Tab will only display the Parameter list in our TaskConfig // model element (see the definition of this element in MyModel.ecore). Task myTask = (Task)be; TaskConfig taskConfig = null; // Fetch all TaskConfig extension objects from the Task List<TaskConfig> allTaskConfigs = ModelDecorator.getAllExtensionAttributeValues(myTask, TaskConfig.class); if (allTaskConfigs.size()==0) { // There are none, so we need to construct a new TaskConfig // which is required by the Property Sheet UI. taskConfig = ModelFactory.eINSTANCE.createTaskConfig(); TargetRuntime rt = getTargetRuntime(); // We need our CustomTaskDescriptor for this Task. The ID must match // the one defined in the <customTask> extension point in plugin.xml CustomTaskDescriptor ctd = rt.getCustomTask("org.imixs.workflow.bpmn.customTask"); // Get the model feature for the "taskConfig" element name. // Again, this must match the <property> element in <customTask> EStructuralFeature feature = ctd.getModelDecorator().getEStructuralFeature(be, "taskConfig"); // Add the newly constructed TaskConfig object to the Task's Extension Values list. // Note that we will delay the actual insertion of the new object until some feature // of the object changes (e.g. the Parameter.name) ModelDecorator.addExtensionAttributeValue(myTask, feature, taskConfig, true); } else { // Else reuse the existing TaskConfig object. taskConfig = allTaskConfigs.get(0); } // Display the Parameters list in TaskConfig bindList(taskConfig, ModelPackage.eINSTANCE.getTaskConfig_Parameters()); } } }
Define a modelEnablement extension
By default extension model features are “disabled”, that is they are not displayed in Property Sheets. You can enable these using a “modelEnablement” extension. The ID is again the runtimeID. Here you can control which BPMN elements in general will be available by your runtime extension. The section in your plugin.xml file can look like this:
.... <modelEnablement id="Imixs-BPMN2.modelEnablement2" profile="Full" ref="org.eclipse.bpmn2.modeler.runtime.none:Full" runtimeId="org.imixs.workflow.bpmn.runtime"> <enable object="TaskConfig"> </enable> <enable object="Parameter"> </enable> </modelEnablement> ....
Test the plugin…
Now you can test again the result of your BPMN2 Plugin extention. If add your custom task element into the editor you will see the new property section.
Part III. explains how to extend the custom property section.
Hello Ralph,
I have a problem when trying to create new properties.
In all examples we have to extend some abstract classes, but when I write my code eclipse says this:
-> the hierarchy of the type is inconsistent
for example it says it for:
– AbstractBpmn2PropertySection
– AbstractDetailComposite and others.
For AbstractBpmn2PropertySection I solved by adding this plugin “org.eclipse.ui.views.properties.tabbed”
but for the others no idea how to solve it.
Do you have any idea?
Any help would be more than appreciated.
Thanks in advance
When you start developing your own plugin, make sure that your Eclipse IDE is aware of the corresponding Sources of the Eclipse BPMN2 Plugin. Have you checked this?
Good afternoon Ralph.
When trying your code for adding a property I get some errors, caused by the same cause.
It seems that some classes imported from the plugins have some hierarchy problem.
This is the message when I try to extends a class “ImixsTaskPropertySection extends DefaultPropertySection “: The hierarchy of the type ImixsTaskPropertySection is inconsistent
I solved it for the class AbstractBpmn2PropertySection by adding the plugin “org.eclipse.ui.views.properties.tabbed” as said in the following link: https://www.eclipse.org/forums/index.php/t/804445/
But unfortunately I have no idea how to solve in all cases.
Do you have any peace of advice?
Thanks in advance, Alex
Maybe you can take a look into this tutorial:
https://wiki.eclipse.org/BPMN2-Modeler/DeveloperTutorials/Maven
After I setup my own plugin with this maven configuration the dependencies are resolved correctly.
I have an example in Github:
https://github.com/rsoika/bpmn2-tutorial
Hope this will help you