Registering a servlet filter in Apache Karaf

I have already written several articles about deploying web applications in OSGi, and in particular in Apache Karaf. This blog post is a small addition to indicate how to deploy a filter in such an environment.

My first attempt was trying to register my filter directly in the HTTP service. Unfortunately, there is no method for this. After searching the web, I finally found an example here. Apache Karaf comes with Pax-Web. And Pax-Web has extender modules. Basically, all you have to do is registering your filter as an OSGi service. Pax-Web extender modules will be notified about this service (white board pattern) and automatically register it as a servlet filter in the web server (which is Jetty, by the way).

Personnaly, I use this approach to add authentication to REST services. They are implemented with Jersey 1.x and served by Karaf’s web server. The filter intercepts requests and verifies they have the identity token. Obviously, it aims at being used with HTTPS.

So, here are some code snippets.
Let’s start with the filter itself.

public class AuthenticationFilter implements Filter {

	private final Logger logger = Logger.getLogger( getClass().getName());

	private AuthenticationManager authenticationMngr;
	private boolean authenticationEnabled;
	private long sessionPeriod;


	@Override
	public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain )
	throws IOException, ServletException {

		if( ! this.authenticationEnabled ) {
			chain.doFilter( req, resp );

		} else {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) resp;
			String requestedPath = request.getRequestURI();
			this.logger.info( "Path for auth: " + requestedPath );

			// Find the session ID in the cookies
			String sessionId = null;
			Cookie[] cookies = request.getCookies();
			if( cookies != null ) {
				for( Cookie cookie : cookies ) {
					if( UrlConstants.SESSION_ID.equals( cookie.getName())) {
						sessionId = cookie.getValue();
						break;
					}
				}
			}

			// Is there a valid session?
			boolean loggedIn = false;
			if( ! Utils.isEmptyOrWhitespaces( sessionId )) {
				loggedIn = this.authenticationMngr.isSessionValid( sessionId, this.sessionPeriod );
				this.logger.finest( "Session " + sessionId + (loggedIn ? " was successfully " : " failed to be ") + "validated." );
			} else {
				this.logger.finest( "No session ID was found in the cookie. Authentication cannot be performed." );
			}

			// Valid session, go on. Send an error otherwise.
			// No redirection, we mainly deal with our web socket and REST API.
			boolean loginRequest = requestedPath.endsWith( IAuthenticationResource.PATH + IAuthenticationResource.LOGIN_PATH );
			if( loggedIn || loginRequest ) {
				chain.doFilter( request, response );
			} else {
				response.sendError( 403, "Authentication is required." );
			}
		}
	}


	@Override
	public void destroy() {
		// nothing
	}


	@Override
	public void init( FilterConfig filterConfig ) throws ServletException {
		// nothing
	}

	public void setAuthenticationEnabled( boolean authenticationEnabled ) {
		this.authenticationEnabled = authenticationEnabled;
	}

	public void setAuthenticationManager( AuthenticationManager authenticationMngr ) {
		this.authenticationMngr = authenticationMngr;
	}

	public void setSessionPeriod( long sessionPeriod ) {
		this.sessionPeriod = sessionPeriod;
	}
}

And here is the section in my class that registers my filter.
It is the same method that registers my servlets in the HTTP service.

this.authenticationFilter = new AuthenticationFilter();
this.authenticationFilter.setAuthenticationEnabled( this.enableAuthentication );
this.authenticationFilter.setAuthenticationManager( this.authenticationMngr );
this.authenticationFilter.setSessionPeriod( this.sessionPeriod );

initParams = new Hashtable<> ();
initParams.put( "urlPatterns", "*" );

// Consider the bundle context can be null (e.g. when used outside of OSGi)
if( this.bundleContext != null )
	this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
else
	this.logger.warning( "No bundle context was available, the authentication filter was not registered." );

In Karaf 4.x, this is enough to register a servlet filter.
There are other alternatives, but this one requires less code. Read Achim’s answer here for more details.

Web Applications with OSGi – Static Files

Context

I currently work on migrating an existing web application to OSGi (*).
The current solution relies on Jersey to expose REST services. It also exposes static resources for the web. And what I need to do is to drop Tomcat and deploy it inside an OSGi container. This is the last step to perform to complete our OSGi migration.

(*) Our motivation to use OSGi is classloaders isolation.

Beyond deploying a web application and Jersey services, we also have to plug it with iPojo. iPojo is the framework we chose to define components and services in our application. And to be complete in our description, we will deploy it on Apache Karaf 3.0.x, with Felix as the default container.

Before breaking my application, and given all the things that are involved, I wanted to create a short sample to make sure verything works together. Most of all, and because I did not find a ready-to-use solution on the web, I wanted to determine HOW everything could work together. To be honest, I did not succeeed on the first time. In fact, I have been forced to proceed step by step.

So, this post is the first of a series about web applications in OSGi. And it will start with very simple cases.

Serving Static Files in OSGi

I am NOT going to tell you to write a bundle activator and register a servlet with the HTTP service. Now, OSGi containers support the deployment of web bundles (WAB). This is a ZIP archive that contain both web resources (like in a WAR) and an OSGi bundle. Said differently, it is a WAR file with OSGi metadata in a MANIFEST. And it does not have to use a specific file extension. So, it could also be a JAR file with the structure of a WAR. Using one extension or another depends on your context.

So, here is an example of a WAB that will server static resources. No servlet, no JSP… Since it is very simple, and not dependent on OSGi, I will create a WAR file and add OSGi metadata. This way, I will be able to deploy it in an OSGi container with web support, or in any application server such as Tomcat.

Let’s start with the POM.

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>net.vzurczak</groupId>
	<artifactId>serving-static-files</artifactId>
	<name>OSGi Sample :: Web App :: Static Files</name>
	<packaging>war</packaging>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<configuration>
					<archive>
						<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
					</archive>
				</configuration>
			</plugin>
			
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<executions>
					<execution>
						<id>bundle-manifest</id>
						<phase>process-sources</phase>
						<goals>
							<goal>manifest</goal>
						</goals>
						<configuration>
							<instructions>
								<Web-ContextPath>static-files</Web-ContextPath>
								<Webapp-Context>static-files</Webapp-Context>
							</instructions>
						</configuration>
					</execution>
				</executions>
				<configuration>
					<supportedProjectTypes>
						<supportedProjectType>jar</supportedProjectType>
						<supportedProjectType>bundle</supportedProjectType>
						<supportedProjectType>war</supportedProjectType>
					</supportedProjectTypes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

The project structure must be…

pom.xml
src/main/webapp/
* Your static resources (HTML, CSS, JPG…)
* WEB-INF/web.xml

And here is the content of the web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xmlns="http://java.sun.com/xml/ns/javaee" 
		xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
		id="WebApp_ID" version="3.0">

	<display-name>Welcome Page</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
	
	<servlet-mapping>
		<servlet-name>default</servlet-name>
		<url-pattern>*.jpg</url-pattern>
	</servlet-mapping>
	
	<servlet-mapping>
		<servlet-name>default</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
</web-app>

The sources are available on GitHub.

Run mvn clean install.
Pick up the WAR file under the target directory and you can directly deploy it in a Tomcat server, or in an OSGi container with web support. How to enable web support in an OSGi container? Well, you can rely on Pax Web. Pax Web is a project that embeds application servers such as Jetty and Tomcat in bundles. Since I deploy on Apache Karaf, I used the WAR feature, which relies on Pax Web.

So, if you are using Apache Karaf, start it and type in feature:install war (or if you are using Karaf 2.x, features:install war). This will enable web support in your installation. And to deploy your web bundle in Karaf, just drop it in Karaf’s deploy directory.

If you want to make sure it is correctly deployed, you can first check in your browser that your resources are displayed at http://localhost:8181/static-files (assuming you kept the default settings for Jetty). And you can also install Karaf’s web console (feature:install web-console) and go to http://localhost:8181/system/console/http (default credentials: karaf / karaf). You will see the HTTP and web contexts there.

Serving a JSP in OSGi

Now, let’s assume you want to use JSP files and have a custom servlet in your web application. What was defined before is still valid. The only modification to bring is in the POM file.

Simply add the following dependency.

<dependencies>
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>2.5</version>
		<scope>provided</scope>
	</dependency>
</dependencies>

Make sure the scope is provided.
The Maven-bundle-plugin will automatically add the package imports in the MANIFEST. And these imports will be provided at runtime by other bundles.

This post is quite detailed.
But I found a lof of information on this blog entry. You may be interested by its reading.