views:

35

answers:

1

I'm developing a 'user settings portlet' where users can effect the search behaviour of multiple other portlets. The way I'd like to do it is by a shared bean. All portlets are in different wars, and I'd rather avoid having all wars in a single ear and using a parent application context, so deployment of portlets can be made autonomously, but haven't had much luck in finding any information on how to do it.

I have followed this blog post to try to deploy an ear file with the wars in them, but after many hours of wrestling I've come no closer to solving my problem...

The directory structure looks like this:

portlets
|--- ear
|    \--- src/main/application/META-INF/application.xml
|
|--- jar (contains UserSettings.java)
|    \--- src/main/resources/beanRefContext.xml
|    \--- src/main/resources/services-context.xml
|    \--- src/main/java/com/foo/application/UserSettings.java
|
|--- messagehistory (war, portlet 1)
|    \--- [...]
|
|--- settings (war, portlet 2)
|   \--- [...]
|
\--- pom.xml

I've tried setting scope="session" like the following:

<bean id="userSettings" class="com.foo.application.UserSettings" scope="session">
    <aop:scoped-proxy />
</bean>

But then when I deploy the ear I get java.lang.IllegalStateException: No Scope registered for scope 'session'.

This is the controller för the history portlet, where users can search for message history, with restrictions from the settings portlet. The controller for the settings portlet is identical.


package com.foo;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletSession;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.portlet.bind.annotation.ActionMapping;

import com.foo.application.UserSettings;
import javax.annotation.PostConstruct;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ServletContextAware;

@Controller
@SessionAttributes({"searchQuery", "searchResults"})
@RequestMapping("VIEW")
public class ViewHistory extends ContextLoader implements ServletContextAware {
    private UserSettings userSettings;
    private ServletContext servletContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @PostConstruct
    public void init() {
        ApplicationContext ctx = loadParentContext(servletContext);
        servletContext.setAttribute(LOCATOR_FACTORY_KEY_PARAM, "ear.context");
        userSettings = (UserSettings) ctx.getBean("userSettings");
    }

    @ModelAttribute("userSettings")
    public UserSettings createUserSettings(Model model) {
        model.addAttribute(userSettings);
    }

    @RequestMapping
    public String doSearch(Model model, PortletSession portletSession) {
        return "view";
    }

    @ActionMapping(params = "action=search")
    public void searchAction(
            Model model,
            ActionRequest request, ActionResponse response,
            BindingResult bindingResult, SessionStatus status)
    {
        // do nothing
    }
}

The web.xml file for both wars (they are identical) looks like this:

<?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"&gt;

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

    <context-param>
        <param-name>parentContextKey</param-name>
        <param-value>ear.context</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

    <filter> 
        <filter-name>springFilter</filter-name> 
        <filter-class>
            org.springframework.web.filter.RequestContextFilter
        </filter-class>
    </filter> 
    <filter-mapping> 
        <filter-name>springFilter</filter-name> 
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet>
        <servlet-name>ViewRendererServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>ViewRendererServlet</servlet-name>
        <url-pattern>/WEB-INF/servlet/view</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
A: 

Turns out it was really easy just using Spring's @EventMapping annotation for plain JSR 286 eventing. No ear required and no parent application context. I just have my UserSettings.java in a separate jar project and include it as a dependency to both wars.

The controller for the search portlet looks like this:


package com.foo;

import com.foo.event.UserSettings;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.EventRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.portlet.bind.annotation.ActionMapping;

import javax.portlet.Event;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.portlet.bind.annotation.EventMapping;

@Controller
@RequestMapping("VIEW")
public class ViewHistory {
    private UserSettings userSettings = new UserSettings();

    @ModelAttribute("userSettings")
    public UserSettings createUserSettings(Model model) {
        return userSettings;
    }

    @RequestMapping
    public String doSearch(Model model) {
        return "view";
    }

    @ActionMapping(params = "action=search")
    public void searchAction(
            Model model,
            ActionRequest request, ActionResponse response,
            @ModelAttribute("userSettings") UserSettings userSettings,
            BindingResult bindingResult, SessionStatus status)
    {
        // do something
    }

    /**
     * Spring calls this whenever an event is received.
     * Can be limited to certain event.
     */
    @EventMapping
    public void handleEvent(EventRequest request) {
        Event event = request.getEvent();

        if (event.getName().equals("UserSettings")) {
            userSettings = (UserSettings)event.getValue();
        }
    }
}

...and for the settings portlet:


package com.foo;

import com.foo.event.UserSettings;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.portlet.bind.annotation.ActionMapping;

import javax.xml.namespace.QName;
import org.springframework.web.bind.annotation.ModelAttribute;

@Controller
@RequestMapping("VIEW")
public class ViewSettings {
    private QName qname = new QName("http:foo.com/usersettings", "UserSettings");

    @ModelAttribute
    public UserSettings createUserSettings(Model model) {
        return new UserSettings();
    }

    @ActionMapping(params = "action=search")
    public void searchAction(
            Model model,
            ActionRequest request, ActionResponse response,
            @ModelAttribute("userSettings") UserSettings userSettings,
            BindingResult bindingResult, SessionStatus status)
    {
        // as soon as an action is triggered (save button is pressed or 
        // whatever), send the modified UserSettings instance as an 
        // event to the search portlet (actually any portlet, but I 
        // only have one that will read events).
        response.setEvent(qname, userSettings);
    }

    @RequestMapping
    public String doView(Model model) {
        return "view";
    }
}
scorch