2 min read

Grails Mobile Browser Detection with WURFL

Here's a quick and cheap solution for detecting and redirecting clients on mobile browsers to mobile versions of your Grails application using WURFL.
Grails Mobile Browser Detection with WURFL
Photo by ROBIN WORRALL / Unsplash

Here's a quick and cheap solution for detecting and redirecting clients on mobile browsers to mobile versions of your Grails application using WURFL.

WURFL is basically an XML file containing information about different mobile browsers and their capabilities such as WAP, WML, PNG, J2ME, etc. support.

*** Disclaimer: WURFL is not always 100% correct on its detection and capability look-up. WURFL depends on users to report resource data and therefore can be prone to missing or incorrect resource definitions.

Having said that... here's how to integrate mobile browser detection into your Grails application.

First, download the latest WURFL resource file and place it in your classpath. I put mine in /src/java/.

Since the root WURFL resource file's main purpose is to define the capabilities of mobile browsers, we'll need to download a patch file (found here) to have it be able to recognize regular web browsers.

Download the patch file and save it to your classpath as well.

Next, we'll grab the WURFL Java API and add it to our "/lib" folder. The link below is to the Hello World sample application but we only need the wurfl-x.x.x-x.jar.

The backport-util-concurrent.jar for your particular Java version is also required and should be added to the /lib folder.

Download: wurfl-latest.zip

Download: web_browsers_patch.xml

Download: wurfl-x.x.x-x.jar

Download: backport-util-concurrent.jar

Now we'll configure Grails/Spring to load the required classes.

Edit the Spring beans conf at /grails-app/conf/spring/resources.groovy

// Place your Spring DSL code here 
beans = { 
  ... 
  xmlns util: "http://www.springframework.org/schema/util" 
  
  // WURFL 
  // Groovy resource implementation of classpath:spring-wurfl.xml
  wurflResource(org.springframework.core.io.ClassPathResource, "/wurfl-latest.zip") { } 
  wurflSpringResource(net.sourceforge.wurfl.core.resource.SpringXMLResource, ref("wurflResource")) { } 

  // Begin Patch 
  wurflPatch(org.springframework.core.io.ClassPathResource, "web_browsers_patch.xml") { } 
  wurflPatchSpringResource(net.sourceforge.wurfl.core.resource.SpringXMLResource, ref("wurflPatch")) { } 
  util.list(id: "patchResourceList") { 
    ref(bean: "wurflPatchSpringResource") 
  } 
  wurflPatchResources(net.sourceforge.wurfl.core.resource.WURFLResources, ref("patchResourceList")) { } 
  // End Patch 

  wurflModel(net.sourceforge.wurfl.core.resource.DefaultWURFLModel, ref("wurflSpringResource"), ref("wurflPatchResources")) { } 
  wurflMatcherManager(net.sourceforge.wurfl.core.handlers.matchers.MatcherManager, ref("wurflModel")) { } 
  wurflDeviceProvider(net.sourceforge.wurfl.core.DefaultDeviceProvider, ref("wurflModel")) { } 
  wurflService(net.sourceforge.wurfl.core.DefaultWURFLService, ref("wurflMatcherManager"), ref("wurflDeviceProvider")) { } 
  wurflManager(net.sourceforge.wurfl.core.DefaultWURFLManager, ref("wurflService")) { } 
  wurflUtils(net.sourceforge.wurfl.core.WURFLUtils, ref("wurflModel")) { } 
  wurflHolder(net.sourceforge.wurfl.core.DefaultWURFLHolder, ref("wurflManager"), ref("wurflUtils")) { } 
  ... 
}

Next, we'll create a simple service to detect the browser type from the request.

package org.test.services 

import org.apache.commons.lang.StringUtils 
import net.sourceforge.wurfl.core.Device 

class MyWurflService { 
  // Inject the WurflManager we defined in resources.groovy 
  def wurflManager 

  def Boolean isMobile(request) { 
    def Device device = this.getDevice(request) 
    def String iwdCapability = device.getCapability("mobile_browser") 
    // Debugging System.out.println("Capability of 'mobile_browser': ${iwdCapability}") 
    return StringUtils.isNotBlank(iwdCapability) 
  } 

  def Device getDevice(request) { 
    Device device = wurflManager.getDeviceForRequest(request) 
    def capabilities = device.getCapabilities() 
    // Debugging capabilities.each { System.out.println(it) } 
    return device 
  } 
}

And finally, we'll test our service with a simple redirection.

package org.test.controllers 

class TestController { 
  // Inject org.test.services.myWurflService 
  def myWurflService 

  def beforeInterceptor = { 
    if (null == params.action || params.action == 'index') { 
      if (myWurflService.isMobile(request)) { 
        redirect(action: 'mobile') 
      } 
    } 
  }

  def index = { } 

  def option = { } 
  
  def mobile = { } 
}

The controller catches requests to /text/index and calls the myWurflService.isMobile() method passing the request object.

If WURFL determines that the request is from a mobile browser, the user is redirected to the /test/mobile action.

You can extend functionality even further by detecting what markup is used on the device making the request and redirecting accordingly.

You can easily test your app using the User Agent Switcher extension in Firefox.