views:

609

answers:

4

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).

+1  A: 

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.

dbrown0708
This would work if there 1) wasn't literally tens of thousands of user agents out there 2) user agent couldn't be spoofed. In general, if you want to detect the user agent, you're most likely already doing something wrong.
Esko
@Esko "most likely already doing something wrong"? There are a lot of requirements out there, and not all of them involve all ~ ten thousand user-agents.
cap3t0wn
Occasionally you have to make sacrifices to make things work with certain browsers. *cough*Internet Explorer*cough*
Alan Krueger
+1  A: 

I am going with a custom view resolver as suggested in comments. (and upgrading my app to Spring 3.0.0)

cap3t0wn
UPDATE: Spring 3.0 provides an out-of-box solution using the new ContentNegotiatingViewResolver.
cap3t0wn
A: 

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/

Peter
A: 

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.

Chepech
This works only on Spring 3.0 since there is no ContentNegotiatingViewResolver in previous versions
Chepech