/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.web; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.servlet.http.HttpSession; import net.wotonomy.control.EOEditingContext; import net.wotonomy.control.KeyValueCodingUtilities; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDate; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSKeyValueCodingAdditions; import net.wotonomy.foundation.NSKeyValueCodingSupport; import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; /** * A pure java implementation of WOSession. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOSession implements Serializable, NSKeyValueCodingAdditions { //NOTE: need to set this when deserialized and on creation transient private HttpSession session; // the current context transient private WOContext context; // the last requested page: an optimization transient private WOComponent currentPage; // the last requested page's context id transient private String currentContextID; //FIXME: transient until ec's implement serializable private transient EOEditingContext defaultEditingContext; private NSMutableDictionary state; private NSMutableDictionary pages; private NSMutableDictionary permanentPages; private NSMutableArray stateStack; private NSMutableArray pageStack; private NSMutableArray permanentPageStack; private boolean terminating; // used by WOResourceManager to cache dynamic resources transient NSMutableDictionary dynamicDataCache; public static final String WOSessionDidTimeOutNotification = "WOSessionDidTimeOutNotification"; public static final String WOSessionDidRestoreNotification = "WOSessionDidRestoreNotification"; public static final String WOSessionDidCreateNotification = "WOSessionDidCreateNotification"; /** * Default constructor. This is called implicitly by * subclasses in all cases. */ public WOSession () { session = null; state = new NSMutableDictionary(); pages = new NSMutableDictionary(); permanentPages = new NSMutableDictionary(); stateStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); pageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); permanentPageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); defaultEditingContext = null; terminating = false; } /** * Package method to initialize the backing session. */ void setServletSession( HttpSession aSession ) { session = aSession; } /** * Package method to set the current context. */ void setContext( WOContext aContext ) { context = aContext; } /** * Returns the id of the current session. If no session * currently exists, return null. */ public String sessionID () { if ( session != null ) { return session.getId(); } return null; } /** * Sets whether distribution is currently enabled. * This method is not implemented by this implementation * as the servlet container manages distribution. */ public void setDistributionEnabled (boolean enabled) { throw new RuntimeException( "Not implemented yet." ); } /** * Returns whether the session is part of a distributed application. * This implementation always returns false. */ public boolean isDistributionEnabled () { return false; } /** * Sets whether session ids should be stored in cookies. * This method is not implemented in this implementation * as the servlet container manages sessions with cookies. */ public void setStoresIDsInCookies (boolean cookies) { throw new RuntimeException( "Not implemented yet." ); } /** * Returns whether session ids are currently stored in cookies. * This implementation always returns true. */ public boolean storesIDsInCookies () { return true; } /** * Returns the current expiration date for cookies that store session ids. */ public NSDate expirationDateForIDCookies () { throw new RuntimeException( "Not implemented yet." ); } /** * Sets whether session ids should be stored in urls. * This method is not implemented in this implementation * as the servlet container manages sessions with cookies. */ public void setStoresIDsInURLs (boolean urls) { throw new RuntimeException( "Not implemented yet." ); } /** * Returns whether session ids are currently stored in urls. * This implementation always returns false. */ public boolean storesIDsInURLs () { return false; } /** * Returns the current domain for cookies containing session ids. */ public String domainForIDCookies () { throw new RuntimeException( "Not implemented yet." ); } /** * Terminates this session after the completion of the current response. */ public void terminate () { terminating = true; session.invalidate(); } /** * Returns whether the current session will terminate at the completion * of the current response. */ public boolean isTerminating () { return terminating; } /** * Sets the number of seconds after the last request before * the session should be terminated. */ public void setTimeOut (double timeout) { session.setMaxInactiveInterval( (int) timeout ); } /** * Returns the number of seconds after the last request before * the session should be terminated. */ public double timeOut () { return session.getMaxInactiveInterval(); } /** * Sets the languages for which this session has been localized, * in order of preference. The application will be responsible for * localizing the content based on the languages found in this array. */ public void setLanguages (NSArray anArray) { throw new RuntimeException( "Not implemented yet." ); } /** * Returns the languages for which this session has been localized, * in order of preference. The application will be responsible for * localizing the content based on the languages found in this array. */ public NSArray languages () { throw new RuntimeException( "Not implemented yet." ); } /** * Stores the specified key-value pair in the session. */ public void setObjectForKey (Object anObject, String aKey) { state.setObjectForKey( anObject, aKey ); } /** * Returns the session value associated with the specified key. */ public Object objectForKey (String aKey) { return state.objectForKey( aKey ); } /** * Removes the session value associated with the specified key. */ public void removeObjectForKey (String aKey) { state.removeObjectForKey( aKey ); } /** * Returns the context for the current request. */ public WOContext context () { return context; } /** * Invoked at the beginning of the request-response cycle. * Override to perform any kind of initialization at the * start of a request. This implementation does nothing. */ public void awake () { } /** * Invoked by the Application to extract user-assigned balues * and assign them to attributes. This implementation calls * takeValuesFromRequest on the top-level component. */ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) { context().component().takeValuesFromRequest( aRequest, aContext ); } /** * Invoked by the Application to determine which component is the * intended recipient of the user's action. This implementation calls * invokeAction on the top-level component. */ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) { return context().component().invokeAction( aRequest, aContext ); } /** * Invoked by the Application to generate the content of the response. * This implementation calls appendToResponse on the top-level component. */ public void appendToResponse (WOResponse aResponse, WOContext aContext) { context().component().appendToResponse( aResponse, aContext ); } /** * Invoked at the end of the request-response cycle. * Override to perform any kind of clean-up at the * end of a request. This implementation does nothing. */ public void sleep () { } /** * Returns a list of pages accessed by this session in order * of their access and named by calling WOComponent.descriptionForResponse. */ public NSArray statistics () { throw new RuntimeException( "Not implemented yet." ); } /** * Puts this page in the session's page cache using the current * context id as the key. */ public void savePage (WOComponent aComponent) { currentPage = aComponent; currentContextID = context.contextID(); if ( pages.objectForKey( currentContextID ) == null ) { byte[] data = KeyValueCodingUtilities.freeze( aComponent, defaultEditingContext(), aComponent, true ); System.out.println( "WOSession.savePage: " + currentContextID + " : " + data.length ); pages.setObjectForKey( data, currentContextID ); pageStack.addObject( currentContextID ); if ( pageStack.count() > context().application().pageCacheSize() ) { String id = pageStack.remove( 0 ).toString(); // removeObjectAtIndex System.out.println( "WOSession.savePage: removing from cache: " + id ); pages.removeObjectForKey( id ); } } //System.out.println( "savePage: " + this + " : " + id + " : " + pages ); } /** * Returns the page in the session's page cache corresponding to * the specified context id. Any special permanent caches are * searched before the standard page cache. */ public WOComponent restorePageForContextID (String anID) { if ( anID == null ) return null; if ( anID.equals( currentContextID ) ) return currentPage; WOComponent result = null; byte[] data = (byte[]) permanentPages.objectForKey( anID ); if ( data == null ) data = (byte[]) pages.objectForKey( anID ); if ( data != null ) { System.out.println( "WOSession.restorePageForContextID: " + anID + " : " + data.length ); result = (WOComponent) KeyValueCodingUtilities.thaw( data, defaultEditingContext(), WOApplication.application().getClass().getClassLoader(), true ); } //System.out.println( "restorePageForContextID: " + this + " : " + anID + " : " + result + " : " + pages ); return result; } /** * Puts this page in the special cache is will not get automatically * flushed like the session page cache. Use this if the page * is likely to be around for a while, specifically pages within * frames. */ public void savePageInPermanentCache (WOComponent aComponent) { currentPage = aComponent; currentContextID = context.contextID(); if ( permanentPages.objectForKey( currentContextID ) == null ) { byte[] data = KeyValueCodingUtilities.freeze( aComponent, defaultEditingContext(), aComponent, true ); //System.out.println( "WOSession.savePageInPermanentCache: " // + currentContextID + " : " + data.length ); permanentPages.setObjectForKey( data, currentContextID ); permanentPageStack.addObject( currentContextID ); if ( permanentPageStack.count() > context().application().pageCacheSize() ) { String id = permanentPageStack.remove( 0 ).toString(); // removeObjectAtIndex permanentPages.removeObjectForKey( id ); } } } /** * Writes a message to the standard error stream. */ public static void logString (String aString) { System.err.println( aString ); } /** * Writes a message to the standard error stream * if debugging is activated. */ public static void debugString (String aString) { // TODO: Check to see if debugging is enabled. System.err.println( aString ); } /** * Returns the default editing context used by this session. * Defaults to null. */ public EOEditingContext defaultEditingContext () { return defaultEditingContext; } /** * Sets the default editing context used by this session. */ public void setDefaultEditingContext (EOEditingContext aContext) { defaultEditingContext = aContext; } // interface NSKeyValueCodingAdditions public Object valueForKeyPath (String aPath) { // currently key value coding support also handles keypaths return valueForKey( aPath ); } public void takeValueForKeyPath (Object aValue, String aPath) { // currently key value coding support also handles keypaths takeValueForKey( aValue, aPath ); } public NSDictionary valuesForKeys (List aKeyList) { throw new RuntimeException( "Not implemented yet." ); } public void takeValuesFromDictionary (Map aValueMap) { throw new RuntimeException( "Not implemented yet." ); } public Object valueForKey (String aKey) { // System.out.println( "valueForKey: " + aKey + "->" + this ); Object result = objectForKey( aKey ); if ( result == null ) result = NSKeyValueCodingSupport.valueForKey( this, aKey ); return result; } public void takeValueForKey (Object aValue, String aKey) { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); setObjectForKey( aValue, aKey ); } public Object storedValueForKey (String aKey) { Object result = objectForKey( aKey ); if ( result == null ) NSKeyValueCodingSupport.storedValueForKey( this, aKey ); return result; } public void takeStoredValueForKey (Object aValue, String aKey) { setObjectForKey( aValue, aKey ); } public Object handleQueryWithUnboundKey (String aKey) { return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); } public void handleTakeValueForUnboundKey (Object aValue, String aKey) { NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); } public void unableToSetNullForKey (String aKey) { NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); } public Object validateTakeValueForKeyPath (Object aValue, String aKey) { throw new RuntimeException( "Not implemented yet." ); } }