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=="user1"}" />
</transition>
<transition g="-49,-18" name="to jms" to="jms">
<condition expr="#{channel=="user2"}" />
</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>