Developing a REST Service in Apache Karaf

[toc]

Abstract

This article focuses on the implementation of a REST service that can be deployed on Apache Karaf using the excellent HTTP Whiteboard service.

In the first part, we will concentrate on building a REST service with Apache CXF and exporting it with the HTTP Whiteboard.

In the second part, we will elaborate on securing the service using Spring Security.

The interface

Let’s develop a very simple task management service to which tasks are submitted to and retrieved from.

The task structure is:

public class Task implements Serializable {
	private static final long serialVersionUID =1L;

	/** Task unique ID */
	private String id;
	private String name;
	private Date creationDate;

	public String getOd() { return id; }

	public void setId(String id) { this.id = id; }

	public String getName() { return name; }

	public void setName(String name) { this.name = name; }

	public Date getCreationDate() {	return creationDate; }

	public void setCreationDate(Date creationDate) { this.creationDate = creationDate; }
}

The provided REST API is:

@Consumes({MediaType.APPLICATION_JSON, "text/json"})
@Produces({MediaType.APPLICATION_JSON, "text/json"})
@Path("/")
public interface TaskService {
	@PUT
	@Path("add")
	Task add(Task task) throws TaskException;

	@DELETE
	@Path("by/id/{id}")
	void deleteById(@PathParam("id") String id) throws TaskException;

	@GET
	@Path("by/id/{id}")
	Task findById(@PathParam("id") String id) throws TaskException;
}
A note on exceptions
The TaskException is a RuntimeException, thus including it in the interface is optional. However it is good practice to include exception signatures in cross-domain service interfaces as they emphasize the type of application-level errors that the service may generate.

The server side will wrap any processing errors into this exception and subsequently CXF can translate the exception into the appropriate HTTP error code and HTTP body.

In case the receiving side is Java-based too, then a similar interceptor can be employed to translate the HTTP error code & body into the appropriate user-space exception.

The model & interface are wrapped into a separate ‘api’ bundle:

<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>
	<artifactId>com.modio.blog.osgi.rest.api</artifactId>
	<packaging>bundle</packaging>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<version>2.3.7</version>
				<extensions>true</extensions>
				<configuration>
					<instructions>
						<Export-Package>
							com.modio.blog.osgi.rest.api
						</Export-Package>
						<Import-Package>
							*
						</Import-Package>
						<Private-Package>
						</Private-Package>
					</instructions>
				</configuration>
			</plugin>
		</plugins>
	</build>
	
	<dependencies>
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>2.0</version>
		</dependency>
	</dependencies>
</project>

The implementation

The service implementation is nothing special:

public class TaskServiceImpl implements TaskService {
	private TaskStore store;

	@Override
	public Task add(Task task) throws TaskException {
		return store.add(task);
	}

	@Override
	public Task deleteById(String id) {
		return store.deleteById(id);
	}

	@Override
	public Task findById(String id) {
		return store.findById(id);
	}

	public void setStore(TaskStore) {
		this.store = store;
	}
}

The blueprint definitions are a bit more interesting:

<blueprint default-activation="eager"
	xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:cxf="http://cxf.apache.org/blueprint/core"
	xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs">

	<!-- Service objects -->
		
	<bean id="taskStore" class="com.modio.blog.osgi.rest.server.TaskStore"/>
	
	<bean id="taskServiceImpl" class="com.modio.blog.osgi.rest.server.TaskServiceImpl">
		<property name="store" ref="taskStore"/>
	</bean>

	<!-- REST service -->

	<bean id="cxfServlet" class="org.apache.cxf.transport.servlet.CXFServlet" />

	<bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>

	<jaxrs:server id="taskService" address="/rest/task">
		<jaxrs:serviceBeans>
			<ref component-id="taskServiceImpl" />
		</jaxrs:serviceBeans>
		<jaxrs:providers>
			<!-- Binding Providers -->
			<ref component-id="jsonProvider"/>
			<!-- Exception Mappers -->
		</jaxrs:providers>
		<jaxrs:features>
			<cxf:logging />
		</jaxrs:features>
		<jaxrs:extensionMappings>
			<entry key="json" value="application/json" />
		</jaxrs:extensionMappings>
	</jaxrs:server>
	
	<!-- Services -->
	
	<service interface="javax.servlet.http.HttpServlet" ref="cxfServlet">
		<service-properties>
			<entry key="alias" value="/services" />
			<entry key="servlet-name" value="com.modio.blog.osgi.rest.api"/>
		</service-properties>
	</service>
</blueprint>

The HTTP Whiteboard module listening on service registrations with the javax.servlet.http.HttpServlet interface, will export the service to Karaf’s Jetty container under the URL http://localhost:8181/services/rest/task.

The HTTP Whiteboard functionality is powerful as it fits nicely into blueprint’s services and service reference listeners and allows interacting with the servlet container while still using regular OSGi bundles as opposed to having to deploy WAB bundles.

Securing the API

Security is an aspect and should not affect our current service interface and the implementation.

We are going to secure our API using basic authentication and the popular the Spring Security framework.

Unfortunately Spring Security is quite complicated to deploy in a Blueprint container as it uses several Spring features that are not part of the Blueprint specification. Even the features that are also part of the Blueprint specification don’t have corresponding implementations for Blueprint. E.g. Spring Security does not provide custom namespace handlers for Blueprint.

Given this set of limitations we can either switch to a more Blueprint-friendly security framework such as Apache Shiro or deploy Spring Security as a Spring-DM bundle. In this article we demonstrate the latter approach.

Spring-DM services can be imported into a Blueprint context exactly like Blueprint services.

The Spring-DM bundle contains the typical Spring Security settings for HTTP security and basic authentication:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:osgi="http://www.springframework.org/schema/osgi"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
	http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

	<security:http create-session="stateless">
		<security:intercept-url pattern="/services/rest/**" access="ROLE_USER" />
		<security:http-basic />
	</security:http>

	<security:authentication-manager>
		<security:authentication-provider>
			<security:user-service>
				<security:user name="tom" password="secret" authorities="ROLE_USER" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

	<osgi:service interface="javax.servlet.Filter" ref="springSecurityFilterChain">
		<service-properties>
			<entry key="osgi.jndi.service.name" value="com.modio.blog.osgi.rest/filter" />
		</service-properties>
	</osgi:service>
</beans>

After setting up Spring Security, the springSecurityFilterChain is exported as a service. It is associated with a qualifier (osgi.jndi.service.name) to avoid confusion since in a larger application more than one filters implementing javax.servlet.Filter may be available as services.

The service is imported into the REST implementation Blueprint container. The relevant additions in the blueprint.xml are:

<blueprint>
	...

	<reference id="filter" interface="javax.servlet.Filter"
		filter="(osgi.jndi.service.name=com.modio.blog.osgi.rest/filter)"/>
	
	<service interface="javax.servlet.http.HttpServlet" ref="cxfServlet">
		<service-properties>
			<entry key="alias" value="/services" />
			<entry key="servlet-name" value="com.modio.blog.osgi.rest.api"/>
		</service-properties>
	</service>
	
	<service interface="javax.servlet.Filter" ref="filter">
		<service-properties>
			<entry key="filter-name" value="springSecurityFilterChain"/>
			<entry key="urlPatterns" value="/services"/>
			<entry key="servletNames" value="com.modio.blog.osgi.rest.api"/>
		</service-properties>
	</service>
</blueprint>

6 thoughts on “Developing a REST Service in Apache Karaf

Leave a Reply

Your email address will not be published. Required fields are marked *