5

I have a Spring mvc (3.1.1) app, and I want to define conditions beyond what's available in RequestMapping. I have a couple of things I want to use it for.

First, it would be nice if I could show a different home page for different user types:

@Controller
public class HomepageController {

    @RequestMapping(value = "/")
    @CustomCondition(roles = Guest.class)
    public String guestHome() { /*...*/ }

    @RequestMapping(value = "/")
    @CustomCondition(roles = Admin.class)
    public String adminHome() { /*...*/ }

}

Second, I want the app to function both as a web site and as a REST service (e.g. for mobile apps), so I'd want to let the website access both html and json actions, and let the service (different subdomain) only access json actions (some kind of @CustomCondition(web = true) which only matches website urls)

Can this work for any of the two uses I'm planning?

I found very little documentation about custom conditions, but I did find one example that implements custom conditions which might be what I want, but it uses a @Configuration class instead of the XML configuration which I'm using and I don't want to move my entire spring xml definitions to a @Configuration class.

Can I define a customMethodCondition for RequestMappingHandlerMapping in the XML?

I tried subclassing RequestMappingHandlerMapping and override getCustomMethodCondition, to return my custom RequestCondition, but it didn't work - getMatchingCondition() in my condition didn't fire.

Any help would be greatly appreciated!

UPDATE

I read a little more, and it looks like RequestMappingHandlerMapping is a new class (since ver 3.1).

What happens in my app is that the @Configuration that tries to override and thereby redefine the requestMappingHandlerMapping bean actually works, but the url mappings (@RequestMapping methods in @Controllers) seem to get processed twice, once by the subclass ExtendedRequestMappingHandlerMapping and once by the original RequestMappingHandlerMapping --first with a custom condition, and then again without it.

Bottom line is my custom conditions are simply ignored.

This is supposed to be an advanced pattern, but IMO it should be quite common...

Comments anyone?

ori
  • 7,817
  • 1
  • 27
  • 31

2 Answers2

3

Spring MVC already provides a mechanism for distinguishing between json and html, the RequestMapping annotation takes a consumes attribute which looks at the content type of the request...

// REST version, Content-type is "application/json"
@RequestMapping(value = "/", consumes = "application/json")
public void myRestService() {
...

// HTML version, Content-type is not "application/json"
@RequestMapping(value = "/", consumes = "!application/json")
public void myHtmlService() {
...

Another way to use the same url but have distinct methods is with the param or headers attribute...

// the url is /?role=guest
@RequestMapping(value = "/", param = "role=guest")
public void guestService() {

// the url is / with header role=admin
@RequestMapping(value = "/", headers = "role=admin")
public void adminService() {

I would think you would want distinct urls for security. Typically, with something like Spring Security, you would put all of the admin functionality under /admin and let the framework manage it all...

<http auto-config="true">
  <intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
...

Would this be sufficient for your use case(s)?

hyness
  • 4,785
  • 1
  • 22
  • 24
  • 1
    My goal was to have programmatic access to the mapping mechanism in order to enforce certain restrictions. The filters you mentioned are limited to request properties, and therefore don't solve my problem. – ori Jun 09 '12 at 13:17
1

If you have extended RequestMappingHandlerMapping(say ExtendedRequestMappingHandlerMapping) you have to register this new mapping a little differently in application context xml.

You cannot use <mvc:annotation-driven/> to configure the Spring MVC as that defines it's own handlerMapping internally, you can instead do something along these lines(or follow the approach in the link with @Configuration that you have provided):

<bean name="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="webBindingInitializer">
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="conversionService" ref="conversionService"></property>
            <property name="validator">
                <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
            </property>
        </bean>
    </property>
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
        </list>
    </property>
</bean>

<bean name="handlerMapping" class="..ExtendedRequestMappingHandlerMapping">
</bean>

This should ensure that your mapping takes effect and will ensure that the appropriate handler method is found by the handlerAdapter component.

Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125