How to implement BPEL Activities in bexee

This guide describes the procedure for adding and implementing a new Activity in bexee. This will be necessary as new activities will be added to the BPEL standard or if an existing activity that has not been implemented in bexee should be implemented.

To describe this procedure, the "Switch" activity will be implemented.

In order to implement the "Switch" activity it will necessary to do the following steps:

  1. Define an Interface for the "Switch" activity
  2. Implement the interface
  3. Complete the xml-oo mapping configuration file "bpel-rules.xml"
  4. Implement an ObjectCreationFactory for the "Switch" activities
  5. Complete the BPELElementFactory class

Define an Interface for the "Switch" activity

Before the definition of the activity interface it is necessary to understand the type of the activity as specified in the bpel4ws xml schema. The type for "Switch" is defined as follows:

<complexType name="tSwitch">
  <complexContent>
    <extension base="bpws:tActivity">
      <sequence>
        <element maxOccurs="unbounded" name="case">
          <complexType>
            <complexContent>
              <extension base="bpws:tActivityContainer">
                <attribute name="condition" type="bpws:tBoolean-expr" use="required"/>
              </extension>
            </complexContent>
          </complexType>
        </element>
        <element minOccurs="0" name="otherwise" type="bpws:tActivityContainer"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

This excerpt from the BPEL xml schema means that "Switch" is an activity type, i.e. it inherits from the tActivity type. It contains an unspecified number of elements each of them has a Boolean-expr attribute and a nested activity. After all elements it can also contain one element with a nested activity (the "otherwise" of a switch).

The interface for the "Switch" activity will be placed in the bexee.model.activity package and it will extend the Activity superclass. We want to be able to add unlimited number of case elements and retrieve an ordered list of those elements. Furthermore, it must be possible to set the otherwise activity.

public interface Switch extends Activity {
    public void addCase(BpelCase bpelCase);
    public List getCases();
    public void setOtherwise(Activity activity);
    public Activity getOtherwise();
}

It becomes obvious that it's further necessary to define a BpelCase interface. This interface will be placed in the bexee.model.elements package.

public interface BpelCase {
    public void setBooleanExpression(BooleanExpression booleanExpression);
    public BooleanExpression getBooleanExpression();
    public void setCaseActivity(Activity activity);
    public Activity getCaseActivity();
}

The used interface BooleanExpression has already been defined and a default implementation has been provided.

Implement the interface

The implementation of the "Switch" activity will be put in the bexee.model.activity package and might be implemented in the following way:

public class SwitchImpl extends AbstractActivity implements Switch {

    private List bpelCases;

    private Activity otherwiseActivity;

    public SwitchImpl(StandardAttributes standardAttributes) {
        super(standardAttributes);
        bpelCases = new ArrayList();
    }

    public void addCase(BpelCase bpelCase) {
        bpelCases.add(bpelCase);
    }

    public List getCases() {
        return bpelCases;
    }

    public void activity(Activity activity) {
        setOtherwise(activity);
    }    

    public void setOtherwise(Activity activity) {
        otherwiseActivity = activity;
    }

    public Activity getOtherwise() {
        return otherwiseActivity;
    }

    public void accept(ProcessController controller, ProcessInstance instance)
            throws Exception {
        controller.process(this, instance);
    }

    public void accept(BPELElementVisitor elementVisitor) {
        elementVisitor.visit(this);
    }
}

A speciality is the method activity(Activity). It is used by the Digester in order to add activities enclosed directly in this "Switch", in this case an otherwise activity. We need to add this method because we don't know the position of an activity in a BPEL document. After the creation of an Activity, we need to add it to the activitiy one level further up. Therefore we need to equip all elements and activities which might contain a nested activity with a uniformly named method, therefore we need this activity(Activity) method. You also will find this method in the implementation of the BpelCase interface.

Warning! It is necessary to implement the accept(ProcessController, ProcessInstance) and the accept(BPELElementVisitor) methods exactly in this way in order to enable processing the activity inside bexee (see document: Enhancing the Controller).

Now we need to implement the BpelCase interface:

public class BpelCaseImpl implements BpelCase {

    private BooleanExpression booleanExpression;

    private Activity activity;

    public BpelCaseImpl() {
        super();
    }

    public void setBooleanExpression(BooleanExpression booleanExpression) {
        this.booleanExpression = booleanExpression;
    }

    public BooleanExpression getBooleanExpression() {
        return booleanExpression;
    }

    public void activity(Activity activity) {
        setCaseActivity(activity);
    }

    public void setCaseActivity(Activity activity) {
        this.activity = activity;
    }

    public Activity getCaseActivity() {
        return activity;
    }
}

Complete the xml-oo mapping configuration file "bpel-rules.xml"

It will now be necessary to complete the xml-oo mapping configuration file "bpel-rules.xml". This configuration file is needed by the Digester xml-oo mapping tool from the Apache Jakarta Project [Digester] .

The configuration file has to be completed directly inside the root of the "bpel-rules.xml" document:

<digester-rules>
  ...
  ...
  <pattern value="*/switch">
    <factory-create-rule classname="bexee.model.xmltobpel.SwitchImplFactory"/>
    <pattern value="case">
      <factory-create-rule classname="bexee.model.xmltobpel.BpelCaseImplFactory"/>
    <set-next-rule methodname="addCase"/>
    </pattern>
    <set-next-rule methodname="activity"/>
  </pattern>
  ...
  ...
</digester-rules>

In normal case, we don't let Digester create the activities but delegate the creation to a specialized factory. This is done with the <factory-create-rule/>. We specify the fully qualified name of the factory to be used, i.e. bexee.model.xmltobpel.SwitchImplFactory. The implementation of this factory will be shown later. This factory will be responsible of creating SwitchImpl activities.

When the nested pattern "*/case" will be found, Digester will delegeate the creation of a BpelCaseImpl object to the correspondent factory. The newly created BpelCaseImpl object will be added to the list off cases of the formerly created SwitchImpl object.

After the completion of the "switch" element the newly created SwitchImpl object will be added to the enclosing activity with the already mentioned "activity(Activity)" method.

Implement an ObjectCreationFactory for the "Switch" activity

The task of the factory will be to look up the right instance of the BPELElementFactory, extract standard attributes from the attributes passed to it and then delegate the creation of a SwitchImpl to the BPELElementFactory.

public class SwitchImplFactory extends AbstractObjectCreationFactory {
    public Object createObject(Attributes attributes) throws Exception {
        return getElementFactory().createSwitch(
                getStandardAttributes(attributes));
    }
}

Similarily, it is neseccary to implement a factory for the creation of BpelCaseImpl objects:

public class BpelCaseImplFactory extends AbstractObjectCreationFactory {
    public Object createObject(Attributes attributes) throws Exception {
        String condition = attributes.getValue(CONDITION);
        return getElementFactory().createBpelCase(condition);
    }
}

All standard elements inside the newly created SwitchImpl object will be set implicitly by Digester. Digester will find the patterns corresponding to the standard elements, e.g. "*/target", create the elements and then set them in the enclosing activity:

  <pattern value="*/target">
    <object-create-rule classname="bexee.model.elements.impl.TargetImpl"/>
    <set-next-rule methodname="addTarget"/>
  </pattern>

Note!The methods to set the standard elements within activities are provided by the AbstractActivity

Complete the BPELElementFactory class

Finally, we have to complete the BPELElementFactory class with the method used by the SwitchImplFactory to create the SwitchImpl objects. The implementation of this method is fairly straightforward:

    public Object createSwitch(StandardAttributes standardAttributes) {
        Switch bpelSwitch = new SwitchImpl(standardAttributes); 
        return bpelSwitch;
    }

The creation of a BpelCaseImpl is also delegated to this factory, therefore the second addition:

    public BpelCase createBpelCase(String condition) {
        BpelCase bpelCase = new BpelCaseImpl();
        bpelCase.setBooleanExpression(new BooleanExpressionImpl(condition));
        return bpelCase;
    }