Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC: Resolving the view based on User-Agent

Spring Version: 2.5.6

I want to resolve the view to a specific velocity file based on the value of the User-Agent header.

My current line of thinking is an implementation similar to the UrlBasedViewResolver such that the user-agent value is Map'd (via context) to a specific directory(value) based on a matching a regular expression(key).

I am almost certain there is an easier way.

A similar question was previously posted regarding Theme determination based on User-Agent. However, my understanding is that Themes relate more to static (css,js) content, not which file handles the actual response construction (HTML,XML,etc).

like image 723
bearded_devil Avatar asked Dec 08 '09 17:12

bearded_devil


3 Answers

There is an other option suggested here

However I resolved Extending a ContentNegotiatingViewResolver and overriding the resolveViewName method, I called my ViewResolver HttpHeaderParamViewResolver. The extended method looks something like this:

@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
    //Get the HTTP Header param "User-Agent"
    String headerParamValue = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest().getHeader(headerParam);

    viewName = setViewName(viewName, headerParamValue);

    return super.resolveViewName(viewName, locale);
}

Where headerParam="User-Agent" (or any other HTTp Header parameter you like, this is defined in the bean xml), after that you evaluate it and determine the viewName. In My case the HttpHeaderParamViewResolver can be configured with a Map where the key is a prefix to be appended to the actual viewName and the value is a RegExp that will be used to evaluate the value of the header param. It looks something like this in the App Context XML:

<bean id="HttpHeaderViewResolver" class="com.application.viewresolver.HttpHeaderParamViewResolver">
    <property name="viewResolvers">
        <list>
            <ref bean="tilesViewResolver"/>
        </list>
    </property>
    <property name="headerParam" value="User-Agent"/>
    <property name="viewPrefixPattern">
        <map>
            <entry>
                <key>
                    <value>mobile-webkit</value>
                </key>
                <value>iPhone.*Apple.*Mobile.*Safari</value>
            </entry>
            <entry>
                <key>
                    <value>mobile-bb</value>
                </key>
                <value>BlackBerry([0-9]{0,4})([a-zA-Z])?</value>
            </entry>
        </map>
    </property>
</bean>

That way the if my controller calls a view called userDetails and is accessing the application with an IPhone the first pattern catchs it and appends the mobile-webkit suffix so the view is now mobile-webkit-userDetails and its then passed to the tilesViewResolver which generates the actual view.

I explored a lot of possibilities and I think this is the easiest and most flexible I was able to came up with. In this case the ability to choose an entire different view was critical because we support a wide variety of user agents, from WAP to IPhone 4 and WebKit capable mobiles, so views change dramatically from user agent to user agent. Other advantage is that you no longer require to handle this issue on the view since you can have views as specialized as you like. Other bright side is that you can implement this quite easily without having to remove or change the view resolvers you might already have since ContentNegotiatingViewResolver has the ability to delegate the view call to other view resolvers in the specific sequence you define.

The down side is that you may be tempted to over specialize the views and end up with a ton of view files rendering the app a maintainable nightmare.

Hope it is helpful.

like image 53
Chepech Avatar answered Oct 14 '22 21:10

Chepech


I had the same problem a few months back!

On our mobile project (using Spring 2.5.6) we ended up using an interceptor with our SimpleUrlHandler. This caught all incoming requests and add -m.jsp to the end of any mobile requests.

It involved two steps:

1) declaring an interceptor to our standard URL Mapper:

 <bean id="handlerMapping"
 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
 <!--   This interceptor catches all
 requests and redirects them to portal
 or mobile html content.
 --> 
<property name="interceptors">    <list>
      <ref bean="MultiViewController"/>    </list> </property>

and 2) implementing the Interceptor, which looked for the word 'Mobile' in the user-agent.

public class MultiViewController extends HandlerInterceptorAdapter {

I talk about it in more detail on my blog (about the new exciting world of mobile web development) post: http://plumnash.com/it/iphone-web-development-using-spring/

like image 34
Peter Avatar answered Oct 14 '22 22:10

Peter


An alternative that doesn't require configuration in a ViewResolver might involve a top-level Velocity file, then conditionally parsing sub-files that has something like the following.

#if ($userAgent1)
  #parse ("user-agent-1.vm")
#elseif ($userAgent2)
  #parse ("user-agent-2.vm")
#end

However, implementing a new or extending an existing ViewResolver is a pretty simple solution and would be the way I'd go with.

like image 1
dbrown0708 Avatar answered Oct 14 '22 21:10

dbrown0708