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)
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.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.
Device ObjectgetDeviceForRequest() 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.
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. |
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.WURFLHolder can be instantiated in a declarative way through the
web.xml./WEB-INF/web.xmlweb.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.
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.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?).
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.
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.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>
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.
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.
/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 amenitiesweb.xml example.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:
"Nokia", place it in the Nokia family)WURFLRequest object with the right WURFL entry
(for ex., if UA string contains "Nokia", leverage the Nokia family and heuristics
to find a match)MatcherManager needs the wurflModel.DeviceDevice interface and the DefaultDevice implementation
is the object that gives you access to all the capabilities of a given device.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.
<!-- 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.
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 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>
This class collects a number of introspective utility functions, such as: