cancel
Showing results for 
Search instead for 
Did you mean: 

How to create a custom activity

sascha1
Champ in-the-making
Champ in-the-making
Hello,

is there any document or example how to create custom Activities?

I think i have to implement an ActivityBehavior.

First of all I would like to implement an Activity that is waiting for an JMSMessage. I began to implement a class that extends AbstractBpmnActivityBehavior and implements javax.jms.MessageListener at the moment if a message arrives simply leave() is called. The implementation should be improved later but now I need to know how to make Activiti use my custom Activity?  Can someone please sketch how to do this.

Sascha
18 REPLIES 18

jbarrez
Star Contributor
Star Contributor
It works the other way aroung:

You process will have a simple wait state (eg receiveTask)
You will have to put a JMS listener separate from your process. That JMS listener then calls the runtimeService.signal method, with the execution id of the execution waiting in that wait state.

Putting the listener in your service task is never going to work, as you need to go through the services to build up the context to actually execute the process logic.

sascha1
Champ in-the-making
Champ in-the-making
Thank you for that hint! It seems that this is working. But simple wait-state behavior doesn't suffice for me. I want to be able to receive JMS-Messages inside my process. Thus I need to transport Data via signal and then store it to a variable. I am worrying that I need to change Activiti-Engine implementation to realize this behavior. Is that true?

If this is true are the following steps correct:

Implement a method like signal(String instanceID, Object signalData) in RuntimeServiceImpl
Implement a TaskActivityBehavior similar to ReceiveTaskActivityBehavior
Implement parsing for my new activity (did not look at parsing in detail so far)


Sascha

jbarrez
Star Contributor
Star Contributor
I see two simple solutions for your problem:

- pass the data using runtimeService.setVariable() with your data before calling the signal method. Note that in this case you have two separate transactions instead of one

- Create your own ActivityBehaviour, and use SignallableActivityBehaviour instead of ActivityBehaviour (note that this is internal API)

Implement a method like signal(String instanceID, Object signalData) in RuntimeServiceImpl

The SignallableActivityBehaviour has exactly that method


Implement a TaskActivityBehavior similar to ReceiveTaskActivityBehavior

Correct. But this is nothing more than making your execute() method empy (the engine assumes wait state by default)

Implement parsing for my new activity (did not look at parsing in detail so far)

Not needed. You can simply use it as a serviceTask: <serviceTask activiti:class="MyImpl">

sascha1
Champ in-the-making
Champ in-the-making
Hi,

i would really prefer the second alternative. Therefore I implemented SignallableActivityBehavior.

In execute-method only a class for receiving the messages is instantiated for now the constructor has only one parameter the executionId. 
My implementation of the

public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception;

looks like this:

Logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> signal");
  receiver.destroy();
  leave(execution);

Now if I use my class as serviceTask implementation (<serviceTask activiti:class="MyImplementation' …) I always get an exception:

SCHWERWIEGEND: Error while closing command context
org.activiti.engine.ActivitiException: this activity doesn't accept signals
at org.activiti.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.signal(FlowNodeActivityBehavior.java:53)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.signal(ExecutionEntity.java:309)
at org.activiti.engine.impl.cmd.SignalCmd.execute(SignalCmd.java:50)
at org.activiti.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:42)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33)
at org.activiti.engine.impl.RuntimeServiceImpl.signal(RuntimeServiceImpl.java:135)
at de.uni_stuttgart.informatik.eventum.activiti.activities.JMSReceiver.onMessage(JMSReceiver.java:92)
at com.sun.messaging.jmq.jmsclient.MessageConsumerImpl.deliverAndAcknowledge(MessageConsumerImpl.java:358)
at com.sun.messaging.jmq.jmsclient.MessageConsumerImpl.onMessage(MessageConsumerImpl.java:287)
at com.sun.messaging.jmq.jmsclient.SessionReader.deliver(SessionReader.java:119)
at com.sun.messaging.jmq.jmsclient.ConsumerReader.run(ConsumerReader.java:192)
at java.lang.Thread.run(Thread.java:619)
org.activiti.engine.ActivitiException: this activity doesn't accept signals
at org.activiti.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.signal(FlowNodeActivityBehavior.java:53)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.signal(ExecutionEntity.java:309)
at org.activiti.engine.impl.cmd.SignalCmd.execute(SignalCmd.java:50)
at org.activiti.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:42)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33)
at org.activiti.engine.impl.RuntimeServiceImpl.signal(RuntimeServiceImpl.java:135)
at de.uni_stuttgart.informatik.eventum.activiti.activities.JMSReceiver.onMessage(JMSReceiver.java:92)
at com.sun.messaging.jmq.jmsclient.MessageConsumerImpl.deliverAndAcknowledge(MessageConsumerImpl.java:358)
at com.sun.messaging.jmq.jmsclient.MessageConsumerImpl.onMessage(MessageConsumerImpl.java:287)
at com.sun.messaging.jmq.jmsclient.SessionReader.deliver(SessionReader.java:119)
at com.sun.messaging.jmq.jmsclient.ConsumerReader.run(ConsumerReader.java:192)
at java.lang.Thread.run(Thread.java:619)

I did not try the other alternative because there are some questions.

How to instantiate the receiving-class and how to get the receiving-class know the correct executionID …

jbarrez
Star Contributor
Star Contributor
It appears you have not correctly override the signal method in FlowNodeActivityBehavior, as this is the implementation (check the exception you see)

public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
    // concrete activity behaviours that do accept signals should override this method;
    throw new ActivitiException("this activity doesn't accept signals");
  }

sascha1
Champ in-the-making
Champ in-the-making
My implementation looks like this so that cannot be the problem:


@Override
public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> signal");
    receiver.destroy();
    leave(execution);
}

Eclipse is also showing the symbol for implementing (implements org.activiti.engine.impl.pvm.delegate.SignallableActivityBehavior.signal).

Just before calling activityBehavior.signal(…) in ExecutionEntity the attribute activityBehavior references a ClassDelegate-object. The attribute className of this ClassDelegate-object is "de.uni_stuttgart.informatik.eventum.activiti.activities.JmsCatchMsgBehavior" which seems to be correct. Then not my implementation "de.uni_stuttgart.informatik.eventum.activiti.activities.JmsCatchMsgBehavior.signal(..)" is called but the implementation of FlowNodeActivityBehavior which throws the Exception.

EDIT: At all i don't understand why the implementation of FlowNodeActivityBehavior is called because I did directly implement SignallableActivityBehavior.

sascha1
Champ in-the-making
Champ in-the-making
I think now I know what is causing the problem can anybody confirm my guess?

Problem is that in method ExecutionEntity.signal(…) the ActivityBehavior-implementation is retrieved with:

SignallableActivityBehavior activityBehavior = (SignallableActivityBehavior) activity.getActivityBehavior();

activity.getActivityBehavior() returns a ClassDelegate-object.

Next activitiyBehavior.signal(…) is called but ClassDelegate extends AbstractActivityBehavior which extends FlowNodeActivityBehavior since my Class JMSCatchMsgBehavior does not extend FlowNodeActivityBehavior and override signal-method (it implements SignallableActivityBehavior) the default implementation of signal-method in FlowNodeActivityBehavior is called.

Am I right?

I tried to implement a method in ClassDelegate that looks like this:


@Override
public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
   if (activityBehaviorInstance == null) {
       activityBehaviorInstance = getActivityBehaviorInstance(execution);
   }
   ((SignallableActivityBehavior)activityBehaviorInstance).signal(execution, signalName, signalData);
}

This seems to work.

jbarrez
Star Contributor
Star Contributor
Your analysis is correct. The ClassDelegate doesnt support delegating to a wait state, it seems.

I added the following method to ClassDelegate. According to your findigs, that should do the trick.
Thanks for the detailed description. This was really valuable!

  // Signallable activity behavior
  public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
    if (activityBehaviorInstance == null) {
      activityBehaviorInstance = getActivityBehaviorInstance(execution);
    }
   
    if (activityBehaviorInstance instanceof SignallableActivityBehavior) {
      ((SignallableActivityBehavior) activityBehaviorInstance).signal(execution, signalName, signalData);
    } else {
      throw new ActivitiException("signal() can only be called on a " + SignallableActivityBehavior.class.getName() + " instance");
    }
  }

sascha1
Champ in-the-making
Champ in-the-making
Thank you for implementing this so fast! This is a great help so I don't have to merge every time ClassDelegate changes or every time I checkout a new workingcopy.

Is it possible to also implement an additional method in RuntimeService that look like this:

 
public void signal(String activityInstanceId, Object o){
   commandExecutor.execute(new SignalCmd(activityInstanceId, null, o));
}
Getting started

Tags


Find what you came for

We want to make your experience in Hyland Connect as valuable as possible, so we put together some helpful links.