Hi there!
In this post, I will try to explain how to write a web service using Apache Camel CXFRS component.
It will be a simple web service that will accept a GET and a POST request and returns a plain text output for the GET request and a JSON object for the POST request.
1. Start by creating a simple Maven Project , Choose the archetype of webapp. The final project structure will appear as follows.
2. Include the below dependencies in the pom.xml file.
<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>ExampleRestAPI</groupId>
<artifactId>com.restapi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Rest Api Camel </name>
<description>Rest API Camel project</description>
<properties>
<camel-web>2.5.0</camel-web>
<camel-version>2.12.0</camel-version>
<xbean-spring-version>3.5</xbean-spring-version>
</properties>
<dependencies>
<!-- Spring + Camel jars -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>${xbean-spring-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jms</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-stream</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf-transport</artifactId>
<version>${camel-version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>2.7.6</version>
</dependency>
<!-- Other jars (logging, io etc) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>
We have used Apache Camel cxf jars , SLF4J jars , Apache common IO , Spring jars. Spring jars are used to load the Application Contexts automatically.
2. After adding the dependencies, start creating the required resources. First we will start creating the servlet which listens to the input html requests. The contents of the WEB-INF/web.xml file will look as below.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:web="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>test</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Here we have added the applicationContext.xml as the contextConfigLocation and the listener class.
In the applicationContext.xml file, we have to set the route for all implementation classes.
3. Add the below configurations to the WEB-INF/applicationContext.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for additional
information regarding copyright ownership. The ASF licenses this file to
You under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of
the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://camel.apache.org/schema/spring" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
">
<bean id="contextApplicationContextProvider" class="conf.ApplicationCtxProvider"></bean>
<camelContext xmlns="http://camel.apache.org/schema/spring"
id="Demo-Camel">
<!-- All the Routes should be created within below Package -->
<camel:package>camel.route</camel:package>
</camelContext>
</beans>
In here we have added the route for all implementation classes under camelContext/camel:package as camel.route which has the DemoRouteBuilder class that contains the REST endpoint URI.
4. Add the applicationContexts classes. These classes are loaded during spring initializing.
ApplicationCtx.java
package conf;
import org.springframework.context.ApplicationContext;
public class ApplicationCtx {
private static ApplicationContext ctx;
// Injected from the class "ApplicationCtxProvider" which is automatically loaded during Spring-Initialization.
public static void setApplicationContext(
ApplicationContext applicationContext) {
ctx = applicationContext;
}
// Get access to the Spring ApplicationContext from everywhere in your Application.
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
ApplicationCtxProvider.java
package conf;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationCtxProvider implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
ApplicationCtx.setApplicationContext(ctx);
}
}
5. Add the camel router implementation to DemoRouteBuilder.java file. This class handles all the REST calls to the REST service. It will run automatically when the server starts since the location is mentioned in the applicationContect.xml file as camel.route. It consist of cxfrs endpoint URI and resourceClasses which points to the request service classes in camel.rs.
DemoRouteBuilder.java
package camel.route;
import org.apache.camel.builder.RouteBuilder;
import camel.process.MappingProcessor;
import rs.RequestServiceImpl;
public class DemoRouteBuilder extends RouteBuilder {
private static final String REST_ENDPOINT_URI = "cxfrs://http://localhost:9003/rest?resourceClasses=rs.RequestServiceImpl";
@Override
public void configure() {
errorHandler(noErrorHandler());
from(REST_ENDPOINT_URI)
.routeId("RestFulService")
.process(new MappingProcessor(new RequestServiceImpl()));
}
}
6. Content of camel process is implemented by MappingProcessor.java file.
package camel.process;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.cxf.common.message.CxfConstants;
public class MappingProcessor implements Processor {
private Class<?> beanClass;
private Object instance;
public MappingProcessor(Object obj) {
beanClass = obj.getClass();
instance = obj;
}
public void process(Exchange exchange) throws Exception {
String operationName = exchange.getIn().getHeader(CxfConstants.OPERATION_NAME, String.class);
Method method = findMethod(operationName,exchange.getIn().getBody(Object[].class));
try {
Object response = method.invoke(instance, exchange.getIn().getBody(Object[].class));
exchange.getOut().setBody(response);
} catch (InvocationTargetException e) {
throw (Exception) e.getCause();
}
}
private Method findMethod(String operationName, Object[] parameters)throws SecurityException, NoSuchMethodException {
return beanClass.getMethod(operationName,getParameterTypes(parameters));
}
private Class<?>[] getParameterTypes(Object[] parameters) {
if (parameters == null) {
return new Class[0];
}
Class<?>[] answer = new Class[parameters.length];
int i = 0;
for (Object object : parameters) {
answer[i] = object.getClass();
i++;
}
return answer;
}
}
7. Now we will start by coding the java resource which will handle the rest request. It will consist of a interface and a implementation class. The interface class will contain the web service annotations where as the implementation class will respond to the client requests. Here the paths are defined by using the path annotation. The main path is
/book. Each method defined in this class must be called staring from
http://localhost:9003/rest/book. The interface class will look as below.
RequestService.java
package rs;
import javax.jws.WebParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/book")
@Consumes("application/json")
@Produces(MediaType.APPLICATION_JSON)
public interface RequestService {
@GET
@Path("/author/{id}")
@Produces(MediaType.APPLICATION_JSON)
Response getAvailabilityResults(@PathParam("id") Long id);
@POST
@Path("/search")
@Produces(MediaType.APPLICATION_JSON)
String getSearhResults(@WebParam(name="request") String request);
}
RequestServiceImpl.java
package rs;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RequestServiceImpl implements RequestService {
final Logger logger = LoggerFactory.getLogger(RequestServiceImpl.class);
public String getSearhResults(String request) {
logger.info("getSearhResults().request : " + request);
return "Hello request " + request;
}
public Response getAvailabilityResults(Long id){
logger.info("getAvailabilityResults().Long : " + id);
return Response.ok("Hello request ID :"+id).status(Status.OK).build();
}
}
And that is all you require to write a REST web service with cxfrs component.
Build the project from Maven. I have used clean install package as the Maven goal in Eclipse.
Deploy the war file in a web container like Tomcat. I have used Tomcat 8 for this project.
Finally test the GET and POST methods using a rest client. I have used Postman.
GET
POST
Also note that getSearhResults() method in RequestService class which accept POST requests only consumes json objects. If we remove the Consume annotation and send a XML input in the body, the
getSearhResults(0 will get invoked and you will be able to see the xml input. You can send any content type with the resource url and the method will get invoked as long as it doesn't contain consume annotation which consumes a specific content type. The example is below for your reference.
Happy Coding !!!
~ Ashen Jayasinghe ~