REST Activity Feature

#1

I am looking at an implementation where the process models are built and deployed by non-developers. In order to interact with other systems I can provide restful endpoints for the process (and users) to push/pull data. However, currently there is no REST activity.

Having non-developers interact with external systems in their processes can greatly expand the user base and adoption. Looking at the Road Map, I did not see this feature listed. Can this be added?

1 Like
#2

I’m not sure if I understand it correctly. Flowable does have a REST API allowing you to interact with the Flowable Engine in a lot of ways. The REST API is documented in the user guide.

Best regards,

Tijs

1 Like
#3

This is different from the api included. The current engine REST API allows external systems to initiate a call and interact with Flowable.

In this case I want Flowable to be the one initiating a call to other external systems via REST. I can do this today by implementing a service task. However, I want a non-developer to be able to do this from the modeler application without any code required (except the rest url and query config). Thus, if there was a REST activity that the user can drag onto the diagram, non-developers can then query data or post data from/to external systems without the need to write code.

Also, I am interested in helping create this feature but want to review with the community before working on it.

1 Like
#4

Hi, am also looking for something like this. Did you get an answer to this question ? Also, if possible, please share share an example of how to do it using a ServiceTask. I Activiti Enterprise there was something called REST-ServiceTask. Do we have something like it in Flowable ?

#5

Hi,

A Rest service task would be a nice feature to provide in Flowable, but it’s not implemented yet. We do have it on our todo list to provide a REST service task as an out-of-the-box feature.

Best regards,

Tijs

#6

Thanks for answering.

#7

That wold be a very nice feature, @tijs please keep an eye on me, for help to code this feature.

Best regards.

#8

@Sibok666 Help with this feature would be great. Let me know what you need to get started and how we can help you to develop the feature. You could have a look at the DmnActivityBehavior which is a special service task to execute a decision table on the DMN engine. I would think that for the REST service task also a special service task type could be used.

Best regards,

Tijs

#9

Look at comments for issue #165 and
examples of EL calls in them

You can implement RestCallIntf using some http library. I’ve done it with
Javalite HTTP. I think this is what original
poster is looking for

Thanks

#10

Thanks I will try to implement the code.

#11

Hi @tijs I was seeing the DmnActivityBehavior and I saw other class called WebServiceActivityBehavior, I see that this class is designed to be used with SOAP web services, maybe I can reuse that.

I was thinking about the aproach to the feature and I think if we have a new bpmn elemnt called “Web service task” in the modeler app and this element has the next properties:

type of ws: soap or rest
url:example.com/WebService?wsdl
data input: variable to send request in {json:data} i
data output: variable to store response in format {json:data}

if data input and output are empty we assume that the ws dont receive or response any parameters

What do you think?

I will look at the suggestion of mgrouch, because he already proposse some code.

Tomorrow I will start to add the element to the modeler.

So I will add the element in the stencilset.json

If you have any diagram to know how the ui aps are designed it will be very helpful, because I dont know anything about angular js.

Best regards.

#12

Hi,

I would separate the REST service task from the Web service task. The web service task isn’t available in the Modeler because it’s not easy to make a user friendly wizard where you can import the wsdl, select the service you want to inoke and fill-in the wsdl input parameters for this service and map the output parameters to process variables.

For the REST service task it’s probably a bit easier. You could think of a GET call where you can set the request parameters or a POST request where you can define the JSON body using expressions etc.

I think it’s best to start with the Java implementation and focus on Engine unit tests first. Then we can have a look at the Modeler etc after that.

Let me know what you think.

Best regards,

Tijs

#13

Yes youre right, the wsdl import create a lot of object to be used.

Well for now I start to work on the REST.

I will do what youre telling, start from the engine.

Soon I come back here an update the progress.

Best regards.

#14

Hi @tijs until now I have created a class called RestCallActivityBehavior
and added the methods:

public abstract RestCallActivityBehavior createRestCallActivityBehavior(ServiceTask serviceTask);

public abstract RestCallActivityBehavior createRestCallActivityBehavior(SendTask sendTask);

Added the RestCall in the ServiceTaskParseHandler class

protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) {

    // Email, Mule and Shell service tasks
    if (StringUtils.isNotEmpty(serviceTask.getType())) {

        if (serviceTask.getType().equalsIgnoreCase("mail")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createMailActivityBehavior(serviceTask));

        } else if (serviceTask.getType().equalsIgnoreCase("mule")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createMuleActivityBehavior(serviceTask));

        } else if (serviceTask.getType().equalsIgnoreCase("camel")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createCamelActivityBehavior(serviceTask));

        } else if (serviceTask.getType().equalsIgnoreCase("shell")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createShellActivityBehavior(serviceTask));

        } else if (serviceTask.getType().equalsIgnoreCase("dmn")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createDmnActivityBehavior(serviceTask));// below else if added to rest call featur
        }else if (serviceTask.getType().equalsIgnoreCase("rest")) {
            serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createRestCallActivityBehavior(serviceTask));    
        } else {
            logger.warn("Invalid service task type: '{}'  for service task {}", serviceTask.getType(), serviceTask.getId());
        }

        // activiti:class
    } else if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(serviceTask.getImplementationType())) {

        serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createClassDelegateServiceTask(serviceTask));

        // activiti:delegateExpression
    } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(serviceTask.getImplementationType())) {

        serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createServiceTaskDelegateExpressionActivityBehavior(serviceTask));

        // activiti:expression
    } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(serviceTask.getImplementationType())) {

        serviceTask.setBehavior(bpmnParse.getActivityBehaviorFactory().createServiceTaskExpressionActivityBehavior(serviceTask));

        // Webservice
    } else if (ImplementationType.IMPLEMENTATION_TYPE_WEBSERVICE.equalsIgnoreCase(serviceTask.getImplementationType()) && StringUtils.isNotEmpty(serviceTask.getOperationRef())) {

        WebServiceActivityBehavior webServiceActivityBehavior = bpmnParse.getActivityBehaviorFactory().createWebServiceActivityBehavior(serviceTask);
        serviceTask.setBehavior(webServiceActivityBehavior);

    } else {
        logger.warn("One of the attributes 'class', 'delegateExpression', 'type', 'operation', or 'expression' is mandatory on serviceTask {}", serviceTask.getId());
    }

}

}

I have compiled the engine, everything works fine.

Now whats next? Can I submit a PR for get your comments about the code changes that i have made?

Best regards.

#15

Hi,

Ok great. Yes you can create a PR or if you want to do a code review first, you could share the link the Github branch where the changed code is available and we’ll have a look. Just choose the path that you prefer.

Best regards,

Tijs

#16

Well, @tijs I have just added the elements to the classes, but there is no funcionality yet, so I think its better to do a code review in my github repo, here are the classe I have modified:

My repo https://github.com/sibok666/flowable-engine

In the class RestCallActivityBehavior I only get the processDefinition I think I have to create the service now, any clue on how to do this?

UPDATE:

I already added the ResCallService and RestCallServiceImple to the engineproject.

The RestCallService is a copy of the task service beacuse I think some methods will be useful, and in here I will create the methods for execute the rest call with input parameters and without parameters.

Any suggestions?

Best regards.

#17

Hi,

Yes this looks fine. I don’t think the RestCallService should be a copy of the TaskService, because the functionality is really different and TaskService is an external interface for user tasks in the Flowable Engine. The RestCallService is more an internal implementation that provides methods to invoke REST services.

Best regards,

Tijs

#18

Hi @tijs I have already coded a general implementation of the RestCallService already is commited in my repo, as you say I only implemented the get based call to a rest service, here is the code:

@Override
public String callRestService(String protocol,String host, Integer port, String restPath, Map<String,String> inputParameters){
	
	String result="";
	URIBuilder builder = new URIBuilder();
	
	if(protocol.equals("")){
		protocol="https";	
	}else{
		builder.setScheme(protocol);
	}
	
	builder.setHost(host);
	
	if(port==0){
	}else{
		builder.setPort(port);
	} 
	builder.setPath(restPath);
	
	
	for (Map.Entry<String, String> entry : inputParameters.entrySet())
	{
		builder.addParameter(entry.getKey(), entry.getValue());
	}

	try {
		
		HttpClient httpclient = HttpClients.createDefault();
		HttpGet httpget = new HttpGet(builder.build());
		//Execute and get the response.
		HttpResponse response = httpclient.execute(httpget);
		HttpEntity entity = response.getEntity();

		if (entity != null) {
		    InputStream instream = entity.getContent();
		    try {
		    	result = IOUtils.toString(instream, "UTF-8");
		    } finally {
		        instream.close();
		    }
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
	
	return result;
}

On the class RestActicityTaskBehavior im hardcoding the parameter inputs to the RestCallService

    RestCallService restCallService=processEngineConfiguration.getRestCallService();
    // the rest call service receives the protocol, host name, port, path of rest service and input parameters like this:
    String restJsonResponse=restCallService.callRestService("https","reqres.in",0,"/api/users",inputParameters);

I haven write any test, because I dont know how to doit, but I already test the implementation inside a main methond and with the harcoded params, works fine.

Now I think that its time to make the part that will be getting the variables stored from the ui modeler, thats right?

Now im wondering whats next?.

Thanks.

Best regards.

#19

Hi,

Ok great. No I think the next step is to make the harcoded params flexible with using field properties on the REST service task. The field property elements needs to be defined like the Mule task (http://www.flowable.org/docs/userguide/index.html#bpmnMuleTask). When this is done, I can help to write some unit tests and maybe you can extend them a bit. Adding the REST task to the Modeler would be the final step to my opinion, when the core engine work is full ready and tested.

Best regards,

Tijs

#20

Hi,

I see standard rest task would be a nice addition.
We have a set of field properties, we use for a similar rest task.
Please consider, if this is a good enough specification.

// Request url
private Expression reqUrl;
// Request method (GET,POST,PUT etc)
private Expression reqMethod;
// Request headers, comma separated KV pairs ( Accept: application/json, Authorization: Basic) (Optional)
private Expression reqHeaders;
// Timeout in seconds for the request (Optional)
private Expression reqTimeout;
// Number of retries of the http client, this is not the task job retry count (Optional)
private Expression reqRetries;
// Request signature in header/cookies (Optional)
private Expression reqSign;
// Request Authenticator object delegate expression, for example OAuth etc (Optional)
private Expression reqAuth;
// Request body expression (Optional)
private Expression reqBody;
// Response headers(by default this is added to execution variables as taskId.respHeaders)
// To override we can mention the variable name as string value (Optional)
private Expression respHeaders;
// Response headers(by default this is added to execution variables as taskId.respCode)
// To override we can mention the variable name as string value (Optional)
private Expression respCode;
// Response headers(by default this is added to execution variables as taskId.respBody)
// To override we can mention the variable name as string value (Optional)
private Expression respBody;
// Response converter delegate expression, like say xml to json, json to xml (Optional)
private Expression respConverter;
// Response filter delegate expression, sometimes we may need to parse response, extract fields (Optional)
private Expression respFilter;
// Response error handler delegate expression( for example to specify HTTP status codes to ignore or retry
// Also, can be a list of KV pairs ( retry: [404, 500], ignore: [400]) (Optional)
private Expression errHandler;

Thanks,
Harsha