Thursday 5 September 2013

Web Service implementation with JAX-WS 2.1 and jBPM workflow engine

This entry shows how to construct a Web Service by using the JAX-WS specification. The web service proposed is a standalone SOAP- based service that implements a workflow deployed in the platform JBpm. This example uses an embedded workflow engine and this is the piece that will be invoked by a client application to decide if a message is persisted in the database via EJB or is sent to a JMS queue. These steps will be explained in following entries. In this article, only the Web service creation and the jbpm implementation are contemplated.

Firstly I would like to show how to define a workflow in jBpm.

In this occasion, the version chosen is the jBpm 4.1. The persistence of the processes definitions and instances are persisted in memory using HSQL database. I considered deploying it as a workflow application in JBoss 7.1.1.Final but several problems arose regarding Hibernate version. jBPm uses the Hibernate 3 version and JBoss the Hibernate 4.  I could work it out but I’d rather use the workflow engine just like an application-embedded component.

Here we go.

FUNCIONALITY: There is a Web Services deployed out of the JBOss AS which execute a workflow. This workflow is embedded in the WS itself and his main goal is deciding whether a message is persisted in a database by way of EJB or , on the other hand, the message is sent to a JMS-based queue. This queue is deployed and managed by the jBoss AS server.

This example only shows how the Web Service is developed and deployed and how the jBpm process is developed and deployed. It is important to highlight the fact that the IoC pattern is implemented by using Spring Framework 2.5. The web service has a little authentication provider.

Here I resume the technologies used:

JAX-WS for implementing the Web Service.

Spring Framework for IoC pattern implementation

jBpm for implementing the process deciding the channel to be used to send the message.

This Web Service will be invoked by a JSF application that will be described in a future article.

Maven is used for developing this example.

 

Web Service Creation

This is the code of the Web Service based on JAX-WS

An interface is defined:

 

package com.farewell.ws;

import javax.jws.WebMethod;

import javax.jws.WebService;

import javax.jws.soap.SOAPBinding;

import javax.jws.soap.SOAPBinding.Style;

 

@WebService

@SOAPBinding(style = Style.DOCUMENT)

public interface FarewellManager {

      @WebMethod int giveComm(String pattern);

}

 

The class implemeting the inteface

 

import javax.jws.WebService;

import javax.jws.WebMethod;

import javax.inject.*;

import javax.annotation.*;

import com.farewell.workflow.*;

import com.farewell.ws.monitoring.FarewellMonitorContainer;

import com.farewell.ws.publisher.FarewellManagerPublisher;

import javax.xml.ws.WebServiceContext;

import javax.xml.ws.handler.MessageContext;

 

import org.apache.log4j.*;

//Service Implementation

@WebService(endpointInterface = "com.farewell.ws.FarewellManager")

public class FarewellManagerImpl extends FarewellBaseManager implements FarewellManager {

      private final int JMS=1;

      private final int EJB=2;

      private final int ERROR=-1;

      static Logger logger = Logger.getLogger(FarewellManagerImpl.class.getName());

     

      @Resource WebServiceContext wcontext;

     

      private WorkflowManager manager;

     

     

      public FarewellManagerImpl ()

      {

            FarewellMonitorContainer.setCondition(FarewellMonitorContainer.PERMIT_BOTH);

      }

      @WebMethod ( exclude=true)

      public void setWorkflowManager(WorkflowManager workflow)

      {

            logger.info("Web Service is instantiating the worflow engine");

           

            this.manager=workflow;

     

      }

      public int giveComm(String pattern) {

            if (this.isUserActive()){

                 

                  if (this.authenticate(user, password)==null)

                  {

                        return -1;

                  }else{

                  String result=manager.executeWorkflow(user);

                                          return checkRestriction(result);  

                  }

                 

            } else{

                  return this.ERROR;

            }

           

      }

      private int checkRestriction(String text){

            logger.info("Checking constrsit");

            if (FarewellMonitorContainer

                        .getCondition()

                        .compareTo(FarewellMonitorContainer.PERMIT_BOTH)==0){

                  if (text.compareTo("ejb")==0){

                        return this.EJB;

                  }

                  if (text.compareTo("jms")==0){

                        return this.JMS;

                  }

                 

            }

            if (FarewellMonitorContainer

                        .getCondition()

                        .compareTo(FarewellMonitorContainer.PERMIT_EJB_ONLY)==0){

                  return this.EJB;

            }

            if (FarewellMonitorContainer

                        .getCondition()

                        .compareTo(FarewellMonitorContainer.PERMIT_JMS_ONLY)==0){

                  return this.JMS;

            }

            return this.ERROR;

      }

}

There is a method which is excluded from the WSDL definition. The reason is that it is needed to inject the Workflow class implementing the workflow functionality via Spring.

To generate the rest of classes needed to deploy the Web Service, the following command is required.

wsimport -keep http://localhost:9980/service/FarewellWS?wsdl

 

This command generates extra classes that must be included in the project.

To launch as an standalone the Web Service, in the context of Spring, these two beans must be included:

<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">

      <property name="baseAddress" value="http://localhost:9980/service/FarewellWS"/>

      </bean>

     

<bean id="farewellManagerImpl" class="com.farewell.ws.FarewellManagerImpl">

      <property name="workflowManager"><ref bean="farewellWorkflowManager"/></property>

      </bean>

 

This chuck of application context creates the Service deploying the EndPoint in a standalone way. You can see that the “workflow manager” is injected.

To start the application context, there is a class that does this action:

package com.farewell.ws.publisher;

import com.farewell.ws.*;

 

import javax.xml.ws.Endpoint;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FarewellManagerPublisher {

 

      public static void main(String[] args) {

           

            //Endpoint.publish("http://localhost:9980/service/FarewellWS", new FarewellManagerImpl());

            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

 

     

      }

 

}

 

The invocation of the service from a “console” client application is as follow:

package com.client;

import java.net.URL;

import javax.xml.namespace.QName;

import javax.xml.ws.Service;

import com.farewell.ws.*;

import java.util.Collections;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 import javax.xml.ws.BindingProvider;

import javax.xml.ws.handler.MessageContext;

 

public class Cliente1{

 

      public static void main(String[] args) throws Exception {

 

      URL url = new URL("http://localhost:9980/service/FarewellWS?wsdl");

        QName qname = new QName("http://ws.farewell.com/", "FarewellManagerImplService");

 

        Service service = Service.create(url, qname);

 

        FarewellManager manager = service.getPort(FarewellManager.class);

       

        Map<String, Object> req_ctx = ((BindingProvider)manager).getRequestContext();

        req_ctx.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:9980/service/FarewellWS?wsdl");

 

        Map<String, List<String>> headers = new HashMap<String, List<String>>();

        headers.put("Username", Collections.singletonList("user4"));

        headers.put("Password", Collections.singletonList("puser1"));

        req_ctx.put(MessageContext.HTTP_REQUEST_HEADERS, headers);

 

 

    }

 

}

As you can see, the user and password are conveyed to authenticate the operation. The authentication is quite basic but it is a good approach to show how it works. All about login is delegated to the parent class

package com.farewell.ws;

 

import javax.annotation.Resource;

import javax.xml.ws.WebServiceContext;

import javax.xml.ws.handler.*;

 

import org.apache.log4j.Logger;

 

import com.farewell.ws.provider.FarewellAuthorization;

import com.farewell.ws.provider.FarewellAuthorizationProvider;

import com.farewell.ws.provider.FarewellUserInfo;

 

import java.util.*;

 

public class FarewellBaseManager {

      static Logger logger = Logger.getLogger(FarewellBaseManager.class.getName());

      @Resource

      protected  WebServiceContext wcontext;

      protected String user;

      protected String password;

      protected  boolean isUserActive(){

            logger.info("Service is checking if user is provided");

            MessageContext mctx = wcontext.getMessageContext();

             

            //get detail from request headers

              Map http_headers = (Map) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);

              List users = (List) http_headers.get("Username");

              List passwords = (List) http_headers.get("Password");

              if (users==null || passwords==null) return false;

             

              user= users.get(0).toString();

              password=passwords.get(0).toString();

              System.out.println("Usuario" + user + " password " + password);

       

            return true;

      }

      protected FarewellUserInfo authenticate (String user, String password)

      {

            logger.info("User is being authenticated");

            FarewellAuthorization provider=new FarewellAuthorizationProvider();

            return provider.authenticate(user, password);

           

           

      }

     

     

}

The rest of the classes involved in the authentication process are the following

package com.farewell.ws.provider;

 

public interface FarewellAuthorization {

      public FarewellUserInfo authenticate(String user, String password);

}

 

package com.farewell.ws.provider;

 

public class FarewellAuthorizationProvider implements FarewellAuthorization{

 

      public FarewellUserInfo authenticate(String user, String password) {

           

            if (((user.compareTo("user1")==0) && (password.compareTo("puser1")==0))

            || ((user.compareTo("user2")==0) && (password.compareTo("puser2")==0))){

                  return new FarewellUserInfo(user, password);

            }

           

            return null;

      }

     

}

package com.farewell.ws.provider;

 

public class FarewellUserInfo {

     

      private String user;

      private String password;

      public FarewellUserInfo(String user, String password){

            this.user=user;

            this.password=password;

      }

      public String getUser() {

            return user;

      }

      public String getPassword() {

            return password;

      }

     

}

 

This Web Service provides a JMX MBean for tunning it properly. We can see it in other article.

WORKFLOW Creation

Creating a process and executing it is quite easy with jBPM once the development environment is well conformed. I assume this task is already done properly.

The first thing to do is to write the jdpl file that defines the process to be executed.

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

 

<process name="process1" xmlns="http://jbpm.org/4.0/jpdl">

   <start g="256,25,48,48" name="start1">

      <transition g="-68,-18" name="ichannel" to="exclusive1"/>

   </start>

   <decision g="257,95,48,48" name="exclusive1">

    

      <transition g="-49,-18" name="to ejb" to="ejb">

            <condition expr="#{channel==&quot;user1&quot;}" />

      </transition>

      <transition g="-49,-18" name="to jms" to="jms">

            <condition expr="#{channel==&quot;user2&quot;}" />

      </transition>

     

   </decision>

   <state g="162,173,92,52" name="ejb">

      <transition g="-42,-18" name="to end1" to="end1"/>

   </state>

   <state g="326,169,92,52" name="jms">

      <transition g="-42,-18" name="to end1" to="end1"/>

   </state>

   <end g="270,256,48,48" name="end1"/>

</process>

 

As you can see, the process takes a decision depending on the channel variable value (user 1 or user2). If the user that request the action to the Web Service is “user1”, the process indicates that the message must be persisted via EJB. If the user is “user2”, the communication will be via JMS. Graphically,  the process is the following

There are a class that manages  the process and launches the process instances. This class is injected (IoC) to the Web Service via Spring context.

package com.farewell.workflow;

import org.jbpm.api.ProcessInstance;

import java.util.HashMap;

import java.util.Map;

import org.jbpm.api.Configuration;

import org.jbpm.api.ExecutionService;

import org.jbpm.api.HistoryService;

import org.jbpm.api.ManagementService;

import org.jbpm.api.ProcessEngine;

import org.jbpm.api.RepositoryService;

import org.jbpm.api.TaskService;

import org.jbpm.api.Execution;

public class WorkFlowManagerImpl implements WorkflowManager {

      private String deploymentId;

      private ProcessEngine processEngine;

      private RepositoryService repositoryService;

      private ExecutionService executionService;

      public WorkFlowManagerImpl(){

           

            processEngine = new Configuration()

            .buildProcessEngine();

            repositoryService = processEngine.getRepositoryService();

           executionService = processEngine.getExecutionService();

         

          // Deploying a process

           deploymentId = repositoryService.createDeployment()

              .addResourceFromClasspath("process1.jpdl.xml")

              .deploy();

 

           

           

           

           

      }

      public String executeWorkflow(String text) {

           

            String result;

            Map<String, Object> variables = new HashMap<String, Object>();

         

            variables.put("channel", text);

          ProcessInstance processInstance = executionService.startProcessInstanceByKey("process1", variables);

          if (processInstance.isActive("ejb")){

            result="ejb";

            executionService.endProcessInstance(processInstance.getId(), "ejb");

          } else

          {

            result="jms";

             executionService.endProcessInstance(processInstance.getId(), "jms");

          }

            return result;

      }

 

}

 

The maven files for the Web Service project is the following

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.farewell.webservice</groupId>

  <artifactId>FarewellWebService</artifactId>

  <version>0.0.1-SNAPSHOT</version>

 

      <dependencies>

            <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring</artifactId>

      <version>2.5.6</version>

</dependency>

           

            <dependency>

                  <groupId>javax.inject</groupId>

                  <artifactId>javax.inject</artifactId>

                  <version>1</version>

            </dependency>

            <dependency>

                  <groupId>com.farewell.workflow</groupId>

                  <artifactId>FareWellWorkflowManager</artifactId>

                  <version>0.0.1-SNAPSHOT</version>

            </dependency>

 

      </dependencies>

      <build>

            <plugins>

                  <plugin>

                          <groupId>org.apache.maven.plugins</groupId>

                          <artifactId>maven-jar-plugin</artifactId>

                          <version>2.4</version>

                          <configuration>

                             <archive>

                                   <manifest>

                                         <mainClass>com.farewell.ws.publisher.FarewellManagerPublisher</mainClass>

                                   </manifest>

                             </archive>

                          </configuration>

                  </plugin>

     

           

            </plugins>

     

     

      </build>

</project>

 

The maven file for the WorkflowManager project is the following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.farewell.workflow</groupId>

  <artifactId>FareWellWorkflowManager</artifactId>

  <version>0.0.1-SNAPSHOT</version>

 

 

<dependencies>

     

      <dependency>

      <groupId>javax.inject</groupId>

      <artifactId>javax.inject</artifactId>

      <version>1</version>

</dependency>

      <dependency>

            <groupId>org.jbpm.jbpm4</groupId>

            <artifactId>jbpm-jpdl</artifactId>

            <version>4.1</version>

      </dependency>

    <dependency>

      <groupId>hsqldb</groupId>

      <artifactId>hsqldb</artifactId>

      <version>1.8.0.7</version>

    </dependency>

  </dependencies>

  </project>

No comments:

Post a Comment