gravitonian

Activiti Enterprise Developer Series - Custom REST Endpoints

Blog Post created by gravitonian Employee on Nov 18, 2016

Table of Contents                   

 

Introduction

The Activiti Enterprise application comes with an extensive REST API. However, sometimes it might be useful to add your own REST API, to for example support a domain specific workflow implementation. However, before starting any major REST API development, make sure that you are familiar with the REST API that is available out of the box, so you don’t implement stuff that is already supported. It might also at this point be a good idea to read up a bit on the Spring Web MVC framework, which is what the Activiti REST API is based on.

 

Here is a quick summary to get you going. Spring’s annotation based MVC framework simplifies the process of creating RESTful web services. The key difference between a traditional Spring MVC controller and the RESTful web service controller is the way the HTTP response body is created. While the traditional MVC controller relies on the View technology, the RESTful web service controller simply returns the object and the object data is written directly to the HTTP response as JSON or XML.

 

In the traditional workflow the ModelAndView object is forwarded from the controller to the client. Spring lets you return data directly from the @Controller, without looking for a view, using the @ResponseBody annotation on a method. When you use the @ResponseBody annotation on a method, Spring converts the return value and writes it to the HTTP response automatically. Each method in the Controller class must be annotated with @ResponseBody.

 

Beginning with Spring version 4.0, this process is simplified even further with the introduction of the @RestController annotation. It is a specialized version of the controller which is a convenience annotation that does nothing more than adding the @Controller and @ResponseBody annotations. By annotating the controller class with @RestController annotation, you no longer need to add @ResponseBody to all the methods that should map to requests. The @ResponseBody annotation is active by default. You can read more about it here.

Source Code

Source code for the Activiti Developer Series can be found here.

Prerequisites

Before starting with your REST API implementation make sure to set up a proper Activiti Extension project.

Implementing a Custom REST API

This section walks you through the process of creating a "Hello World" REST API with Spring.

 

We will build a REST endpoint that accepts the following HTTP GET requests:

 

http://localhost:8080/activiti-app/app/hello:

 

http://localhost:8080/activiti-app/app/hello?name=Martin:  

 

 

http://localhost:8080/activiti-app/app/hello/martin:

 

 

As you can see, we can make a GET request to the endpoint in three different ways, without any parameters with /hello, with a query parameter like /hello?name={name}, and with a path parameter like /hello/martin. The response to all these requests will be XML. We will see later on how to change it to also support JSON.

Implementing the REST Controller

In Spring’s approach to building web sites, HTTP requests are handled by a controller. You can easily identify these request classes by the @RestController annotation. In the following example, the HelloWorldController handles GET requests for /hello by returning XML.

 

package com.activiti.extension.rest;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/hello")
public class HelloWorldRestController {

  @RequestMapping(value = "/", method= RequestMethod.GET)
  public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                defaultValue="World") String name) {
      GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
      return msg;
  }

  @RequestMapping(value = "/{name}", method= RequestMethod.GET)
  public GreetingMessage sayHelloAgain(@PathVariable String name) {
      GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
      return msg;
  }
}

 

Note here in which Java package this class is located in: com.activiti.extension.rest. This will register this endpoint as available under /activiti-app/app URL path. It is then meant to be used in the application UI. And it will use cookie based authentication. So if we are logged into the Activiti Application when trying this REST endpoint, then there will be no need to authenticate.

 

The use of the @RestController annotation marks this class as a controller where every method returns a domain object/POJO instead of a View. All URLs will start with /hello for this controller. We are responding with a domain object converted into format understood by the consumers. In our case, due to the Jackson library being included in the class path, the GreetingMessage object will be converted into XML format (or into JSON as we will see later on).

 

The @RequestMapping annotation specifies the exact request that will map to the class and method, for example /app/hello/martin will map to the class and the sayHelloAgain method. The request method (i.e. GET, POST etc) has no default value so we need to set it to something, HTTP GET in this case.

 

For the response to be converted into XML we also provide the following data transfer object class:

 

package com.activiti.extension.rest;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;

@XmlRootElement(name = "greeting")
public class GreetingMessage implements Serializable {
  String name;
  String text;

  public GreetingMessage() {}

  public GreetingMessage(String name, String text) {
      this.name = name;
      this.text = text;
  }

  @XmlElement(name = "name")
  public String getName() {
      return name;
  }

  @XmlElement(name = "text")
  public String getText() {
      return text;
  }
}

 

This class uses the JAXB annotations to tell Spring MVC what XML elements to use in the responses.

Making the REST endpoint part of Public API

So far we created a REST endpoint that is available in the Activiti Application context with cookie based authentication. This is not ideal for the public REST API. We would like to instead have it available under /activiti-app/api and supporting basic authentication.

 

To make this happen we need to do just a few changes, here is the updated controller:

 

package com.activiti.extension.api;

import com.activiti.extension.rest.GreetingMessage;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/enterprise")
public class HelloWorldRestController {

  @RequestMapping(value = "/hello", method= RequestMethod.GET)
  public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                defaultValue="World") String name) {
      GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
      return msg;
  }

  @RequestMapping(value = "/hello/{name}", method= RequestMethod.GET)
  public GreetingMessage sayHelloAgain(@PathVariable String name) {
      GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
      return msg;
  }
}

 

The class has been moved into the com.activiti.extension.api package. This package is scanned automatically by Activiti and any components are instantiated as custom REST endpoints in the public REST API, protected by basic authentication. Note that the endpoint needs to have /enterprise as first element in the URL, as this is configured in the SecurityConfiguration to be protected with basic authentication (more specifically, the api/enterprise/* path).

 

Now, with basic auth available, it is easy to use other clients than the browser, such as Postman, curl etc. We can also in an easy way request JSON responses by setting the Accept header.

 

Let’s look at a couple of calls using Postman. First we need to set up the Authorization:

 

 

Then configure the Accept header to application/json to see that we can get responses in JSON format:

 

 

Now, click Send button with the http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin GET URL specified as above:

 

 

We can see the JSON response. To have responses in XML, just remove the Accept header:

 

 

Using curl is as easy:

 

$ curl -u admin@app.activiti.com:admin http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |xmlstarlet fo

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                Dload  Upload   Total   Spent    Left  Speed

100   121    0   121    0     0   7330      0 --:--:-- --:--:-- --:--:--  7562

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<greeting>

 <name>Martin</name>

 <text>Hello Martin!</text>

</greeting>

 

Here we are piping the XML response into the xmlstarlet tool for formatting. To get JSON responses, do something like this:

 

$ curl -u admin@app.activiti.com:admin -H "Accept: application/json" http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |jq '.'

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                Dload  Upload   Total   Spent    Left  Speed

100    40    0    40    0     0   3587      0 --:--:-- --:--:-- --:--:--  3636

{

 "name": "Martin",

 "text": "Hello Martin!"

}

 

In this case we are using the jq command line tool for formatting of the JSON response.

Injecting Spring Beans into the REST Controller

So far we have used a REST controller class that was not dependent on any other component. In a real scenario we are likely to have to wire in custom beans and out-of-the-box beans.

 

Take the following custom Spring Bean:

 

package com.activiti.extension.bean.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class HelloWorldService {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

  public String greeting() {
      logger.info("Custom Service method greeting() was called");
    
      return "Hello World from Service!";
  }
}

 

It’s defined in a sub-package to the com.activiti.extension.bean package so it will be picked up by Activiti during scanning and registered as a Spring bean with the helloWorldService name.

 

We can now wire this bean into our REST Controller as follows:

 

package com.activiti.extension.api;

import com.activiti.domain.idm.User;
import com.activiti.extension.bean.service.HelloWorldService;
import com.activiti.extension.rest.GreetingMessage;
import com.activiti.service.api.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/enterprise")
public class HelloWorldRestController {

  @Autowired
  HelloWorldService helloWorldService;

  @Autowired
  UserService userService;

  @RequestMapping(value = "/hello", method= RequestMethod.GET)
  public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                defaultValue="World") String name) {
      long userId = 1; // Admin
      User user = userService.findUser(userId);
      String username = user.getFirstName() + " " + user.getLastName();

      GreetingMessage msg = new GreetingMessage(name, "Hello " + name +
                ", service message: " + helloWorldService.greeting() +
                ", userId = 1 = " + username);

      return msg;
  }

  @RequestMapping(value = "/hello/{name}", method= RequestMethod.GET)
  public GreetingMessage sayHelloAgain(@PathVariable String name) {
      GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");

      return msg;
  }
}

 

As you can see, we also wire in one of the out-of-the-box services called UserService, and use it to get the first name and last name of the Administrator user.

 

Making a call via curl now shows the following response, that have involved to external beans:

 

$ curl -u admin@app.activiti.com:admin -H "Accept: application/json" http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |jq '.'

 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                Dload  Upload   Total   Spent    Left  Speed

100   116    0   116    0     0   1509      0 --:--:-- --:--:-- --:--:--  1526

{

 "name": "Martin",

 "text": "Hello Martin, service message: Hello World from Service!, userId = 1 = null Administrator"

}

 

The administrator user has no first name, hence it is null.

Calling a REST endpoint from a Process

Closely related to implementing a custom REST API is calling out to REST endpoints from a process. This can be done with the out-of-the-box REST call task:

 

 

Read more about it here and watch this video for a detailed walkthrough. The custom REST call task does not have to be configured to call an external web service, such as a weather service. It can also call back into the Activiti public REST API. This means it could actually call the custom REST API we created in this article.

Outcomes