Announcement Announcement Module
Collapse
No announcement yet.
Method level RequestMapping annotations for more REST style actions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Method level RequestMapping annotations for more REST style actions

    Hi,
    I'm toying with annotation driven controller configuration and I really, really like it.

    Now I just have one issue: I'd like to be able to have more (g)rails like URLs with the pattern /controller/action[/id] but that doesn't seem to be possible as I don't seem to be able to specify a pattern like "action/*" in the method level RequestMapping annotation. If I do, that method never gets mapped. Is there a simple solution to that short of writing my own variant of org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter (which somehow seems like a bad idea) and isn't that something that would be more generally useful?

    Any suggestions would be greatly appreciated.

    Jörg

  • #2
    I'm able to do this and it works fine mapping all html requests (below). Have you tried putting two asterisks instead of just one? I also jus tried "/delete/*" and "/delete/**" and they both intercepted a "/delete/person.html?id=1" request.

    Have you tried "/**/action/*"?

    Code:
    @RequestMapping(value="/**/*.html")

    Comment


    • #3
      I actually just tried "/**/action/*" in a webapp I have setup already and seemed like it was working ok. It intercepted '/action/person.html', '/foo/bar/action/person.html', '/foo/bar/action/12', etc.

      Comment


      • #4
        Thanks David! That's good news - but I actually think now that the controller mapping is the problem. I've always tried with relative paths at the method level RequestMapping and mapping the controller at the type level and it seems that then it doesn't work. Will need to further test that.

        Jörg

        Comment


        • #5
          Solved

          OK, it was my fault. The mappings didn't work becuase I didn't explicity add org.springframework.web.servlet.mvc.annotation.Def aultAnnotationHandlerMapping as a bean in my context but inadvertently added a org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter bean instead which kind of made it work half.

          Now if only I wouldn't need to specify '/**/action/*' at th emethod level but 'action/*' I would be in heaven ...

          Comment


          • #6
            sample code to extract id from url

            Just if anybody else cares: I've created a little interceptor to extract ids from the URL in REST style URLs following the pattern /controller/action/id. It's not tested production quality code but maybe somebody else can draw some inspiration from it.

            I have a dispatcher servlet context that looks something like this

            Code:
            <?xml version="1.0" encoding="UTF-8"?>
            
            <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
            	xsi:schemaLocation="http://www.springframework.org/schema/beans 
            		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
            		http://www.springframework.org/schema/context 
            		http://www.springframework.org/schema/context/spring-context-2.5.xsd">
            
            	<bean id="handlerMappingTemplate" abstract="true">
            		<property name="interceptors">
            			<list>
            				<bean class="some.package.IdFromUrlInterceptor" />
            			</list>
            		</property>
            	</bean>
            	<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" parent="handlerMappingTemplate" />
            	<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" parent="handlerMappingTemplate" />
            			<context:component-scan base-package="some.package.controllers" />
            
                    ...
            
            </beans>

            and the interceptor looks like this

            Code:
            package some.package;
            
            import java.lang.reflect.Method;
            import java.util.Map;
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;
            
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            
            import org.springframework.core.CollectionFactory;
            import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
            
            /**
             * Interceptor that tries to read an id value from URLs of the pattern /controller/action/id and tries to then set
             * the read value on the passed in handler (generally a Controller class) by calling a method with the signature setId(String id) if existent.
             * 
             * @author joerg
             *
             */
            public class IdFromUrlInterceptor extends HandlerInterceptorAdapter
            {
                private final Map<String, Method> handlerMethodCache = CollectionFactory.createConcurrentMapIfPossible(16);
                private final static Pattern idPattern = Pattern.compile("^/(\\S*)/(\\S*)/(\\S*)");
            
                @Override
                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
                {
            
                    Matcher matcher = idPattern.matcher(request.getPathInfo());
            
                    if (matcher.matches())
                    {
                        try
                        {
                            Method idSetter;
                            if (handlerMethodCache.containsKey(handler.getClass().getName()))
                                idSetter = handlerMethodCache.get(handler.getClass().getName());
                            else
                            {
                                idSetter = handler.getClass().getMethod("setId", new Class[]{String.class});
                                handlerMethodCache.put(handler.getClass().getName(), idSetter);
                            }
            
                            if (idSetter != null)
                                idSetter.invoke(handler, new Object[]{matcher.group(3)});
                        }
                        catch (Exception e)
                        {
                            //noop
                        }
                    }
                    return true;
                }
            }
            You can then write a controller like this

            Code:
            @Controller
            @Scope("prototype")
            public class TestController
            {
            
                private String id;
                
                public String getId()
                {
                    return id;
                }
            
                public void setId(String id)
                {
                    this.id = id;
                }
            
                @RequestMapping("/**/hello/*")
                public ModelAndView hello(HttpServletRequest request, HttpServletResponse response) throws Exception
                {
                    System.out.println("id: " + getId());
                    return new ModelAndView("hello");
                }
            }
            So as long as your controller has a method with the signature void setId(String id) you then should be able to get the extracted id by the time the action is called. Hope this all makes sense.

            If somebody knows of a better way to achieve this please let me know.
            Last edited by joeslow; Jun 10th, 2008, 10:09 AM.

            Comment

            Working...
            X