Using Wurfl with Spring:
a look at Spring-Mobile and rolling Wurfl by hand
The basics for this were in the lectures, and we also covered
Wurfl in practical
7. which looked at the old version of WURFL. This practical looks
at new Java API for WURFL that integrates with Spring.
This practical is still being updated for the latest versions of Wurfl and Spring Mobile (Nov 2011)
As before with Spring, there are two ways of doing this. The first
version below uses the most recent Spring modules and hooks into our
Spring Roo application, and could also be used to hook into any Spring
application. The second, older version will be from scratch and
integrates Wurfl into Spring by hand.
Using Spring Mobile in your app
Spring mobile was released in mid-November 2010 and integrates Wurfl into your spring-based application for you. With a little spent add the module to your project and writing some configuration files, you'll be able to modify your application to identify mobile users and adapt your pages accordingly. What follows is based on the guide at the Spring Mobile site and on the notes written up about Spring Mobile usage, as well as the sample example that you can clone from the spring mobile git source site.
Start up eclipse and open the roo shell for the travelagent
application we worked on last week. Start up tomcat from within eclipse
too, so that your tomcat manager configuration works for redeployment.
Set up logging to see what's happening
First, we want to know what's going on in our application, so we'll add logging to it. This will show us everything that is happening in the background, and detail the deployment of our application for us. We do this with the command in the roo shell of:
logging setup --level DEBUG
With this we will now see everything that's happening in the application: what database calls are happening, when sessions start, and everything else. You can change the src/main/resources/META-INF/log4j.properties file later to use INFO instead of DEBUG if you want less output.
Configure the pom.xml file for dependencies
Second, you need to have your app download the appropriate dependencies. So add these two bits of code to your pom.xml file.This part need to go near the top of the file before the end of the </dependencies> section as it tells maven which version it should use. Add the lines in bold.
<org.springframework.mobile-version>1.0.0.M3</org.springframework.mobile-version>
<wurfl.version>1.3.5-SNAPSHOT</wurfl.version>
</properties>
We also need to update the repository used by Wurfl, so add this repository code just after the <repositories> element as shown in bold.
<repository>
<id>net.sourceforge.wurfl</id>
<name>Scientia Mobile Public repository</name>
<url>http://dev.scientiamobile.com/nexus/content/repositories/public-snapshot/</url>
</repository>
<repository>
We also need to add the corresponding dependencies to our project
for spring-mobile and the wurfl Java library, which we do with these
lines that are added to the other dependencies in the pom.xml file.
<groupId>org.springframework.mobile</groupId>
<artifactId>spring-mobile-device</artifactId>
<version>1.0.0.M3</version>
</dependency>
<dependency>
<groupId>net.sourceforge.wurfl</groupId>
<artifactId>wurfl-java-api</artifactId>
<version>${wurfl.version}</version>
</dependency>
</dependencies>
We now have the dependencies sorted and the appropriate libraries will be added to our repository the next time you build the project.
Second, we need to tell Spring how to interept the http requests and what to do with those mobile and desktop requests. We do this by adding a request interceptor that makes use of the wurfl device library to the webmvc-config.xml file in the WEB-INF/spring directory. There are two parts that we need to deal modify to sort this out.
At the top of the file we need to add some more namespaces that will let us use the beans in the spring-mobile code. Add the code in bold so that the namespace declarations look like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:device="http://www.springframework.org/schema/mobile/device"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/mobile/device
http://www.springframework.org/schema/mobile/device/spring-mobile-device-1.0.xsd
">
You need to have all of these namespaces and have them match the ones you'll use below. You also need to ensure that you have paired the lines above so that the namespace declaration is paired with the appropriate schema declaration. If you don't, then you will receive errors on deployment.
Troubleshooting error messages: While sorting out this file you may encounter errors that suggest some of your namespaces are not 'bound', which means you've not added in the schema declaration correctly. Similarly, one about needing a space between the systemId and publicId means that your xml is not well-formed and an element is not closed correctly.
The next part to add to the webmvc-config.xml file the part declaring your http interceptor and the location of the wurlf file and its updates. Place the code in bold ahead of the closing </interceptors> tag.
<!-- On
pre-handle, detect the device that originated the web request -->
<bean
class="org.springframework.mobile.device.mvc.DeviceResolvingHandlerInterceptor">
<constructor-arg>
<device:wurfl-device-resolver
root-location="/WEB-INF/wurfl/wurfl-2.0.25.zip"
patch-locations="/WEB-INF/wurfl/web_browsers_patch.xml" />
</constructor-arg>
</bean>
</mvc:interceptors>
You will need to download and save the wurlf zip file (you want the version to match the code above) and the patch file and save them to the specified locations in a wurfl directory under WEB-INF, which you'll need to save. You can download both from the links to the left on the wurfl home page.
Changing content for mobile pages
The most simple thing to do is change the output of the jsp and jspx views. For example, we can edit the WEB-INF/views/header.jspx file to add this line in bold:
</a>
<h4>You are using
${currentDevice.userAgent}</h4>
</div>
Users will now be told which browser they are using. While this
isn't useful as such it does show that everything is working as it
should be and you can look at this through either various mobile
emulators, or put this somewhere which is reachable via a mobile
device, or use the Firefox user-agent
switcher plugin to 'pretend' to be a mobile browser.
With this all in place you can now check the browser type and modify
the content. You can also see other
examples
of
what
to
do
with
views in the sample app.
If you find mobile content, then you can opt to change the content with something like this for appropriate methods in your controllers:
public void myMethod(Device
device) {
if (device.isMobile()) {
logger.debug("Hello mobile user!");
} else {
logger.debug("Hello desktop user!");
}
}
This can be modified where appropriate and suitable content organised and views returned. You can also possibly decide that you want to redirect all mobile users to a specific sub-site by using a redirect configured in the webmvc-config.xml file as shown in the Spring Mobile documentation. This might be easier to then manage the content, which could be pulled from the database and managed with suitable menus for both feature phones and smartphones.
Wurfl and Spring by Hand
This practical will look at the basic 'hello world' example from Wurlf, and then move onto integrate Wurfl with the Travelagent example we used previously.
Setup
In addition to having done the Spring
Travelagent practical, you need to download some materials from Wurfl, which we'll go over as
we go along.
Wurfl HelloWorld
Go to the Wurfl page explaining the new Java API using Spring and download the HelloWorld example. Click the link and then unfold the latest version to select, and download the 'wurfl-helloworld antzip' file. Unpack the file into your Eclipse workspace, and rename the folder to 'wurfl-helloworld' so that we can use it for a project.Create project and rearrange folders and files
Create a new 'dynamic web project' in eclipse called
'wurfl-helloworld' and eclipse should point to that folder you put
there by default. Untick the box that asks if you want a web.xml file
created as we'll use the one that comes with the example. When looking
at the project in Navigator view, it should look like this:

As you can see we have some folders and files to rearrange into a workable project. We have all that we need, but just need to put it into the correct place and everything will work fine. Just take your time, while you do this.
First, we need to cut everything from the src/webapp folder and put it into the WebContent folder. Either drag and drop the contents, or cut and paste them.
Second, we need to move the Java code up a directory to two so that Eclipse doesn't complain about nesting issues. Drag the 'net' directory up and onto the 'src' directory so that it ends alongside 'main' and 'test'.
Third, we need to move some of the jars under 'lib' to the
WEB-INF/lib directory. As you can see many of them are related to
spring, and others are one's that we'll already have under tomcat.
Therefore, we don't need to move all of them. The files that we can
delete from 'lib' are: jsp-api-2.0.jar, jstl-1.1.2.jar,
servlet-api-2.4.jar, and standard-1.1.2.jar. We have all of these under
tomcat already. Move everything else to the WEB-INF/lib directory.

You can now switch back to the 'Project Explorer' view in Eclipse and we'll fix the errors that are being flagged up.
Fix the errors and build path for the project
The errors can be fixed by some general project maintenance. We need
to set the 'src' directories for our project. Open the 'properties' for
the project and go to the 'build path' panel, and the 'source' tab.
Highlight and remove any folders that are there. Then use 'add
folder' to select the 'java' and 'test' directories under the 'src'
directory. Set the output to go to the 'bin' directory. Your errors
should now be fixed.
Run the app
You can now run the application in Tomcat and it should work. The
page shown in the browser should tell you which browser you're running.
So far, so good, and you should also start up the OpenWave emulator and
see which page it gets from your helloworld example as shown below.

What's happening here?
If you look through the web.xml file you'll see that there is one servlet handling the app. Looking at the HelloWorld servlet, you'll see that it determines the handset and device based on the request details and then routes the request to the appropriate JSP. The web.xml file also explains that there is a context listener set up from Spring, which then reads in details from the wurfl-default-ctx.xml file. This file, also under WEB-INF, is a regular spring configuration file that instantiates and loads a number of beans used by Wurlf. This is all explained nicely on the Wurfl page explaining the new Java API, so do take the time to look through it more closely to see what's possible.Wurfl and the Spring Travelagent
We can now use this simple setup to add Wurfl to our travelagent example. In order to ensure that nothing happens to your working example, I'd suggest that you copy the working version and rename the copy to 'spring-travelagent-wurfl', which is what I did. Then you can import the project into Eclipse and edit the build.xml file to change the project name so that you know which app is being loaded into tomcat. Do remember to remove the current travelagent deployed to tomcat so that your reading of the console logs doesn't get too confusing.
Make sure that this version runs ok so that we have a baseline to
work from in case any thing goes wrong along the way.
Add the Wurfl jars and config files to the project
Before we go too far we need to add in the Wurfl details to the project so that they are smoothly added as imports when we get to the coding part of the process.
First, we need to copy a number of jars to the WEB-INF/lib directory of our project. You can copy all of the jars across except for spring-aop-2.5.6 and spring-beans-2.5.6 jars, which we have already included as part of the spring.jar in our project.
Second, we need to put the xml configuration details of Wurfl.
However, to ensure that our current web.xml file is kept intact, rename
it to web-orig.xml. Now, copy all of the xml files from the WEB-INF
directory of the helloworld app, and paste them into the WEB-INF
directory of our modified travelagent app. Do the same for the
wurfl.zip. Do the same for the files in the WEB-INF/tld folder too.
Third, rename the current web.xml file to web-wurfl.xml, and change the web-orig.xml file back to web.xml. A pain, I know, but it keeps the process clean as to which one we're using and want to change.
That will do for now and we'll configure the files after we add the new code to the application.
Add two new service classes to handle Wurfl
We'll use code inspired by the example from IllegalArgumentException about using Wurfl as the basis for our own code. The code there is simple, as is ours, but we put in a little more to get more details back about the device. Create a new class called MobileServiceManager in the travelagent.service package and put this code into the class.
import javax.servlet.http.HttpServletRequest;
import net.sourceforge.wurfl.core.Device;
import net.sourceforge.wurfl.core.WURFLManager;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MobileServiceManager implements MobileService {
@Autowired
private WURFLManager wurflManager;
protected final Log logger =
LogFactory.getLog(getClass());
public boolean isMobile(HttpServletRequest request) {
boolean isMobile = false;
String element =
request.getHeader("user-agent");
logger.info("user-agent: "
+ element);
if (element != null) {
Device device =
wurflManager.getDeviceForRequest(request);
String mobileBrowser =
device.getCapability("mobile_browser");
if(mobileBrowser != null) {
isMobile = true;
}
}
// return
StringUtils.isNotBlank(mobileBrowser);
return isMobile;
}
public Device
getDevice(HttpServletRequest request) {
Device device =
wurflManager.getDeviceForRequest(request);
return device;
}
}
This should all work ok, you may need to add some import statements. You will also need to let Eclipse create an interface of MobileService for you, which you can then complete by putting in the isMobile and getDevice methods.
Integrate new classes into CruiseController
We can now use the new service in our app by calling the
MobileServiceManager from the CruiseController and passing the details
onto the JSP view. This code will allow us to check whether we've a
mobile client, and if so, then return the correct device for inclusion
as an attribute to be used by the view. The new code is in bold.
public ModelAndView
handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
logger.info("cc:
user-agent:
"
+
request.getHeader("user-agent"));
MobileServiceManager msm =
new MobileServiceManager();
Device device = null;
boolean myDevice =
msm.isMobile(request);
if (myDevice == true) {
device =
msm.getDevice(request);
}
logger.info("Returning
cruise view");
Map<String, Object>
myModel = new HashMap<String, Object>();
myModel.put("cruises",
this.cruiseManager.getCruises());
myModel.put("device",
device);
return new
ModelAndView("cruise", "model", myModel);
}
Inside of the cruise.jsp file we can add a few lines to display the device details like this:
<h3>Some mobile values</h3>
<p>Device: <%=((Device)request.getAttribute("device")).getId()
%></p>
We also need to add an import statement to the @page declaration for import="net.sourceforge.wurfl.core.Device".
Modify the web.xml file
The next step is to modify the web.xml file so that we tell it about Wurfl. Open the file for editing. Also open up the web-wurfl.xml file, which is the one we used for wurfl-helloworld as it has some code we need to copy into the web.xml file that we'll use for this application.
Wurfl needs to get some values from the request objects, and then hand them off to the MobileServiceManager so that it can determine the handset being used. Therefore we need to use the HelloWorld servlet in our application.
First, copy these details into the web.xml file from the web-wurfl.xml file.
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>net.sourceforge.wurfl.core.example.HelloWorld</servlet-class>
<!-- Default value -->
<!--
<init-param>
<param-name>wurflHolderKey</param-name>
<param-value>net.sourceforge.wurfl.core.WURFLHolder</param-value>
</init-param>
-->
</servlet>
Second, change the name from HelloWorldServlet to 'hello' as it
will run alongside the DispatcherServlet currently in use by our
application. What we want to do is have all requests first go to the
HelloWorldServlet, and then be passed onto the DispatcherServlet. This
means we need to have two servlet-mappings for our application. Copy
the declaration of the <servlet-mapping> for the travelagent so
that it appears twice. Change the mapping of the servlet that goes to
*.htm to be the 'hello' servlet, and change the mapping for the
travelagent to *.do. This will allow us to reroute requests to the
DispatcherServlet without any problems. The two servlets should now
look like this:

Third, copy the <context-param> statement from web-wurfl.xml
to web.xml. This points to another xml file, which is used by Spring to
instantiate and load the beans used by Wurfl for our application.Then
add two more file declarations to the statement so that it looks like
this:

Fourth,copy the three different <taglib> declarations from web-wurfl.xml to web.xml.
Modify the travelagent-servlet.xml file
We need to make a slight change to the travelagent-servlet.xml file so that we can call the DispatcherServlet from the HelloWorldServlet after Wurfl's done its device detection. In the past the file extensions used were .htm so we sent the requests to /cruise.htm and /cruisesearch.htm. This won't work any more, and if you do use them, you'll create an endless loop when you request one of those pages as tomcat routes between the two servlets in a neverending loop. While that may be interesting to see now and again, it's not what you want your web app to do. So change the extention to .do for both. Now your beans should be names /cruise.do and /cruisesearch.do as is consistent with what we set in place for the web.xml file.
Integrate the HelloWorld Servlet
We now need to integrate the HelloWorld servlet into our application so that it replaces the functionality of the DispatcherServlet we previously used.
First, copy the package and the class and paste it into the packages
in our current project.
Second, open the file for editing as we don't want it to do anything except forward requests as appropriate. Comment out the lines checking for the MarkUp, and the line request.getRequestDispatcher....
Third, copy the request.getRequestDispatcher line and past it after itself. Then change it to point to your cruise.do file instead of the location on the line above. Yes, this will mean we don't go to any other page, but you'll get the idea of how this works. It should now look like this:
request.getRequestDispatcher("cruise.do").forward(request,response);
With this in place we now route the request back to the
CruiseController and return pages as expected.

Beyond the basics
Ok, yes, this is incomplete. In order for this work fully we need to make a few more changes.
First, we need to have HelloWorld check for all destination requests, and not just, as now send everything to the one page.
Second, we need to consider whether we need to have anything special
done with the navigation and views of the pages determined to be
mobile. So far we've not done this, but we now have a framework in
place to handle this and could use screensize, colours, etc to
determine which view a request received. You'll find more about this in
the dotMobi Developer's Guide.
You can also find some good
discussions on this at the Little Spring Design
blog and the Design4Mobile
site they maintain.
[an error occurred while processing this directive]