This is the third part of my tutorial about how to extend the Eclipse BMN2 Plugin. See also:
In this part I will explain some details about how to manage properties of a custom task element and how to implement custom property sections.
createBindings
In part II the custom property section class has a very simple implementation of the createBindings() method:
.... @Override public void createBindings(EObject be) { 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) { taskConfig = ModelFactory.eINSTANCE.createTaskConfig(); TargetRuntime rt = getTargetRuntime(); CustomTaskDescriptor ctd = rt.getCustomTask(ImixsTaskFeatureContainer.PROCESSENTITY_TASK_ID); EStructuralFeature feature = ctd.getModelDecorator().getEStructuralFeature(be, "taskConfig"); ModelDecorator.addExtensionAttributeValue(myTask, feature, taskConfig, true); }
Here I simply created a new empty taskConfig element if non existed and add this new object to the Task object. But we can also add some default values to the taskConfig element. See the following example:
taskConfig = ModelFactory.eINSTANCE.createTaskConfig(); // add some default values... Parameter paramAddress=ModelFactory.eINSTANCE.createParameter(); paramAddress.setName("address"); paramAddress.setValue("London"); taskConfig.getParameters().add(paramAddress);
In the same way you can evaluate existing values and change them to your needs.
Bind Attributes
Now as we have defined concrete parameters for our custom task, these objects can be bound to the property section. The AbstractDetailComposite provides a set of methods to bind an EObject to a text input widget:
bindAttribute(parent, paramAddress,"value","Please enter the address here");
The third parameter ‘value’ is the property of our Parameter class to be bound.
Create custom controls
If the bind methods provided by the AbstractDetailComposite did not fit your needs, you can also create input widgets manually. Also here some convenience methods are provided by BPMN2. See the following example which creates the same result es before:
TargetRuntime rt = getTargetRuntime(); CustomTaskDescriptor ctd = rt.getCustomTask(ImixsTaskFeatureContainer.PROCESSENTITY_TASK_ID); EStructuralFeature feature= ctd.getModelDecorator() .getEStructuralFeature(paramAddress, "value"); ObjectEditor editor = new TextObjectEditor(this,paramAddress,feature); editor.createControl(parent,"Please enter the Address here");
Custom Layout
You can also customize the layout of your property section. In this case you overwrite the method initialize(). This method is called before createBindings(). So this is the right place to add custom composites and layout your section.
public void initialize() { super.initialize(); Section mailAttributesSection = createSection(parent, "Mail Konfiguration", true); mailAttributesSection.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true, 3, 1)); Composite mailAttributesComposite = toolkit .createComposite(mailAttributesSection); mailAttributesSection.setClient(mailAttributesComposite); mailAttributesComposite.setLayout(new GridLayout(3, false)); this.createLabel(mailAttributesComposite, "Some text"); }
Hide the default property list
If you subclass a custom property detail composite from ‘AbstractDetailComposite’ this will automatically bind a the properties to a list element which is shown in top of your property section (see part II.). To avoid the generation of such an default list element you need to place at least one element into the ‘AttributesParent’ composite provided by the ‘AbstractDetailComposite’:
setTitle("Mail Configuration"); bindAttribute(this.getAttributesParent(), paramAddress,"value","Please enter the address here");
The full example
Here is my full example of a custom property section where I initialize a param list with default values and bind them to the AttributesParent composite:
package org.imixs.bpmn.model; import java.util.List; import org.eclipse.bpmn2.Task; import org.eclipse.bpmn2.di.BPMNDiagram; import org.eclipse.bpmn2.modeler.core.merrimac.clad.AbstractBpmn2PropertySection; import org.eclipse.bpmn2.modeler.core.merrimac.clad.AbstractDetailComposite; import org.eclipse.bpmn2.modeler.core.model.ModelDecorator; 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.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.widgets.Composite; /** * This PorpertySection provides the attributes for Mail config. * * @author rsoika * */ public class MailPropertySection extends AbstractBpmn2PropertySection { public MailPropertySection() { 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); } /** * Here we extract the bpmn task element from the current ISelection */ @Override public EObject getBusinessObjectForSelection(ISelection selection) { EObject bo = BusinessObjectUtil .getBusinessObjectForSelection(selection); if (bo instanceof BPMNDiagram) { if (((BPMNDiagram) bo).getPlane() != null && ((BPMNDiagram) bo).getPlane().getBpmnElement() != null) return ((BPMNDiagram) bo).getPlane().getBpmnElement(); } return bo; } public class MyTaskDetailComposite extends AbstractDetailComposite { public MyTaskDetailComposite(AbstractBpmn2PropertySection section) { super(section); } public MyTaskDetailComposite(Composite parent, int style) { super(parent, style); } public void initialize() { super.initialize(); } @Override public void setBusinessObject(EObject be) { super.setBusinessObject(be); } @Override public void createBindings(EObject be) { TaskConfig taskConfig = null; if (!(be instanceof Task)) { System.out .println("WARNING: this proeprty tab in only working with Tasks Please check por plugin.xml!"); } TargetRuntime rt = getTargetRuntime(); // Get the CustomTaskDescriptor for this Task. CustomTaskDescriptor ctd = rt .getCustomTask(ImixsTaskFeatureContainer.PROCESSENTITY_TASK_ID); Task myTask = (Task) be; // 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(); // initalize values initializeProperty(taskConfig, "txtMailSubject", ""); initializeProperty(taskConfig, "namMailReceiver", ""); initializeProperty(taskConfig, "keyMailReceiverFields", ""); initializeProperty(taskConfig, "namMailReceiverCC", ""); initializeProperty(taskConfig, "keyMailReceiverFieldsCC", ""); initializeProperty(taskConfig, "rtfMailBody", ""); // 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); } setTitle("Mail Configuration"); bindAttribute(this.getAttributesParent(), getProperty(taskConfig,"namMailReceiver"), "value", "To"); bindAttribute(this.getAttributesParent(), getProperty(taskConfig,"txtMailSubject"), "value", "Subject"); bindAttribute(this.getAttributesParent(), getProperty(taskConfig,"rtfMailBody"), "value", "Body"); } } /** * This method verifies if a specific property still exists. If not the * method initializes the value * * @param taskConfig * @param propertyName */ protected Parameter initializeProperty(TaskConfig taskConfig, String propertyName, String defaultVaue) { // test all parameters if we have the propertyName EList<Parameter> parameters = taskConfig.getParameters(); for (Parameter param : parameters) { if (param.getName().equals(propertyName)) { // param allready exists return param; } } // the property was not found so we initialize it... Parameter param = ModelFactory.eINSTANCE.createParameter(); param.setName(propertyName); param.setValue(defaultVaue); taskConfig.getParameters().add(param); return param; } /** * THis method returns the Parameter object for a specific object. If the * object did not exist the method creates an empty new parameter * * @param taskConfig * @param propertyName * @return */ protected Parameter getProperty(TaskConfig taskConfig, String propertyName) { // test all parameters if we have the propertyName EList<Parameter> parameters = taskConfig.getParameters(); for (Parameter param : parameters) { if (param.getName().equals(propertyName)) { // param allready exists return param; } } // we have not found this param - so we add a new one.... return initializeProperty(taskConfig, propertyName, ""); } }
The property tab will look like this:
And this is like the bpmn task element will look in the bpmn2 file:
<bpmn2:task id="Task_2" imixs:type="MyTask" imixs:Imixs="Hello World" imixs:benefit="0" name="Task 2"> <bpmn2:extensionElements> <imixs:taskConfig> <imixs:parameter xsi:type="imixs:Parameter" name="txtMailSubject" value="Hello"/> <imixs:parameter xsi:type="imixs:Parameter" name="namMailReceiver" value="xxx@yyy.com"/> <imixs:parameter xsi:type="imixs:Parameter" name="keyMailReceiverFields" value=""/> <imixs:parameter xsi:type="imixs:Parameter" name="namMailReceiverCC" value=""/> <imixs:parameter xsi:type="imixs:Parameter" name="keyMailReceiverFieldsCC" value=""/> <imixs:parameter xsi:type="imixs:Parameter" name="rtfMailBody" value="Some message...."/> </imixs:taskConfig> </bpmn2:extensionElements> <bpmn2:incoming>SequenceFlow_3</bpmn2:incoming> <bpmn2:outgoing>SequenceFlow_4</bpmn2:outgoing> </bpmn2:task>
Changing a BPMN extension dynamically
As shown in the XML example before the data structure I used in this tutorial is not restricted to a fix set of attributes. Each parameter is defined by the attributes ‘name’ and ‘value’.
<imixs:parameter xsi:type="imixs:Parameter" name="...." value=""/>
This means that you can extend the structure later or dynmically during runtime. But the behavior of the Eclipse EMF model and the Grafiti framework is little bit tricky if you try to change the structure after a EObject was read from your bpmn file. Normally you can add an additional attribute like this:
... Parameter param = ModelFactory.eINSTANCE.createParameter(); param.setName(propertyName); param.setValue(defaultVaue); taskConfig.getParameters().add(param);
But if you do so during runtime you will get a java.lang.IllegalStateException with the error message: “Cannot modify resource set without a write transaction”
To avoid this restriction you need to embed your code into a EMF transactional editing domain. This allows you to extend existing structures dynamically. See the following example:
@Override public void createBindings(EObject be) { super.createBindings(be); TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(taskConfig); if (domain != null) { domain.getCommandStack().execute(new RecordingCommand(domain) { public void doExecute() { // do the changes here bindAttribute(newparam, "value", "....") ; } }); } }
If you run into this situation be carfull if you add new ExtensionAttributesValue into a new taskConfig object:
ModelDecorator.addExtensionAttributeValue(task, feature, taskConfig, false); // !! need to be false!!
The last param of the method addExtensionAttributesValue() need to be set to ‘false’. Otherwise the ExtensionAttributeValue will be added only when the values are changed the first time. This can be to late if you need to manipulate your extensions dynamically.
So that’s it…
I hope this will help someone. If you like you can also see the result of the BPMN extension of Imixs-Workflow on GitHub and check out the source code.
If you like to join the Imixs-Workflow Project take a look at the Imixs-Workfow project home.
good morning,
I wonder if there is a tutorial of how to generate the installer for my extension BPMN2Modeler through maven. I made changes to the metamodel, but it does not know how to reference these changes modeler.
could help me in any way?
thanks in advance.
I have seen that you already posted your question to the forum. This is the best place for your question. There is much support of the bpmn2 dev team in the forum.