WURFL Java API 1.4

If you are in a hurry to get started with the new API, you can download the WURFL Hello World application directly from Sourceforge: unpack the package into your tomcat webapps/ directory, and just hit http://yourserver[:port]/wurfl-helloworld-{servlet|spring}-{version}/ with your firefox.

Make sure that you have the good User-Agent Switcher FireFox plug-in by Chris Pederick installed. This will allow you to see how your "Hello World" will be delivered in multiple languages: WML, XHTML and CHTML.

The new Java API is no rocket science to anyone who has used WURFL before. Some new concepts will need to be explained, though. No better place to start than a good old HelloWorld.java servlet:

package com.scientiamobile.example;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sourceforge.wurfl.core.Device;
import net.sourceforge.wurfl.core.MarkUp;
import net.sourceforge.wurfl.core.WURFLHolder;
import net.sourceforge.wurfl.core.WURFLManager;
import net.sourceforge.wurfl.core.exc.CapabilityNotDefinedException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld extends HttpServlet {

	/** Serial */
	private static final long serialVersionUID = 10L;

	private static final String XHTML_ADV = "xhtmladv.jsp";
	private static final String XHTML_SIMPLE = "xhtmlmp.jsp";
	private static final String CHTML = "chtml.jsp";
	private static final String WML = "wml.jsp";

	/** Logger */
	private final Logger log = LoggerFactory.getLogger(getClass());

	/**
	 * Processes requests for both HTTP GET and POST
	 * methods.
	 * 
	 * @param request
	 *            servlet request
	 * @param response
	 *            servlet response
	 */
	protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		WURFLHolder wurfl = (WURFLHolder) getServletContext().getAttribute(WURFLHolder.class.getName());
		
		WURFLManager manager = wurfl.getWURFLManager();

		Device device = manager.getDeviceForRequest(request);
		
		log.debug("Device: " + device.getId());
		log.debug("Capability: " + device.getCapability("preferred_markup"));

		MarkUp markUp = device.getMarkUp();

		String jspView = null;

		if (MarkUp.XHTML_ADVANCED.equals(markUp)) {
			jspView = XHTML_ADV;
		} else if (MarkUp.XHTML_SIMPLE.equals(markUp)) {
			jspView = XHTML_SIMPLE;
		} else if (MarkUp.CHTML.equals(markUp)) {
			jspView = CHTML;
		} else if (MarkUp.WML.equals(markUp)) {
			jspView = WML;
		}

		log.info("View: " + jspView);

		// MIME type is decided by JSP. Only in case of XHTML we will need to
		// multi-serve
		// i.e text/html vs application/xml+xhtml vs
		// application/vnd.wap.xml+xhtml
		if (markUp == MarkUp.XHTML_ADVANCED || markUp == MarkUp.XHTML_SIMPLE) {
			String contentType = "text/html";
			try {
				contentType = device.getCapability("xhtmlmp_preferred_mime_type");
			} catch (CapabilityNotDefinedException e) {
				throw new RuntimeException("Somethingh is seriously wrong with your WURFL:" + e.getLocalizedMessage(), e);
			}

			request.setAttribute("contentType", contentType);
			log.debug("ContentType: " + contentType);
		}

		request.setAttribute("markUp", markUp);
		request.setAttribute("device", device);

		request.getRequestDispatcher("WEB-INF/jsp/" + jspView).forward(request, response);
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		processRequest(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		processRequest(request, response);
	}

}

No initialization code for the API anymore? Correct. No initialization no more.

Someone somewhere has already initialized everything for you. In the case of a Servlet (or more generally a web context), this is the work of /WEB-INF/web.xml in which you can define an appropriate ServletContextListener. There are two ways to define ServletContextListeners: raw web.xml config or through Spring Framework (more about this later)

Download Latest Version

For those of you who would like to see the code and understand how the new heuristics work in more depth, here comes the package.

Time to look at the main new concepts of the new API.

New Concepts: WURFLEngine, WURFLManager and Device

The new Main Engine Manager (WURFEngine) is an high-level interface introduced to further abstract hence simplify the WURFL API management.

The Main Engine Manager is self-configured following the "Convention over Configuration" principle. All sensible default collaborators are provided, nevertheless it is highly configurable and backward compatible preserving collaborator such as the WURFLManager and including the WURFLHolder interface.

This Manager implements also the ReloadableWURFLHolder interface, basically to developers facility and to perform hot engine reloading.

Obtaining a Device Object

getDeviceForRequest() is the method that performs the magic of returning the Device object. It comes with three signatures, since the parameter can be one of the following three types: HTTP Request (javax.servlet.http.HttpServletRequest), User-Agent String and WURFLRequest, a java type we have introduced to capture only the headers the API cares about.

We are using the term "type" a bit loosely here. The fact is that the new API relies heavily on Interfaces. This is what will allow you to override specific components with your own implementations. Rather then writing "Classes (actually an Interface)" at every step, We'll opt for the term "type", which may jar to the ear of your average Java programmer, but is probably correct.



As I mentioned, there is now a Device type which will allow you to retrieve all the information about a device and its capabilities. The functions that used to be part of the old CapabilityMatrix are now part of Device. In particular, the function performed by the old capabilityMatrix.getDeviceIDFromUALoose(id,capa_name) is now the job of device.getCapability(capa_name):

 java.util.Map getCapabilities()
 Returns a device's capabilities name-value map.
 java.lang.String getCapability(java.lang.String name)
 Return a device's capability value.
 java.lang.String getId()
 Return the WURFL id of this device
 java.lang.String getUserAgent()
 Return the user-agent string for this device as defined in WURFL.

Now that you know how to get to a Device object, you can make total sense of the HelloWorld servlet above and, more importantly, you'll be able to migrate existing WURFL apps to the new API in a matter of hours.

Let's see how WURFLHolder can be instantiated in a declarative way through the web.xml.

Configuring with good old /WEB-INF/web.xml

(You can find a good template for the web.xml distributed with the servlet HelloWorld)

The web.xml configuration is simple. The API distribution already contains a Listener class which does all the work for you.

if you are not sure about what a ServletContextListener is, it is simply a class which can be associated to a servlet context and is invoked when the context starts or stops.


The class is called net.sourceforge.wurfl.core.web.WURFLServletContextListener and it will take care of configuring net.sourceforge.wurfl.core.WURFLHolder as a wurflHolder.
<listener>
  <listener-class>
     net.sourceforge.wurfl.core.web.WURFLServletContextListener
  </listener-class>
</listener>
In practice, the Listener will programmatically create an instance of the wurflHolder object and attach it to the "net.sourceforge.wurfl.core.WURFLHolder" attribute.
From this moment, the object is available to the servlet, which will use it as the launching pad for the rest of the API, as you have seen already in the HelloWorld servlet above.

In addition to this class, the WURFLServletContextListener will also load the wurfl.xml file by reading the "wurfl" parameter from the web.xml itself.
<context-param>
  <param-name>wurfl</param-name>
  <param-value>/WEB-INF/wurfl.zip</param-value>
</context-param>
In case you also want to use one or more patch files, the syntax for the extra context-param is:
<context-param>
  <param-name>wurflPatch</param-name>
  <param-value>/WEB-INF/patch_1.xml,/WEB-INF/patch_2.xml</param-value>
</context-param>
web.xml is the easiest way to configure the API for your needs.

Note: there is a small but useful addition to the API: you can now use a compressed (.zip or .gz) wurfl.xml file in your WEB-INF. I am sure this possibility will be welcome by many now. Regular wurfl.xml file has reached a size of several Mega Bytes.
In the case of an archived file, the API will take the first compressed file in the archive (no need to call it wurfl.xml). Actually, once you become really familiar with the API and Spring, you can build a class that finds the wurfl.xml in any way you want: just make sure that the class implements the WURFLResource Java interface, and you'll be able to plug it into the API.
Finally, patch files may be archived too (did I mention that you can now have as many patch files as you want?).



A day may come when you are willing to trade that simplicity with the possibility of making the API do more for you. On that day, you will be thankful to the Spring framework.

Configuring with Spring

Note: we assume you are familiar with the Spring Framework here. If not, it is probably time to take your career as a java programmer in your hands and become familar with it.



Using Spring, does not mean that the web.xml file is not involved. You need to tell your application that all of the nice features of the API are now taken over by Spring.
In practice, this means that web.xml will contain the following lines:
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/wurfl-ctx.xml</param-value>
</context-param>
  :
<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

Spring Configuration

We decided to completely decouple WURFL 1.4 from Spring framework, to allow non-Spring users to import and use WURFL without having to deal with the Spring library and everything involved with it (e.g. license). This does not mean that you will not be able to use Spring to instantiate WURFL, of course. In the wurfl-helloworld-spring-1.4.war web application you can download from the repository, you can find an easy way to configure WURFL using Spring. First of all, the new wurfl.ctx context file will contain:
<bean id="WurflHolder" class="net.sourceforge.wurfl.core.GeneralWURFLEngine">
  <constructor-arg index="0" value="classpath:/wurfl.zip" />
  <!-- <constructor-arg index="1" value="<< patch here >>"/> -->
  <!-- <constructor-arg index="2" value="<< more patches here >>"/> -->
</bean>
where you can directly specify the wurfl.xml file (or archive) location, and patches, if needed.
Finally you can simply access to the WURFLHolder instance with the code here below, taken from a common Servlet method.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  WURFLHolder holder = (WURFLHolder)wac.getBean("WurflHolder");
  [...]
}
The helloworld-SpringMVC example in the distribution will show how this is implemented with the Spring MVC framework.

Note: to be clear, do not confuse the Spring MVC framework with Spring core.
We used Spring core to inject all of the WURFL API initialization code declaratively in the configuration. In the SpringMVC example we just mentioned, we provide an example of how WURFL can be used with any MVC framework and the example could be easily ported to Struts, JSF or other MVC framework.



Finer-grade API Configuration

If the default 'conventions' assumed by the simple configuration are not good enough, you will need to understand a bit more of different objects that are handled 'under the hood'.
Following the Spring spirit of Inversion of Control (IoC), objects are no longer hard-wired into the API itself. Rather, you have a Spring configuration file that tells the API where to find the resources and the parts of the program which, collectively, combine the API.
For example, if you decide to place your WURFL in a database or modify the behavior of the API in some contexts, you do not need recompile the whole API. Instead, you simply plug-in your new class into the existing API.
As with any Open-Source project, you can always change and rebuild a modified version of the API for yourself. As a matter of fact, many did exactly this with the old API.

In case you do need to change certain features, the new API offers a more elegant way to implement what you need. In case you want to change the implementation of one or more components in the API, you do not need to change existing code or even recompile the library. You can provide your own alternate implementation of that component and plug it into the API through so-called "wiring": open a file called /WEB-INF/wurfl-custom-ctx.xml and tell the WURFL API to load your classes instead. If you are new to Spring, this may sound strange. Yet, I promise you, it will all look simpler (and powerful!) once you understand Spring or, more generally, the pleasures of Inversion of Control (IoC).

MatcherManager, WURFLEngine, WURFLHolder and other amenities

The Spring configuration will plug in more (and more fine-grained) objects than what we have seen with the web.xml example.
The net.sourceforge.wurfl.core.matchers.MatcherManager is one such object:
<bean id="matcherManager"
    class="net.sourceforge.wurfl.core.matchers.MatcherManager">
  <constructor-arg ref="wurflModel" />
</bean>

MatcherManager is a class, not an interface. For this reason, it is one of the few components that cannot be plugged with the new API. We figured that this function was too tightly coupled with the framework as a whole, to go out of our way to abstract its implementation


MatcherManager is responsible for managing all of the heuristics. Heuristics implement the logic to: It goes without saying that the MatcherManager needs the wurflModel.

Configuring Device

The Device interface and the DefaultDevice implementation is the object that gives you access to all the capabilities of a given device.
Believe it or not, the Device object can be configured in a few ways.
<bean id="deviceProvider"
      class="net.sourceforge.wurfl.core.DefaultDeviceProvider">
  <constructor-arg ref="wurflModel" />
</bean>
I already explained the wurflModel, so it is probably no surprise that the Device object needs it.


Configuring the Caching Strategy

Customizing which caching strategy is adopted by the API is, surprise, only a matter of configuration.
You have several options to choose from.
  <!-- DeviceCacheProvider -->
  <bean id="deviceCacheProvider" class="net.sourceforge.wurfl.core.cache.LRUMapCacheProvider" />
  
  <!--
    <bean id="deviceCacheProvider" class="net.sourceforge.wurfl.core.cache.DoubleLRUMapCacheProvider" />
  -->
  
  <!--
    <bean id="deviceCacheProvider" class="net.sourceforge.wurfl.core.cache.HashMapCacheProvider" />
  -->

  <!--
    <bean id="deviceCacheProvider" class="net.sourceforge.wurfl.core.cache.NullCacheProvider" />
  -->
  
  
  <!-- 
    <bean id="deviceCacheProvider" class="net.sourceforge.wurfl.core.cache.EhCacheProvider" />
  -->

Please refer EHCache documentation for details of how to use it. Most probably, you'll need a ehcache.xml in your classpath.

Configuring High-Performance (HP) and High-Accuracy (HA) mode

To configure WURFL API to run in High-Performance or High-Accuracy mode, you can edit the XML /META-INF/wurfl-config.xml configuration file (the Java API refers to this at engine-target).

for High-Accuracy mode:

 <wurfl-api-config engine-target="accuracy">

for High-Performance mode:

 
 <wurfl-api-config engine-target="performance">

High-Performance is the default target.

As well you can change the HP/HA configuration mode by program call trough the method setEngineTarget(EngineTarget target) of the GeneralWURFLEngine object.

WURFLEngine

WURFLEngine is the main interface to build a WURFLRequest and obtain a Device. GeneralWURFLEngine will need to refer to a bunch of beans (injected as properties):

<bean id="WurflHolder" class="net.sourceforge.wurfl.core.GeneralWURFLEngine">
  <property name="cacheProvider" ref="deviceCacheProvider" />
  <property name="capabilitiesHolderFactory" ref="capabilitiesHolderFactory" />
</bean>

WURFLUtils

This class collects a number of introspective utility functions, such as: