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).
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.
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/
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With