WURFL Java API 1.5

Important Note: If you are migrating to 1.5 from 1.4.X or a previous version of the WURFL Java API, you may want to read the WURFL 1.5 migration guide. Not a lot has changed, but enough to prevent 'drop-in' replacement.

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.WURFLEngine;
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 {
		WURFLEngine engine = (WURFLEngine) getServletContext().getAttribute(WURFLEngine.class.getName());		

		Device device = engine.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 any 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, Capability Filter and Virtual Capabilities

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

In most cases, initializing the new WURFLEngine is enough to start using the API. The default implementation of WURFLEngine is GeneralWURFLEngine (if you were already using it with version 1.4.x, everything should still work).
WURFLEngine is now the entry point to all WURFL functionalities, which we can group in:

Examples of configuration with web.xml and Spring framework configuration are illustrated later.

Capability Filter
API 1.5 introduces Capability Filters: in short, a Capability Filter is essentially a list of capability names that allows users to select which capabilities should be loaded in memory by the API. The other capabilities are simply discarded, regardless of the fact that they are part of the wurfl.xml file.
The list of the selected capabilities can be set by calling the setCapabilityFilter method of WURFLEngine. The actual list can be passed as either a string array (String[]) or as a generic collection (Collection<String>).
Two important aspects should be observed:

Virtual Capabiities
Virtual capabilities are a new important feature of the WURFL API to obtain values related to the requesting agent out of the HTTP reuqest as a whole (as opposed to limiting itself to capabilities that are found in WURFL).
In order to compute its final returned value, a virtual capability may look at regular (non-virtual) capabilities as well as other parameters that can be derived from the HTTP request at run-time. Virtual capabilities are useful to model aspects of the HTTP Client that are not easily captured through the finite number of agent profiles in WURFL.
Virtual capabilities depend on certain real capabilities, which, for this reason, may be considered mandatory. These capabilities are: device_os, device_os_version, is_tablet, is_wireless_device, mobile_browser, mobile_browser_version, pointing_method, preferred_markup, resolution_height, resolution_width, ux_full_desktop, xhtml_support_level

Here is the list of virtual capabilities introduced with API 1.5:

Virtual Capability Description
is_android True if the device runs Android (any version).
is_ios True if the device runs iOS (any version).
is_windows_phone True if the device runs Windows Phone 6.5 or higher. Note that this does not include Windows Mobile or Windows CE.
is_app True if the requests is from a native app. This typically of requests from WebView components and RESTful API calls.
is_full_desktop True if the requesting device has a full desktop experience. This capability is an alias for ux_full_desktop.
is_largescreen True if the requesting device's screen is of a high resolution (over 480 pixels in width and height)
is_mobile True if the device is mobile, like a phone, tablet, media player, portable game console, etc. This capability is an alias for is_wireless_device.
is_robot True if the request is from a robot, crawler, or some other automated HTTP client.
is_smartphone True if the device is a smartphone. Internally, the matcher checks the operating system, screen width, pointing method and a few other capabilities.
is_touchscreen True if the primary pointing method is a touchscreen.
is_wml_preferred True if the requesting device should be served with WML markup.
is_xhtmlmp_preferred True if the requesting device should be served with XHTML-MP markup.
is_html_preferred True if the requesting device should be served with HTML markup.
advertised_device_os Returns the operating system name of the requesting device. This works for mobile and desktop devices. (ex: "Windows", "Mac OS X")
advertised_device_os_version Returns the operating system version of the requesting device. This works for mobile and desktop devices. (ex: "XP", "10.2.1")
advertised_browser Returns the browser name of the requesting device. This works for mobile and desktop devices. (ex: "Internet Explorer", "Chrome")
advertised_browser_version Returns the browser version of the requesting device. This works for mobile and desktop devices. (ex: "7", "29")

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 introduced to capture only the headers that the API cares about.

Device objects provides access to all the information about a device by querying capabilities and virtual capabilities:

 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.util.Map getVirtualCapabilities()
 Returns a device's virtual capabilities name-value map.
 java.lang.String getVirtualCapability(java.lang.String name)
 Return a device's virtual 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(*)

* Important Note: you don't normally need the getUserAgent() method. The user-agent string with which the device was retrieved is part of the HTTP request. This method returns the user-agent string that is defined in WURFL for that device.


Now that you know how to get to a Device object, you should know enough to make sense of the HelloWorld servlet above.

Let's see how WURFLEngine 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.WURFLEngine as a wurflEngine.

<listener>
  <listener-class>
     net.sourceforge.wurfl.core.web.WURFLServletContextListener
  </listener-class>
</listener>

In practice, the Listener will programmatically create an instance of the wurflEngine object and attach it to the "net.sourceforge.wurfl.core.WURFLEngine" 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>

Note: You can now use a compressed (.zip or .gz) wurfl.xml file in your WEB-INF.
Also, you can now have as many WURFL patch files as you want.

web.xml is the easiest way to configure the API for your needs.

New capability filter feature can also be configured directly from the web.xml; capability names can be specified into a context parameter value, simply called capability-filter. Capabilities need to be specified one-per-line without punctuation marks, like this:

<context-param>
	<param-name>capability-filter</param-name>
	<param-value>
		device_os
		device_os_version
		is_tablet
		is_wireless_device
		mobile_browser
		mobile_browser_version
		pointing_method
		preferred_markup
		resolution_height
		resolution_width
		ux_full_desktop
		xhtml_support_level
	</param-value>
</context-param>

Configuring with the Spring Framework

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.

The WURFL API (starting with 1.4) is completely decoupled from the Spring framework. This allows non-Spring users to import and use WURFL without having to deal with the Spring library and everything involved with it.
This does not mean that you will not be able to use Spring to instantiate WURFL, of course.

Using Spring, does not mean that the web.xml file is no longer involved. You need to tell your application that all the nice features of the API are now supported through 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 Framework Configuration

In the wurfl-helloworld-spring-{version}.war web application, you can find an easy way to configure the WURFL API 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>

here you can directly specify the path to the wurfl.xml/.zip/.gz file (and WURFL patches, if needed).
Finally you can simply access to the WURFLEngine 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());
  WURFLEngine engine = (WURFLEngine)wac.getBean(WURFLEngine.class.getName());
  [...]
}

Of course, a capabilities filter can be set up also through Spring. The list of the capabilities can be specified by adding this in wurfl-ctx.xml:

<property name="capabilityFilter">
	<set>
			<value>device_os</value>
			<value>device_os_version</value>
			<value>is_tablet</value>
			<value>is_wireless_device</value>
			<value>mobile_browser</value>
			<value>mobile_browser_version</value>
			<value>pointing_method</value>
			<value>preferred_markup</value>
			<value>resolution_height</value>
			<value>resolution_width</value>
			<value>ux_full_desktop</value>
			<value>xhtml_support_level</value>
	</set>
</property>

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: