Announcement Announcement Module
Collapse
No announcement yet.
Using multiple annotated controllers in a Portlet Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using multiple annotated controllers in a Portlet

    Hi all,

    I am migrating a 2.0 spring-portlet-mvc app my colleague wrote to an annotation based 2.5 version.

    My portlet is only usable in Portlet 'view' mode.

    Right now, I have an application that hosts multiple controllers in one portlet. These controllers are mapped like so:
    (2.0 type config follows)
    Code:
    <bean id="parameterMappingInterceptor" class="org.springframework.web.portlet.handler.ParameterMappingInterceptor"/>
    	<bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
            <property name="order" value="10"/>
    		<property name="interceptors">
    			<list>
    				<ref bean="parameterMappingInterceptor"/>
    			</list>
    		</property>
    		<property name="portletModeParameterMap">
    			<map>
    				<entry key="view">
    					<map>
    value-ref="contactCustomerController"/>
    						<entry key="editResult" value-ref="editResultController"/>
    						<entry key="saveResult" value-ref="saveResultController"/>
    						<entry key="editContactPerson" value-ref="editContactPersonController"/>
    						<entry key="saveContactPerson" value-ref="saveContactPersonController"/>
    						
    					</map>
    				</entry>
    			</map>
    		</property>
    	</bean>
    I think this could be improved in a 2.5-like annotation based application. After looking at the petportal-pets portlet carefully I thought that it would be better to create two controller beans:

    ResultController, with two methods: showForm() and save()
    ContactPersonController, with two methods: showForm and save()

    So this is what I came up with as 2.5-style config:
    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:p="http://www.springframework.org/schema/p" 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">
    
    	<context:annotation-config/>
    	
    	<bean class="com.mydomain.ResultController" />
    	
    	<bean class="com.mydomain.ContactPersonController" />
    	
    
    	
    	
    	<bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    		<property name="interceptors">
    			<list>
    				<bean class="org.springframework.web.portlet.handler.ParameterMappingInterceptor">
    							
    				</bean>
    			</list>
    		</property>
    		
    	</bean>
    
    
    
    	<!-- Default ExceptionHandler -->
    	<bean id="defaultExceptionHandler" class="org.springframework.web.portlet.handler.SimpleMappingExceptionResolver">
        <property name="order" value="10"/>
    		<property name="defaultErrorView" value="error"/>
    		<property name="exceptionMappings">
    			<props>
    				<prop key="javax.portlet.PortletSecurityException">unauthorized</prop>
    				<prop key="javax.portlet.UnavailableException">unavailable</prop>
    			</props>
    		</property>  
    	</bean>
    
    	<bean id="messageSource"
    		class="org.springframework.context.support.ResourceBundleMessageSource">
    		<property name="basenames">
    			<list>
    				<value>messages</value>
    			</list>
    		</property>
    	</bean>
    
    	<!-- Default View Resolver -->
    	<bean id="viewResolver"
    		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="cache" value="false" />
    		<property name="viewClass"
    			value="org.springframework.web.servlet.view.JstlView" />
    		<property name="prefix" value="/WEB-INF/jsp/" />
    		<property name="suffix" value=".jsp" />
    	</bean>
    </beans>
    Both beans are annotated with @Controller and @RequestMapping("VIEW") at type level

    The petportal-pets portlet uses just a single controller bean. My application is a little bit bigger so I would like to use several controller beans.

    Now what I am confused about is how to let Spring know which controller-method to invoke. My guess is that I need to do something to notify spring of my portletModeParameterMap, and in such a way that it will invoke the correct controller method.

    Right now, spring complains, quite understandably:

    (exhaustively long package names removed)
    Code:
    java.lang.IllegalStateException: Cannot map handler [ResultController#0] to key [view]: There's already handler [ContactPersonController@9be2b5] mapped.

    I think I have found some pointers at http://static.springframework.org/sp...erMapping.html
    but I can't quite get my head around what is being said there...

    So I suppose my question is, how do I supply my 2.0 parameter mappings in 2.5 style? It is quite unfortunate that petportal does not contain an app that spans multiple controller beans

    Do I need to add extra annotations to avoid this exception or am I heading into the wrong direction? Or, should i supply more config-info in my portlet's mvc-config file?

    Regards,
    Hans
    Last edited by mirror303; Dec 21st, 2007, 03:17 AM. Reason: added current spring 2.5 config

  • #2
    And also, a related question I suppose is:

    how do I tell Spring which annotated method is my default 'index' controller?

    In 2.0 style config this is done like so:
    Code:
    <bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
            <property name="order" value="20"/>
    		<property name="portletModeMap">
    			<map>
    
    				<entry key="view"><ref bean="listContactPersonController" /></entry>
    			</map>
    		</property>
    	</bean>
    But how do I do it in 2.5, to let's say a method called list() in my annotated ResultController bean?

    Comment


    • #3
      To (partially) answer my own question...

      It seems that I misunderstood a fundamental portlet concept. The main reason I required multiple controllers was that I should have moved some functionality (the part that selects an item for another portlet to work on) to a different portlet. That eliminates the urgent need for multiple controllers.

      Having said that, I am left with one controller which gets quite big, bigger than what I would usually like in a non-portlet spring-mvc application. But I suppose that is just the way things are... Right?

      Hans

      Comment


      • #4
        To answer your first question, you need to include an appropriate @RequestMapping annotation on each method that will handle a request. Something like the following:

        Code:
        @RequestMapping(params = "action=editResult")
        ... editResult(...) {
        ...
        
        @RequestMapping(params = "action=saveResult")
        ... saveResult(...) {
        ...
        To answer your second question, for the "default" mapping for the View mode, just specify the @RequestMapping annotation without any parameters:

        Code:
        @RequestMapping
        public String list(...) {
        ...
        To answer your third question, you are correct that with the Annotation-based mapping, you must put all the methods for the given Portlet Mode into a single class. I agree this is not ideal for more complicated portlets and we are exploring making the annotation-based mapping more flexible in the future. For a more complex portlet application, you may want to stick with existing way of doing things.

        Comment


        • #5
          John,

          Thanks for your answer. Right now none of my portlets are too complex but it's good to hear you guys are looking into the matter.

          Does it seem like something that is very complex and therefore can not be expected in a release anytime soon? Or could it appear in a minor version release of Spring?

          Comment


          • #6
            Not sure at this point.

            Juergen and I had some good discussions about this at TSE2007, especially as it related to support for the Portlet 2.0 (JSR 286) specification. Since the new spec adds two new phases, the mappings and Interfaces for doing things the old way can get pretty complicated and the Annotations seem like a good way to simplify things.

            We didn't really talk about changing this in 2.5.x -- more about what we do in 3.0. So at this point I wouldn't expect any significant changes until then, but you never know...

            Comment


            • #7
              Originally posted by johnalewis View Post
              Not sure at this point.

              Juergen and I had some good discussions about this at TSE2007, especially as it related to support for the Portlet 2.0 (JSR 286) specification. Since the new spec adds two new phases, the mappings and Interfaces for doing things the old way can get pretty complicated and the Annotations seem like a good way to simplify things.
              I haven't tried it yet but my request seems to have been fulfilled

              I just read: http://static.springframework.org/sp.../changelog.txt

              One line in the changelog states: @RequestMapping's "params" attribute supported at type level for Portlets, mapping mode+params onto specific handler

              So it seems that i can use an extra param to map onto seperate controller classes. Nice!

              Comment


              • #8
                Yep, in talking about how to make things more flexible for 3.0 we identified some changes that would be appropriate in 2.5 -- Juergen is such a stud that he got them into the next point release. So 2.5.2 definitely makes the annotation-based dispatching attractive for JSR 168 portlets.

                Comment


                • #9
                  Hi John,

                  It seems that I misread the release notes...

                  We haven't been able to implement using multiple controllers through annotations. Yes, the framework 'discovers' every controller and each mapping within a controller but at at runtime it will still only call methods on the 'default' controller, i.e. the one that handles the 'home' page for the portlet.

                  If possible, can you (John) give us an outline of how you would move let's say move the viewPet method from the petportal sample to a a ViewPetController class with a single handler method?

                  Comment


                  • #10
                    Sorry for the slow response to this one.

                    Splitting the handler methods for a single portlet mode across multiple classes does work, but the configuration is still a bit brittle. The current issue has to do with the order in which the Controller classes are mapped into the DefaultAnnotationHandlerMapping. The key is that the class that contains the "default" handler method (i.e. the one that doesn't specify any request parameters) needs to get mapped last.

                    Here is an example from the sample code I am working on right now.

                    BooksController class:

                    Code:
                    @Controller
                    @RequestMapping("VIEW")
                    @SessionAttributes("book")
                    public class BooksController {
                    
                    	...
                    
                    	@RequestMapping
                    	public String listBooks(Model model) {
                    		...
                    	}
                    
                    	@RequestMapping(params="action=viewBook")
                    	public String viewBook(@RequestParam("book") Integer id, Model model) {
                    		...
                    	}
                    
                    	...
                    }
                    BooksAddController class:

                    Code:
                    @Controller
                    @RequestMapping("VIEW")
                    @SessionAttributes("book")
                    public class BooksAddController {
                    
                    	...
                    	@RequestMapping(params="action=addBook")
                    	public String showAddBookForm(Model model) {
                    	...
                    	}
                    
                    	...
                    }
                    So, these will only get mapped correctly if the BooksAddController appears first and the BooksController appears second in the bean declarations. Like this:

                    Code:
                    	<bean id="booksAddController" class="sample.portlet.BooksAddController"/>
                    
                    	<bean id="booksController" class="sample.portlet.BooksController"/>
                    
                    	<bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
                    You can verify the mapping order if you enable debug-level in your logging. You should see something like this:

                    Code:
                    2008-05-21 09:18:42,916 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksAddController@e0ada6]>
                    2008-05-21 09:18:42,917 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksAddController@e0ada6]>
                    2008-05-21 09:18:42,917 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,917 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,918 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,918 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,918 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,918 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    2008-05-21 09:18:42,918 DEBUG [org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping] - <Mapped key [view] onto handler [sample.portlet.BooksController@7bacb]>
                    I'll work with Juergen to see if we can make the DefaultAnnotationHandlerMapping even more flexible in this area, so that the ordering is not so critical, but this is how it works for now.

                    Hope that helps!

                    Comment


                    • #11
                      thanx a bunch, will try soon!

                      Comment


                      • #12
                        FYI: Looks like this limitation should be eliminated in 2.5.5, which should be released next week.

                        The parameter predicate matching will now check the most specific predicate (i.e. the ones with the highest number of parameter conditions) first. For predicates with the same number of conditions, the order of controller definitions is still relevant.

                        Comment


                        • #13
                          Great! I already had a hunch that that situation wasn't going to last long
                          Last edited by mirror303; May 29th, 2008, 02:01 PM.

                          Comment


                          • #14
                            I have a need now for Multiple DefaultAnnotationHandlerMapping beans.

                            For a set of Annotation Handlers, I use security Interceptors.

                            For other set of Annotation Handlers, I don't want them to go thru Security Interceptors.

                            Is this something possible ?

                            Comment


                            • #15
                              Hopefully you can see the inherent conflict in having multiple default handler mappings. So, no, this is not directly possible.

                              You either need to create more specialized handler mapping that does what you need, or you need to get your Interceptors themselves to be selective about what they are securing -- the latter is the more common approach for security filtering/intercepting.

                              If you want to go the route of specializing the HandlerMapping, you might want something like what is described here: http://www.scottmurphy.info/spring_f...r_interceptors -- but for portlets instead of servlets.

                              Comment

                              Working...
                              X