0

I am working on a JSR-286 portlet application that uses JSF 1.2. I am working on moving my JSF managed beans to Spring beans, and I noticed what appears to a difference between how Spring is treating request scope from how JSF is treating request scope.

In my portlet application, I have two portlets that live on the same page and both use the same starting JSF portlet page view. When I use JSF managed request beans, there is an individual request bean created for each portlet, which is the behavior I am looking for. When I use Spring beans, only one request bean is created and is shared among both portlets. Is this normal behavior? Is there any way I can stop it from doing this?

My original faces-config.xml file, before moving my beans to Spring:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
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-facesconfig_1_2.xsd"
version="1.2">
<application>
    <state-manager>com.ibm.faces.application.DevelopmentStateManager</state-manager>
    <variable-resolver>com.ibm.faces.portlet.PortletVariableResolver</variable-resolver>
</application>
<factory>
    <faces-context-factory>com.ibm.faces.context.AjaxFacesContextFactory</faces-context-factory>
    <render-kit-factory>com.ibm.faces.renderkit.AjaxRenderKitFactory</render-kit-factory>
</factory>

<managed-bean>
    <managed-bean-name>sessionBean</managed-bean-name>
    <managed-bean-class>sanitycheck.SessionBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<managed-bean>
    <managed-bean-name>pc_SanityCheckProjectView</managed-bean-name>
    <managed-bean-class>sanitycheck.SanityCheckProjectView</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>sessionBean</property-name>
        <value>#{sessionBean}</value>
    </managed-property>
</managed-bean>

<lifecycle>
    <phase-listener>com.ibm.faces.webapp.ValueResourcePhaseListener</phase-listener>
</lifecycle>

</faces-config>

My faces-config.xml file after moving beans to Spring:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
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-facesconfig_1_2.xsd"
version="1.2">
<application>
    <state-manager>com.ibm.faces.application.DevelopmentStateManager</state-manager>
    <variable-resolver>com.ibm.faces.portlet.PortletVariableResolver</variable-resolver>
    <variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>
<factory>
    <faces-context-factory>com.ibm.faces.context.AjaxFacesContextFactory</faces-context-factory>
    <render-kit-factory>com.ibm.faces.renderkit.AjaxRenderKitFactory</render-kit-factory>
</factory>

<lifecycle>
    <phase-listener>com.ibm.faces.webapp.ValueResourcePhaseListener</phase-listener>
</lifecycle>

</faces-config>

And my spring-web.xml file:

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

<bean id="sessionBean" class="sanitycheck.SessionBean" scope="session">
               <aop:scoped-proxy/>
    </bean>

<bean id="pc_SanityCheckProjectView" class="pagecode.SanityCheckProjectView" scope="request" init-method="init">
               <aop:scoped-proxy/>
    <property name="sessionBean" ref="sessionBean"/>
</bean>
</beans>

I can provide my other files if necessary, just let me know. Thanks!

Edit: Added aop:scoped-proxy to the Spring beans.

Edit: Adding portlet.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" id="com.ibm.faces.portlet.FacesPortlet.3a22ca3014">
<portlet>
    <portlet-name>SanityCheckProject</portlet-name>
    <display-name xml:lang="en">SanityCheckProject</display-name>
    <display-name>SanityCheckProject</display-name>
    <portlet-class>com.ibm.faces.portlet.FacesPortlet</portlet-class>
    <init-param>
        <name>com.ibm.faces.portlet.page.view</name>
        <value>/SanityCheckProjectView.jsp</value>
    </init-param>
    <init-param>
        <name>whichOne</name>
        <value>Portlet1</value>
    </init-param>
    <init-param>
        <name>wps.markup</name>
        <value>html</value>
    </init-param>
    <expiration-cache>0</expiration-cache>
    <supports>
        <mime-type>text/html</mime-type>
        <portlet-mode>view</portlet-mode>
    </supports>
    <supported-locale>en</supported-locale>
    <resource-bundle>com.ibm.sanitycheckproject.nl.SanityCheckProjectPortletResource</resource-bundle>
    <portlet-info>
        <title>SanityCheckProject</title>
        <short-title>SanityCheckProject</short-title>
        <keywords>SanityCheckProject</keywords>
    </portlet-info>
</portlet>
<portlet>
    <portlet-name>SanityCheckPortlet2</portlet-name>
    <display-name xml:lang="en">SanityCheckPortlet2</display-name>
    <display-name>SanityCheckPortlet2</display-name>
    <portlet-class>com.ibm.faces.portlet.FacesPortlet</portlet-class>
    <init-param>
        <name>com.ibm.faces.portlet.page.view</name>
        <value>/SanityCheckProjectView.jsp</value>
    </init-param>
    <init-param>
        <name>whichOne</name>
        <value>Portlet2</value>
    </init-param>
    <init-param>
        <name>wps.markup</name>
        <value>html</value>
    </init-param>
    <expiration-cache>0</expiration-cache>
    <supports>
        <mime-type>text/html</mime-type>
        <portlet-mode>view</portlet-mode>
    </supports>
    <supported-locale>en</supported-locale>
    <resource-bundle>com.ibm.sanitycheckproject.nl.SanityCheckPortlet2PortletResource</resource-bundle>
    <portlet-info>
        <title>SanityCheckPortlet2</title>
        <short-title>SanityCheckPortlet2</short-title>
        <keywords>SanityCheckPortlet2</keywords>
    </portlet-info>
</portlet>
<default-namespace>http://SanityCheckProject/</default-namespace>
</portlet-app>
Chatoyancy
  • 143
  • 1
  • 17

2 Answers2

0

In Spring XML config, you must use the <aop:scoped-proxy/> tag.

http://static.springsource.org/spring/docs/3.0.x/reference/beans.html#beans-factory-scopes-other-injection

<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <!-- this next element effects the proxying of the surrounding bean -->
    <aop:scoped-proxy/>
</bean>
Paul Grime
  • 14,970
  • 4
  • 36
  • 58
  • Okay. I just added to both beans, but unfortunately it didn't make a difference to this issue. Thanks though. – Chatoyancy Sep 10 '13 at 19:33
  • Is that because the HttpServletRequest is the same for both portlets, but the PortletRequest is different? If so, you would need to find the portlet equivalent of the `request` and `session` scopes. Where are your Spring-based portlets defined? Have you read something like these - http://docs.spring.io/spring-webflow/docs/2.3.x/reference/html/ch14s06.html and http://docs.spring.io/spring/docs/3.0.x/reference/portlet.html? – Paul Grime Sep 10 '13 at 19:47
  • I'm not certain, I will have to investigate this. If this answers your question at all, I am still using JSF for the portlet (not Spring MVC) I'm just using Spring to define the beans because it works better for the project. – Chatoyancy Sep 17 '13 at 15:00
  • I can also add this -- the portlet I first noticed this behavior in additionally had a PortletFilter defined on the Render and Action phases of the portlet. I confirmed that this filter would fire on the portlets independently, even when the session bean was being shared between the two. – Chatoyancy Sep 17 '13 at 15:06
  • I have just added my portlet.xml to my original question so you can see how my portlets are defined. – Chatoyancy Sep 17 '13 at 15:09
  • Do you need to register the `org.springframework.web.context.request.RequestContextListener` listener? - http://docs.spring.io/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes-other. – Paul Grime Sep 18 '13 at 08:14
0

I don't know if this was the best or even a very good solution, but what I eventually did was create two custom portlet scopes, one for request scope, and one for session scope. Essentially what my custom scopes do is that they prefix the name of the object requested with the portlet's namespace, which seems to keep everything separated.

Here is the code I used for my scopes:

Request Scope:

import javax.faces.context.FacesContext;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.web.context.request.RequestScope;
import com.ibm.faces.portlet.httpbridge.ActionResponseWrapper;
import javax.portlet.RenderResponse;
import javax.portlet.filter.RenderResponseWrapper;

public class PortletRequestScope extends RequestScope {

@Override
public Object get(String name, ObjectFactory objectFactory) {
    Object response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
    if (response instanceof RenderResponse) {
        String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.get(namespace + name, objectFactory);
    }
    else if (response instanceof RenderResponseWrapper) {
        String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.get(namespace + name, objectFactory);
    }
    else if (response instanceof ActionResponseWrapper) {
        String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.get(namespace + name, objectFactory);
    }
    else {
        writeError(response);
    }
    return super.get(name, objectFactory);
}

@Override
public void registerDestructionCallback(String name, Runnable callback) {
    Object response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
    if (response instanceof RenderResponse) {
        String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else if (response instanceof RenderResponseWrapper) {
        String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else if (response instanceof ActionResponseWrapper) {
        String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else {
        writeError(response);
    }
    super.registerDestructionCallback(name, callback);
}

@Override
public Object remove(String name) {
    Object response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
    if (response instanceof RenderResponse) {
        String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else if (response instanceof RenderResponseWrapper) {
        String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else if (response instanceof ActionResponseWrapper) {
        String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else {
        writeError(response);
    }
    return super.remove(name);
}

protected void writeError(Object response) {
    System.err.println("Error in PortletRequestScope");
    System.err.println("Response is unrecognized class: " + response.getClass().getCanonicalName());
    System.err.println("Please modify code to handle class");
}

}

Session Scope:

import javax.faces.context.FacesContext;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.web.context.request.SessionScope;
import com.ibm.faces.portlet.httpbridge.ActionResponseWrapper;
import javax.portlet.RenderResponse;
import javax.portlet.filter.RenderResponseWrapper;

public class PortletSessionScope extends SessionScope {

@Override
public Object get(String name, ObjectFactory objectFactory) {
        Object response =  FacesContext.getCurrentInstance().getExternalContext().getResponse();
        if (response instanceof RenderResponse) {
            String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
            return super.get(namespace + name, objectFactory);
        }
        else if (response instanceof RenderResponseWrapper) {
            String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
            return super.get(namespace + name, objectFactory);
        }
        else if (response instanceof ActionResponseWrapper) {
            String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
            return super.get(namespace + name, objectFactory);
        }
        else {
            writeError(response);
        }
    return super.get(name, objectFactory);
}

@Override
public void registerDestructionCallback(String name, Runnable callback) {
    Object response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
    if (response instanceof RenderResponse) {
        String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else if (response instanceof RenderResponseWrapper) {
        String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else if (response instanceof ActionResponseWrapper) {
        String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        super.registerDestructionCallback(namespace+name, callback);
    }
    else {
        writeError(response);
    }
    super.registerDestructionCallback(name, callback);
}

@Override
public Object remove(String name) {
    Object response = FacesContext.getCurrentInstance().getExternalContext().getResponse();
    if (response instanceof RenderResponse) {
        String namespace=((RenderResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else if (response instanceof RenderResponseWrapper) {
        String namespace=((RenderResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else if (response instanceof ActionResponseWrapper) {
        String namespace=((ActionResponseWrapper)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getNamespace();
        return super.remove(namespace+name);
    }
    else {
        writeError(response);
    }
    return super.remove(name);
}

protected void writeError(Object response) {
    System.err.println("Error in PortletSessionScope");
    System.err.println("Response is unrecognized class: " + response.getClass().getCanonicalName());
    System.err.println("Please modify code to handle class");
}

}

Then, in my spring-web.xml, I defined my custom scopes:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
    <map>
        <entry key="portletRequestScope">
            <bean class="com.test.scope.PortletRequestScope"/>
        </entry>
        <entry key="portletSessionScope">
            <bean class="com.test.portlet.scope.PortletSessionScope"/>
        </entry>
    </map>
</property>
</bean>

And when I defined my actual Spring beans, I used my custom scopes instead of the regular scope -- for example:

<bean id="sessionBean" class="com.test.managedbeans.SessionBean"
scope="portletSessionScope" lazy-init="true"/>

At the very least, doing this seemed to work in my specific situation of JSF + Spring on WebSphere Portal, and hopefully this will be useful to someone else.

Chatoyancy
  • 143
  • 1
  • 17