[an error occurred while processing this directive]

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.

        <dependency>
             <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:

wurfl helloworld folder layout at beginning of project

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.

Folders after fixing their arrangement

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.

Hello world loaded in different browsers

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:

Use two servlets in wurfl apps

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:

Include multiple files in contextParams

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.

travelagent pages with wurfl in place

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]