Jun 17, 2009

Small Description on Java WURFL Api

WURFL Java API

The API is simple. You have basically three objects which give you all the methods to obtain the info you need:
CapabilityMatrix , UAManager and ListManager.

CapabilityMatrix:
getCapabilityForDevice() requires a DeviceID and a capability name and it will return the value of the capability.

UAManager:
getDeviceIDFromUA() and getDeviceIDFromUALoose() require a User-Agent string and return the WURFL device ID.
It comes with two flavors: strict matching and loose matching. If in doubt, use the loose matching.

ListManager:
contains method that return lists of WURFL related data, such as list of capabilities, list of devices, etc.

To add to that, you have a singleton (ObjectsManager) that makes sure that all the WURFL data is initialized at least once and only once.
The three objects above are returned by the ObjectsManager() with calls like:

CapabilityMatrix cm = ObjectsManager.getCapabilityMatrixInstance();

One important aspect of this API is that you need *no* XML knowledge whatsoever to use it.
You just deal with strings and lists of different kinds (ArrayLists and HashMaps).

Here is a simple example of how to use the library


import net.sourceforge.wurfl.wurflapi.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Test extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();

//HTML Syntax
out.println("Request Information Example");

out.println("

Request Information Example

");

UAManager uam = ObjectsManager.getUAManagerInstance();
CapabilityMatrix cm = ObjectsManager.getCapabilityMatrixInstance();
out.println(uam.getDeviceIDFromUA("MOT-T720/05.08.41R MIB/2.0 Profile/MIDP-1.0 Configuration/CLDC-1.0"));

out.println("loose matching MOT-T720/G_05.01.43R");
out.println(uam.getDeviceIDFromUALoose("MOT-T720/G_05.01.43R"));

out.println(uam.getDeviceIDFromUALoose("UP.Browser"));
out.println(uam.getDeviceIDFromUALoose("Nokia7650"));

out.println("Capability Matrix:");
out.println("Device ID: telit_gm822_ver1_sub302_5017 , Capability Name: mms_wbxml")
out.println(cm.getCapabilityForDevice("telit_gm822_ver1_sub302_5017","mms_wbxml"));
out.println("");
out.println("");


}

This will produce:

mot_t720_ver1_sub050841r

loose matching MOT-T720/G_05.01.43R
mot_t720_ver1_subg050143r

samsung_r200s_ver1_sub4119kxxxx
nokia_7650_ver1_subnover
Capability Matrix:
Device ID: telit_gm822_ver1_sub302_5017 , Capability Name: mms_wbxml
false


One important aspect to note is that the results of your queries are cached. This speeds up WURFL access enormously.
The cache is the same for all of the servlet/JSP pages running inside the same webapplication.

Of course, any WURFL-based application will need to be initialized with a wurfl.xml file. This may happen in two different ways,
depending on wether you are deploying in a web (Servlet/JSP) environment or it's a stand-alone application.

Initializing stand-alone application
You can explicitly initialize the WURFL by telling the system where to find the file:

ObjectsManager.getWurflInstance("C:\wurfl_stuff\wurfl.xml");

if you don't tell the library where to find the wurfl programmatically using the method above, the API will initially look for a file
called wurfl.properties to see if the location of the wurfl is specified there: sample wurfl.properties:

wurflpath = file://C:\\projects\\wurfl\\antbuild\\resources\\wurfl.xml

If the property file is not found, the API will look for "C:\temp\wurfl.xml" on Windows and "/tmp/wurl.xml" on UNIX.

If nothing is found, an exception is thrown and a message is printed.




Retrieving a Device ID through the User-Agent

Two different methods in the UAManager class can do the job:

* getDeviceIDFromUA(String user-agent)
returns a String representing a WURFL device ID. This function looks in the WURFL for an exact match. If you don't get it, you will receive "generic" (i.e. the ID of the unrecognized device).
* getDeviceIDFromUALoose (String user-agent)
also returns a string representing a WURFL device ID. The difference is that this method won't take no for an answer nearly as easily. In case an exact match is not found, a few heuristics are applied to find a device that's reasonably similar.

At the time of this writing, the heuristics are the following:

1. Chars are removed one at the time from the end of the string. At each step a match is attempted between the string and the first part of each existing user-agent. This ends when either a match is found or the query string has become five chars.
2. Vodafone has introduced on its network a bunch of devices that add the "Vodafone/" string at the beginning of the user-agent. Those devices are listed in the WURFL. In case of a miss, the "Vodafone/" string is removed and the matching algorithm is applied from start.
3. If a match is not found, the matching algorithm looks for hints that still can lead to some useful assumptions: "UP.Browser/4","UP.Browser/5","UP.Browser/6", "Series60","Mozilla/4.0" and so on.
4. Defeat! return "generic".

If you wonder about the performance of this animal, don't worry. Queries are cached, so any subsequent query with the same data is resolved with a simple HashMap lookup.

Retrieving Capability Values

Once you have a device ID, you are ready to rock and roll: just use getCapabilityForDevice() to retrieve the value of any capability. This is a method of the CapabilityMatrix class.

- getCapabilityForDevice(String deviceID, String capabilityName)
Returns the value of a capability for a given device. There is not much to add here:

System.out.println(cm.getCapabilityForDevice("sie_m50_ver1","resolution_width"));
Q: Excuse me, Mr. Capability Matrix, how many horizontal pixels can I rely on for a Siemens M50?
A: 110 pixels, Sir.



Simple and powerful.

Setting up the Singletons

The methods above are simple to use, but there is a tiny bit of work you need to do before you can use them. Parsing the WURFL and traversing it to retrieve information is heavy stuff. The API is architected in a way that time-consuming operations are performed either at start-up or only once.

For this reason, you need to create a UAManager and CapabilityMatrix before you invoke the corresponding methods above. For the same reason, you don't just create a UAManager and CapabilityMatrix using 'new()', rather you request ObjectsManager to give them to you. ObjectsManager will make sure that a new object is created only if you are the first to request. Otherwise, you get the existing one (or better a reference to it, but I assume you have a smattering of Java from before). UAManger and CapabilityMatrix are examples of Singletons, in case you are familiar with pattern-theory parlance. If you are not, there's no need to worry: Just make sure that you have these two lines in your code, and you will have someone to talk to when you need to make a query:

UAManager uam = ObjectsManager.getUAManagerInstance();
CapabilityMatrix cm = ObjectsManager.getCapabilityMatrixInstance();

A link to a resource about Singletons can be found in the references.

Time for an another Example

The java snippet at the end of the WURFL intro last month was an example of usage of the Java API. Let's have a look at something more practical here: the implementation of the 'br' tag of the WALL library. There is no common 'br' (breakline) tag for CHTML, XHTML MP and WML, but ... almost.

* Most CHTML devices honor the
tag and simply ignore
tags
* Most XHTML MP devices will honor both
and
tags.
* Some XHTML MP device will throw an error if a single unslashed
is found and no information will be displayed (except a pretty useless error message)

This is a job for WALL, the Wireless Abstraction library:

------------------------------------------------------------------------------
public int doStartTag() throws JspException {

try {
cm = ObjectsManager.getCapabilityMatrixInstance();
uam = ObjectsManager.getUAManagerInstance();
request = (HttpServletRequest) pageContext.getRequest();

//get the user agent
UA = TagUtil.getUA(this.request);

//Check the capability string
warning = TagUtil.checkCapability("preferred_markup");
if (warning.length() > 0) {
throw new JspException(warning);
}

device_id = uam.getDeviceIDFromUALoose(UA);

capability_value = cm.getCapabilityForDevice(device_id, "preferred_markup");
capability_value = TagUtil.getWallMarkup(capability_value);

if ( capability_value.startsWith("xhtmlmp") ) {
try {
JspWriter out = pageContext.getOut();
out.print("
");
} catch(IOException ioe) {
throw new JspException("Error in WALL tag 'br': " + ioe.getMessage());
}
}

if ( capability_value.startsWith("wml") ) {
try {
JspWriter out = pageContext.getOut();
out.print("
");
} catch(IOException ioe) {
throw new JspException("Error in WALL tag 'br': " + ioe.getMessage());
}
}

//CHTML
without trailing slash
if ( capability_value.startsWith("chtml") ) {
try {
JspWriter out = pageContext.getOut();
out.print("
");
} catch(IOException ioe) {
throw new JspException("Error in WALL tag 'br': " + ioe.getMessage());
}

}

return (SKIP_BODY);

} catch (Exception e) {
throw new JspException("Error in WALL tag 'br': " + e.getMessage());
}

}
------------------------------------------------------------------------------



As usual, real apps need to spend much time in managing errors, so, here is an abridged version that shows the relevant parts:

------------------------------------------------------------------------------
1 cm = ObjectsManager.getCapabilityMatrixInstance();
2 uam = ObjectsManager.getUAManagerInstance();
3 request = (HttpServletRequest) pageContext.getRequest();
4
5 //get the user agent
6 UA = TagUtil.getUA(this.request);
7
8 device_id = uam.getDeviceIDFromUALoose(UA);
9
10 capability_value = cm.getCapabilityForDevice(device_id, "preferred_markup");
11 capability_value = TagUtil.getWallMarkup(capability_value);
12
13 if ( capability_value.startsWith("xhtmlmp") ) {
14 out.print("
");
15 }
16
17 if ( capability_value.startsWith("wml") ) {
18 out.print("
");
19 }
20
21 //CHTML
without trailing slash
22 if ( capability_value.startsWith("chtml") ) {
23 out.print("
");
24 }
------------------------------------------------------------------------------

* lines 1 and 2 create our singletons.
* lines 3 and 6 extract the User-Agent string out of the request. TagUtil.getUA() simple gives the developer a chance to override the User-Agent HTTP header for debugging purposes.
* line 8 does whatever it can to retrieve the ID of the device definition that best models the requesting device out of the user-agent string.
* lines 10 and 11 query the 'preferred_markup' for the device. TagUtil.getWallMarkup() normalizes the retrieved value into one of the three that WALL expects ('wml','xhtmlmp' and 'chtml')
* lines 13 through 24 send the most appropriate break-line tag back according to the preferred markup.

It's simple stuff. No rocket science here.

All That We Have Left Behind

The main functionalities of the API are covered, but a bunch of other things are worth mentioning.

* CapabilitMatrix.isCapabilityIn(String capability_name)
In real apps, you may want to check that a given capabilities is defined in the WURFL before querying it. An error here and you may spend more time than you wish tracking out what went wrong where. This method does that.

* ListManager is also a singleton and it supports a bunch of methods to retrieve lists of devices and capabilities you can loop on. In practical applications, you don't need those methods, since you typically deal with one device at the time. The methods become necessary when you want to implement WURFL utilities to analyze the WURFL repository and build reports. For example, you may request a list of all actual devices (devices with 'actual_device_root==true').

* WurflDevice encapsulates in a Java class everything the WURFL knows about a given device. Also not very useful unless you need to build reports of some kind. This also includes the use of the WurflDevice.getActual_device_root(), which can be used to tell if a given device is marked as 'actual device' in the WURFL.

* WurflSource is an interface that lets you initialize the API using alternative sources from which to read wurfl.xml. Totally honestly, this functionality is not very tested. If it doesn't work, give the WURFL team a holler on the WMLProgramming mailing list.