View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.web;
20  
21  import java.io.Serializable;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import javax.servlet.http.HttpSession;
27  
28  import net.wotonomy.control.EOEditingContext;
29  import net.wotonomy.control.KeyValueCodingUtilities;
30  import net.wotonomy.foundation.NSArray;
31  import net.wotonomy.foundation.NSDate;
32  import net.wotonomy.foundation.NSDictionary;
33  import net.wotonomy.foundation.NSKeyValueCodingAdditions;
34  import net.wotonomy.foundation.NSKeyValueCodingSupport;
35  import net.wotonomy.foundation.NSMutableArray;
36  import net.wotonomy.foundation.NSMutableDictionary;
37  
38  /***
39  * A pure java implementation of WOSession.
40  *
41  * @author michael@mpowers.net
42  * @author $Author: cgruber $
43  * @version $Revision: 905 $
44  */
45  public class WOSession implements Serializable, NSKeyValueCodingAdditions
46  {
47      //NOTE: need to set this when deserialized and on creation
48      transient private HttpSession session;
49      
50      // the current context
51      transient private WOContext context;
52      
53      // the last requested page: an optimization
54      transient private WOComponent currentPage;
55      // the last requested page's context id
56      transient private String currentContextID;
57  
58      //FIXME: transient until ec's implement serializable    
59      private transient EOEditingContext defaultEditingContext;
60      
61      private NSMutableDictionary state;
62      private NSMutableDictionary pages;
63      private NSMutableDictionary permanentPages;
64      private NSMutableArray stateStack;
65      private NSMutableArray pageStack;
66      private NSMutableArray permanentPageStack;
67      private boolean terminating;
68      
69      // used by WOResourceManager to cache dynamic resources
70      transient NSMutableDictionary dynamicDataCache;
71      
72      public static final String WOSessionDidTimeOutNotification
73      	= "WOSessionDidTimeOutNotification";
74      public static final String WOSessionDidRestoreNotification
75      	= "WOSessionDidRestoreNotification";
76      public static final String WOSessionDidCreateNotification
77      	= "WOSessionDidCreateNotification";
78          
79  	/***
80  	* Default constructor.  This is called implicitly by 
81  	* subclasses in all cases.
82  	*/
83      public WOSession ()
84      {
85      	session = null;	
86          state = new NSMutableDictionary();
87          pages = new NSMutableDictionary();
88          permanentPages = new NSMutableDictionary();
89          stateStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
90          pageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
91          permanentPageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
92          defaultEditingContext = null;
93          terminating = false;
94      }
95      
96  	/***
97  	* Package method to initialize the backing session.
98  	*/
99      void setServletSession( HttpSession aSession )
100     {
101     	session = aSession;
102     }
103 
104     /***
105     * Package method to set the current context.
106     */
107     void setContext( WOContext aContext )
108     {
109         context = aContext;
110     }
111     
112 	/***
113 	* Returns the id of the current session.  If no session
114 	* currently exists, return null.
115 	*/
116     public String sessionID ()
117     {
118     	if ( session != null )
119 	    {
120 	    	return session.getId();
121 	    }
122 	    return null;
123     }
124     
125     /***
126     * Sets whether distribution is currently enabled.
127     * This method is not implemented by this implementation
128     * as the servlet container manages distribution.
129     */
130     public void setDistributionEnabled (boolean enabled)
131     {
132     	throw new RuntimeException( "Not implemented yet." );
133     }
134     
135     /***
136 	* Returns whether the session is part of a distributed application.
137 	* This implementation always returns false.
138 	*/
139     public boolean isDistributionEnabled ()
140     {
141     	return false;
142     }
143     
144     /***
145 	* Sets whether session ids should be stored in cookies.
146 	* This method is not implemented in this implementation
147 	* as the servlet container manages sessions with cookies.
148 	*/
149     public void setStoresIDsInCookies (boolean cookies)
150     {
151     	throw new RuntimeException( "Not implemented yet." );
152     }
153     
154     /***
155 	* Returns whether session ids are currently stored in cookies.
156 	* This implementation always returns true.
157 	*/
158     public boolean storesIDsInCookies ()
159     {
160     	return true;
161     }
162     
163     /***
164 	* Returns the current expiration date for cookies that store session ids.
165 	*/
166     public NSDate expirationDateForIDCookies ()
167     {
168     	throw new RuntimeException( "Not implemented yet." );
169     }
170     
171     /***
172 	* Sets whether session ids should be stored in urls.
173 	* This method is not implemented in this implementation
174 	* as the servlet container manages sessions with cookies.
175 	*/
176     public void setStoresIDsInURLs (boolean urls)
177     {
178     	throw new RuntimeException( "Not implemented yet." );
179     }
180     
181     /***
182 	* Returns whether session ids are currently stored in urls.
183 	* This implementation always returns false.
184 	*/
185     public boolean storesIDsInURLs ()
186     {
187     	return false;
188     }
189     
190     /***
191 	* Returns the current domain for cookies containing session ids.
192 	*/
193     public String domainForIDCookies ()
194     {
195     	throw new RuntimeException( "Not implemented yet." );
196     }
197     
198     /***
199 	* Terminates this session after the completion of the current response.
200 	*/
201     public void terminate ()
202     {
203         terminating = true;
204     	session.invalidate();
205     }
206     
207     /***
208 	* Returns whether the current session will terminate at the completion
209 	* of the current response.
210 	*/
211     public boolean isTerminating ()
212     {
213     	return terminating;
214     }
215     
216     /***
217 	* Sets the number of seconds after the last request before 
218 	* the session should be terminated.
219 	*/
220     public void setTimeOut (double timeout)
221     {
222     	session.setMaxInactiveInterval( (int) timeout );
223     }
224     
225     /***
226 	* Returns the number of seconds after the last request before 
227 	* the session should be terminated.
228 	*/
229     public double timeOut ()
230     {
231     	return session.getMaxInactiveInterval();
232     }
233     
234     /***
235 	* Sets the languages for which this session has been localized,
236 	* in order of preference.  The application will be responsible for
237 	* localizing the content based on the languages found in this array.
238 	*/
239     public void setLanguages (NSArray anArray)
240     {
241     	throw new RuntimeException( "Not implemented yet." );
242     }
243     
244     /***
245 	* Returns the languages for which this session has been localized,
246 	* in order of preference.  The application will be responsible for
247 	* localizing the content based on the languages found in this array.
248 	*/
249     public NSArray languages ()
250     {
251     	throw new RuntimeException( "Not implemented yet." );
252     }
253     
254     /***
255 	* Stores the specified key-value pair in the session.
256 	*/
257     public void setObjectForKey (Object anObject, String aKey)
258     {
259         state.setObjectForKey( anObject, aKey );
260     }
261     
262     /***
263 	* Returns the session value associated with the specified key.
264 	*/
265     public Object objectForKey (String aKey)
266     {
267         return state.objectForKey( aKey );
268     }
269     
270     /***
271 	* Removes the session value associated with the specified key.
272 	*/
273     public void removeObjectForKey (String aKey)
274     {
275         state.removeObjectForKey( aKey );
276     }
277     
278     /***
279 	* Returns the context for the current request.
280 	*/
281     public WOContext context ()
282     {
283         return context;
284     }
285     
286     /***
287 	* Invoked at the beginning of the request-response cycle.
288 	* Override to perform any kind of initialization at the 
289 	* start of a request.  This implementation does nothing.
290 	*/
291     public void awake ()
292     {
293     
294     }
295     
296     /***
297 	* Invoked by the Application to extract user-assigned balues
298     * and assign them to attributes.  This implementation calls
299     * takeValuesFromRequest on the top-level component.
300 	*/
301     public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
302     {
303         context().component().takeValuesFromRequest( aRequest, aContext );
304     }
305     
306     /***
307 	* Invoked by the Application to determine which component is the
308     * intended recipient of the user's action.  This implementation calls
309     * invokeAction on the top-level component.
310 	*/
311     public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
312     {
313         return context().component().invokeAction( aRequest, aContext );
314     }
315     
316     /***
317 	* Invoked by the Application to generate the content of the response.  
318     * This implementation calls appendToResponse on the top-level component.
319 	*/
320     public void appendToResponse (WOResponse aResponse, WOContext aContext)
321     {
322         context().component().appendToResponse( aResponse, aContext );
323     }
324     
325     /***
326 	* Invoked at the end of the request-response cycle.
327 	* Override to perform any kind of clean-up at the 
328 	* end of a request.  This implementation does nothing.
329 	*/
330     public void sleep ()
331     {
332 
333     }
334     
335     /***
336 	* Returns a list of pages accessed by this session in order
337 	* of their access and named by calling WOComponent.descriptionForResponse.
338 	*/
339     public NSArray statistics ()
340     {
341     	throw new RuntimeException( "Not implemented yet." );
342     }
343     
344     /***
345 	* Puts this page in the session's page cache using the current
346 	* context id as the key.
347 	*/
348     public void savePage (WOComponent aComponent)
349     {
350         currentPage = aComponent;
351         currentContextID = context.contextID();
352 
353         if ( pages.objectForKey( currentContextID ) == null )
354         {
355             byte[] data = KeyValueCodingUtilities.freeze(
356                 aComponent, defaultEditingContext(), aComponent, true );
357             System.out.println( "WOSession.savePage: " + currentContextID + " : " + data.length );                
358                 
359         	pages.setObjectForKey( data, currentContextID );
360             pageStack.addObject( currentContextID );
361             if ( pageStack.count() > context().application().pageCacheSize() )
362             {
363                 String id = pageStack.remove( 0 ).toString(); // removeObjectAtIndex
364                 System.out.println( "WOSession.savePage: removing from cache: " + id );
365                 pages.removeObjectForKey( id );
366             }
367         }
368         //System.out.println( "savePage: " + this + " : " + id + " : " + pages );
369     }
370     
371     /***
372 	* Returns the page in the session's page cache corresponding to
373 	* the specified context id.  Any special permanent caches are 
374 	* searched before the standard page cache.
375 	*/
376     public WOComponent restorePageForContextID (String anID)
377     {
378         if ( anID == null ) return null;
379         if ( anID.equals( currentContextID ) ) return currentPage;
380         
381         WOComponent result = null;
382         byte[] data = (byte[]) permanentPages.objectForKey( anID );
383         if ( data == null ) data = (byte[]) pages.objectForKey( anID );
384         if ( data != null ) 
385         { 
386             System.out.println( "WOSession.restorePageForContextID: " + anID + " : " + data.length );                
387             result = (WOComponent) KeyValueCodingUtilities.thaw( 
388                 data, defaultEditingContext(), 
389                 WOApplication.application().getClass().getClassLoader(), true );
390         }
391         //System.out.println( "restorePageForContextID: " + this + " : " + anID + " : " + result + " : " + pages );
392         return result;
393     }
394     
395     /***
396 	* Puts this page in the special cache is will not get automatically
397 	* flushed like the session page cache.  Use this if the page
398 	* is likely to be around for a while, specifically pages within
399     * frames.
400 	*/
401     public void savePageInPermanentCache (WOComponent aComponent)
402     {
403         currentPage = aComponent;
404         currentContextID = context.contextID();
405         
406         if ( permanentPages.objectForKey( currentContextID ) == null )
407         {
408             byte[] data = KeyValueCodingUtilities.freeze(
409                 aComponent, defaultEditingContext(), aComponent, true );
410             //System.out.println( "WOSession.savePageInPermanentCache: " 
411             // + currentContextID + " : " + data.length );                
412                 
413         	permanentPages.setObjectForKey( data, currentContextID );
414             permanentPageStack.addObject( currentContextID );
415             if ( permanentPageStack.count() > context().application().pageCacheSize() )
416             {
417                 String id = permanentPageStack.remove( 0 ).toString(); // removeObjectAtIndex
418                 permanentPages.removeObjectForKey( id );
419             }
420         }
421     }
422     
423 	/***
424 	* Writes a message to the standard error stream.
425 	*/
426     public static void logString (String aString)
427     {
428     	System.err.println( aString );
429     }
430 
431 	/***
432 	* Writes a message to the standard error stream
433 	* if debugging is activated.
434 	*/
435     public static void debugString (String aString)
436     {
437     	// TODO: Check to see if debugging is enabled.
438     	System.err.println( aString );
439     }
440 
441     /***
442     * Returns the default editing context used by this session.
443     * Defaults to null.
444     */    
445     public EOEditingContext defaultEditingContext ()
446     {
447         return defaultEditingContext;
448     }
449     
450     /***
451     * Sets the default editing context used by this session.
452     */
453     public void setDefaultEditingContext (EOEditingContext aContext)
454     {
455         defaultEditingContext = aContext;
456     }
457     
458     // interface NSKeyValueCodingAdditions
459     
460     public Object valueForKeyPath (String aPath)
461     {
462         // currently key value coding support also handles keypaths
463     	return valueForKey( aPath );
464     }
465 
466     public void takeValueForKeyPath (Object aValue, String aPath)
467     {
468         // currently key value coding support also handles keypaths
469     	takeValueForKey( aValue, aPath );
470     }
471 
472     public NSDictionary valuesForKeys (List aKeyList)
473     {
474     	throw new RuntimeException( "Not implemented yet." );
475     }
476 
477     public void takeValuesFromDictionary (Map aValueMap)
478     {
479     	throw new RuntimeException( "Not implemented yet." );
480     }
481 
482     public Object valueForKey (String aKey)
483     { // System.out.println( "valueForKey: " + aKey + "->" + this );      
484     	Object result = objectForKey( aKey );
485         if ( result == null ) 
486             result = NSKeyValueCodingSupport.valueForKey( this, aKey );
487         return result;
488     }
489 
490     public void takeValueForKey (Object aValue, String aKey)
491     { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this );      
492         setObjectForKey( aValue, aKey );
493     }
494 
495     public Object storedValueForKey (String aKey)
496     {
497     	Object result = objectForKey( aKey );
498         if ( result == null ) 
499             NSKeyValueCodingSupport.storedValueForKey( this, aKey );
500         return result;
501     }
502 
503     public void takeStoredValueForKey (Object aValue, String aKey)
504     {
505         setObjectForKey( aValue, aKey );
506     }
507 
508     public Object handleQueryWithUnboundKey (String aKey)
509     {
510     	return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
511     }
512 
513     public void handleTakeValueForUnboundKey (Object aValue, String aKey)
514     {
515     	NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
516     }
517 
518     public void unableToSetNullForKey (String aKey)
519     {
520     	NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
521     }
522 
523     public Object validateTakeValueForKeyPath (Object aValue, String aKey)
524     {
525     	throw new RuntimeException( "Not implemented yet." );
526     }
527     
528 }