Page 1 of 3 123 LastLast
Results 1 to 10 of 29

Thread: Using multiple annotated controllers in a Portlet

  1. #1
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default 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 at 03:17 AM. Reason: added current spring 2.5 config
    Hans Westerbeek
    Software Engineer

  2. #2
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default

    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?
    Hans Westerbeek
    Software Engineer

  3. #3
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default

    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
    Hans Westerbeek
    Software Engineer

  4. #4
    Join Date
    Sep 2004
    Location
    Arizona, USA
    Posts
    383

    Default

    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.

  5. #5
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default

    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?
    Hans Westerbeek
    Software Engineer

  6. #6
    Join Date
    Sep 2004
    Location
    Arizona, USA
    Posts
    383

    Default

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

  7. #7
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default

    Quote 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!
    Hans Westerbeek
    Software Engineer

  8. #8
    Join Date
    Sep 2004
    Location
    Arizona, USA
    Posts
    383

    Default

    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.

  9. #9
    Join Date
    Oct 2005
    Location
    Amsterdam
    Posts
    123

    Default

    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?
    Hans Westerbeek
    Software Engineer

  10. #10
    Join Date
    Sep 2004
    Location
    Arizona, USA
    Posts
    383

    Default

    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!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •