Jetspeed Proposal: StateManager Service Version: $Revision$ Proposed by: Glenn R. Golden, University of Michigan (ggolden@umich.edu) Status: Revised Proposal Date: May 1, 2002 Revision 1.2 Note ================= This is a revisal of the initial (April 11, 2002) StateManager proposal. The key change is how state information managed by the StateManager is delivered to the Portlet. Rather than proposing new cover methods for the Portlet API, we introduce new methods in the JetspeedRunData that can be used to access state information. Also of importance, the clarification of the concepts of different types of sessions: UserSession, PageSession and PortletSession. Each type of session can have state managed by the StateManagerService. Overview ======== This proposes a new Jetspeed service, the “StateManagerService”, which helps to manage a certain "scope" of state information related to a particular user’s interaction with various parts of Jetspeed. Each type of user interaction is described as being within a “session”; this much like the HTTP session that is maintained by the Servlet container for Jetspeed, but we introduce two new levels of sessions. Each set of state information for each session is independent of all others. The key difference between what this service provides and what current mechanisms (such as the persistence service, the registry, and the turbine user getTemp() / setTemp()) provide is the scope and lifetime of the information stored within. The StateManagerService, and the SessionState objects, keep attributes for each state. Attributes are named with Strings; the name space of each state is independent of all other states. Attribute values can be any object. SessionState is different from the "customization" of a Portlet, which is permanent, and provides parameters that a Portlet instance uses for all users, stored in the PSML document for the Portal page. State is different from the "configuration" of a Portlet, which is permanent, and provides parameters that a Portlet uses for all users of all instances of the Portlet, stored in the Portlet registry. State is different from the getTemp()/setTemp() mechanism in the http session, although it is close. State provides separate namespaces for each Portlet instance, but the temp values provide only a single namespace that must be shared by all Portlets and other uses. One approach that developers currently use is to store state information in hidden form fields, so that when the user next interacts with the Portlet, the state for that particular user will be read from the form. This approach fails in a Portal environment when the user interacts with some *other* Portlet on the page. The state information hidden in form fields is not present in the form that was submitted, but *all* Portlets must provide content with each request. State must be stored in the Portal engine and made available at any time to the Portlet. SessionState ============ “SessionState” information keeps track of the information necessary to properly render the Portal page or element of the page to the user based on the user’s recent activities in this usage session. At the Page level, the state might include which Pane has been selected by menu or tab to view for this particular user in this particular page in this particular browser window. At the Portlet level, the state might include what transient display options the user has selected this time, so far; or what step of a multi-step Portlet process this particular user is on for this particular page in this particular window. SessionState is transient. It begins with a user pointing a browser window at a Jetspeed Portal site, and ends when the user leaves. When the user returns, new state information is collected. The StateManagerService makes sure that expired SessionState is cleaned up. This happens automatically or by explicit requests from the software. Session Levels ============== We recognize three different levels of sessions: - “UserSession”: This is one user’s interaction with Jetspeed, and matches the users’ HTTP session. - “PageSession”: This is one user’s interaction with one particular Jetspeed Portal page in a particular browser window. There may be many Page sessions “within” a singe UserSession; one for each browser window the user has open on a Portal page. When a UserSession is over, all PageSessions also end. A PageSession also ends if and when Jetspeed detects that the page is no longer displayed in the user’s browser. - “PortletSession”: This is one user’s interaction with one particular Jetspeed Portlet placed in a particular location in the layout of a particular Portal page open in a particular browser window. There may be many PortletSessions “within” a single PageSession; one for each Portlet placement in the Portal page (i.e. one for each Portlet instance). All such PortletSessions end when the PageSession (or UserSession) ends. Using the Service ================= The StateManagerService works in conjunction with appropriately formed “StateKey” to keep track of this state information. The service is implemented as a Turbine service, and can be found using the Turbine service manager. A Portlet can access the appropriate PortletSession state for a request by calling a new method in the JetspeedRunData, which forms the proper StateKey and interact with the service to find the proper “SessionState” object. Code in the Portal engine parts of Jetspeed can also use the JetspeedRunData to aid in composing an appropriate StateKey and accessing SessionState (for UserSession and PageSession) objects. StateKey ======== For a PortletSession state, the StateKey includes the following to achieve our desired scope: - HTTP session id - Portal Page id - Portlet id For PageSession state, the StateKey includes all but the Portlet id portion as described above. For the UserSession state, the StateKey matches the HTTP Session id. PortletSession Use ================== The simple way to use this feature, to give a Portlet the appropriate PortletSession state for this request, involves an addition to the JetspeedRunData object. A Portlet can call: public SessionState getPortletSessionState(String id); and provide it with the Portlet’s ID (getID()) as the id parameter. This method takes care of forming the proper state key, using another new method in JetspeedRunData: public String getPageSessionId(); and adding the provided Portlet id. PageSession Use =============== For use within the Portal engine, the PageSession state for the current request can be accessed using the JetspeedRunData as well. Code can call: public SessionState getPageSessionState(); The proper key for the PageSession will be formed. UserSession Use =============== For symmetry, we will provide a way to access the UserSession state, although we could instead use the HTTP session directly. JetspeedRunData will also have: public SessionState getUserSessionState(); The proper key will be formed. General Use =========== Once the state object is found, it can be used to access and manipulate state values and access the PortletSession key. Here's the proposed SessionState interface (but check with the actual code for current details): public interface SessionState { /** * Access the named attribute. * @param name The attribute name. * @return The named attribute value. */ public Object getAttribute( String name ); /** * Set the named attribute value to the provided object. * @param name The attribute name. * @param value The value of the attribute (any object type). */ public void setAttribute( String name, Object value ); /** * Remove the named attribute, if it exists. * @param name The attribute name. */ public void removeAttribute( String name ); /** * Remove all attributes. */ public void clear(); /** * Access an iterator on names of all attributes. * @return An iterator on on names of all attributes. */ public Iterator getAttributeNames(); /** * Access the StateManager key for this SessionState. * @return the StateManager key for this SessionState. */ public String getKey(); } // interface SessionState The StateManagerService API =========================== Direct callers of the API can find the service using code like this: StateManagerService mgr = (StateManagerService)TurbineServices .getInstance().getService(StateManagerService.SERVICE_NAME); These users must form their own unique state keys. The calls to the manager are like the SessionState calls above, with the state key as an additional parameter. Here's the proposed StateManagerService API (but check with the actual code for current details): public interface StateManagerService { /** The name used to find the service in the service manager. */ public String SERVICE_NAME = "StateManagerService"; /** * Access the named attribute of the keyed state. * @param key The state key. * @param name The attribute name. * @return The named attribute value of the keyed state. */ public Object getAttribute ( String key, String name ); /** * Set the named state attribute of the keyed state with the provided object. * @param key The state key. * @param name The attribute name. * @param value The new value of the attribute (any object type). */ public void setAttribute( String key, String name, Object value ); /** * Remove the named state attribute of the keyed state, if it exists. * @param key The state key. * @param name The attribute name. */ public void removeAttribute( String key, String name ); /** * Remove all state attribute of the keyed state. * @param key The state key. */ public void clear( String key ); /** * Access an iterator on all names of attributes stored in the keyed state. * @param key The state key. * @return An iterator on all names of attributes stored in the keyed state. */ public Iterator getAttributeNames( String key ); /** * Access a SessionState object with the given key * @param key The SessionState key. * @return a SessionState object with the given key. */ public SessionState getSessionState( String key ); } // interface StateManagerService Retiring State ============== When the session a state is associated with is ended, the StateManagerService must clean up the old state. This can happen automatically, withing the implementation of the service, and also by explicit request from the software. The retireState() method of the service can be called to explicitly cause a set of states to be retired. This would be done if we knew that a PageSession was over, for example. The PageSession id would be passed in; the service would match this id to the PageSession state and all the PortletSession states “within” this PageSession, and clean them all up. Or, if the entire UserSession was known to be over, the UserSession id could be passed to the retireState(), and the UserSession state, and all PageSession and PortletSession states associated with that user session would be cleaned up. The automatic method within the service implementation will tie it to the binding out of the HTTP session for the user. If there is any state left in the service for this user session at the HTTP session timeout time, it will be cleaned up. Efficiencies ============ The getPortletSessionState(), getPageSessionState() and getUserSessionState() calls in the JetspeedRunData merely forms the appropriate key, find the StateManagerService and asks the service for an SessionState object. This is a "lazy" process; if a caller never asks for the state, no work will be done; once the caller has the SessionState object, accessing state information is delayed until actually called upon. The SessionState object is created and owned by the StateManagerService (it acts as a factory for these objects). SessionState is really a cover to StateManagerService calls with the appropriate key. Work List ========= To implement this proposal, the following work must be done: 1.1) Extend org.apache.jetspeed.services.rundata.JetspeedRunData and the JetspeedRunData implementation org.apache.jetspeed.services.rundata.DefaultJetspeedRunData to add getPortletSessionState(), getPageSessionState(), getUseSessionState(), and getPageSessionId(). 1.2) Add the new StateManager service API interface (called: org.apache.jetspeed.services.statemanager.StateManagerService.java) 1.3) Add the new StateManager service implementation (called: org.apache.jetspeed.services.statemanager.JetspeedStateManagerService.java). The implementation of the SessionState will be a private inner class in the service implementation. 1.4) Add the new SessionState API interface (called: org.apache.jetspeed.services.statemanager.SessionState.java) 1.5) Update tr.p to include the state manager service. 1.6) Provide Unit tests of the JetspeedStateManagerService and SessionState implementation. 1.7) Provide Unit tests for the new JetspeedRunData methods. 1.8) Provide 2 example Portlets (one AbstractPortlet and one VelocityPortlet) using the StateManagerService / SessionState features. 1.9) Provide a tutorial or docs about using state in Portlet design. Browser Window Distinctions =========================== If we want to fully support multiple browser windows for one user (in one http session), that all can display the same Portal page (perhaps different Portlets maximized, different panes selected, or all the same, but with different user interaction state in the Portlets), we need to: a) have a unique id for each browser window b) know what that id is in the request, and c) use it as part of our state key. The browser window information must make its way into the PageSessionId. We will look at ways to do this, but at first this service will not distinguish between different windows. Additional work for this: 2.1) Identify individual browser windows and make part of the PageSessionId. Portal Engine of StateManager ============================= Current mechanisms used to manage PageSession state may not work properly when multiple windows are opened by a user in the same http session (even if they show different Portal pages). We can change Jetspeed to use the StateManagerService and PageSession state to correct this. PageSession state properly distinguishes between two different users viewing the same Portal page, as well as between two different Portal pages being viewed by the same user in the HTTP session. The following areas of Jetspeed could be converted to use the StateManagerService and PageSession state: Maximize: When a users maximizes and restores a Portlet, this should not affect other users, nor should it affect other browser windows the user has open on the same or other Portal pages. The PageSession state could include the Portlet ID of the Portlet to display maximized. Customize: The stack of Portal elements being customized can be stored in the PageSession state. Minimized: When a user minimizes a Portlet, this should not affect other users, and it should be transient, so that next time the user visits the page the minimized Portlet is displayed normally. The PageSession state can include a list of Portal Elements to display minimized. Panes: When a Portal page has many panes, only one is displayed at a time. The one displayed for one user should be independent of the one displayed for another user, and this need not be remembered next time the user views the Portal page. This can be stored in the PageSession state. This work (summarized below) is made possible by this StateManager proposed , but is not part of nor required for this proposal. Additional work for this: 3.1) change maximize handling to use PageSession state. 3.2) change restore handling to use PageSession state. 3.3) change minimize handling to use PageSession state. 3.4) change customizer stack handling to use PageSession state. 3.5) change the pane-to-display mechanize to use the PageSession state.