springscopeportletjsf-1.2jsr286

Difference between Spring request scope and JSF request scope?


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>

Solution

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