001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.presentation.portlet.session;
051:
052: import org.jaffa.presentation.portlet.FormBase;
053: import org.jaffa.presentation.portlet.FormKey;
054: import org.jaffa.presentation.portlet.component.Component;
055: import java.util.*;
056: import java.io.*;
057: import javax.servlet.http.*;
058: import org.apache.log4j.Logger;
059: import org.jaffa.util.ListMap;
060: import org.jaffa.datatypes.DateTime;
061: import javax.servlet.http.HttpSessionBindingListener;
062: import javax.servlet.http.HttpSessionBindingEvent;
063:
064: /** This UserSession Object maintains all the state about the a specific user within the context of the Web Server.
065: * This object could be adapted to other context stores (apart from HttpSession) if needed.
066: */
067: public class UserSession implements HttpSessionBindingListener {
068: private static Logger log = Logger.getLogger(UserSession.class);
069:
070: /** The key used to add the UserSession object to a HTTP Session.*/
071: public static final String USER_ATTRIBUTE = "org.jaffa.presentation.portlet.session.UserInfo";
072:
073: /** An Id assigned by the SessionManager */
074: private String m_sessionId = null;
075:
076: private String m_userId = null;
077:
078: // Holds a reference to an object with user data in it
079: private Object m_userData = null;
080:
081: // Cached so form object can be released from it!
082: private HttpSession m_sessionObject = null;
083:
084: // Maintain the latest componentId
085: private long m_lastComponentId = 0;
086:
087: // This will maintain all the components for a UserSession
088: private Map m_components = null;
089:
090: // Maintain a cache of widgets based on componentId
091: private Map m_widgetCache = null;
092:
093: // // Ensures that the user doesn't step out of line
094: // private long m_token = 0;
095:
096: // Maintain the image-contents used by the ImageTag when the ImageModel keeps the image 'inMemory'
097: private Map m_imageContents = null;
098:
099: // Store the address of the remote user.
100: private String m_hostAddr = null;
101:
102: /** Holds value of property variation. */
103: private String m_variation;
104:
105: /** Is there a user Session? Returns true if there is a user session.
106: * @param request The HTTP request we are processing.
107: * @return true if there is a user session.
108: */
109: public static boolean isUserSession(HttpServletRequest request) {
110: return request.getSession(false) != null ? request.getSession()
111: .getAttribute(USER_ATTRIBUTE) != null : false;
112: }
113:
114: /** Get the UserSession object from the servlet session object. If this is the
115: * first time this routine is called, a new Session Object will be created and
116: * returned. This new object will be added to the session cache, so the next time
117: * this is called the same UserSession object will be returned.
118: * @param request The HTTP request we are processing.
119: * @return This returns either a new, or a current UserSession object.
120: */
121: public static UserSession getUserSession(HttpServletRequest request) {
122: UserSession us = (UserSession) request.getSession()
123: .getAttribute(USER_ATTRIBUTE);
124: if (us == null)
125: us = new UserSession(request);
126: return us;
127: }
128:
129: private UserSession(HttpServletRequest req) {
130: // Initialize Variables
131: m_components = new ListMap();
132: m_widgetCache = new HashMap();
133:
134: // Let the UserSession object keep a reference to the session object so it
135: // can flush attributed out of the attribute cache. This was added so that when a
136: // panel is removed, its associated form object can be dropped from the session cache
137: m_sessionObject = req.getSession();
138:
139: // Store the Address of this request so we can identify this session easier later on
140: // in the session manager tool
141: m_hostAddr = req.getRemoteHost();
142:
143: // Add the user session into the session
144: req.getSession().setAttribute(USER_ATTRIBUTE, this );
145:
146: // Register this session
147: SessionManager.addSession(this );
148: }
149:
150: /** Getter for property sessionId.
151: * @return Value of property sessionId.
152: */
153: public String getSessionId() {
154: return m_sessionId;
155: }
156:
157: /** Setter for property sessionId.
158: * @param sessionId New value of property sessionId.
159: */
160: public void setSessionId(String sessionId) {
161: m_sessionId = sessionId;
162: }
163:
164: /** Getter for property userId.
165: * @return Value of property userId.
166: */
167: public String getUserId() {
168: return m_userId;
169: }
170:
171: /** Setter for property userId.
172: * @param userId New value of property userId.
173: */
174: public void setUserId(String userId) {
175: m_userId = userId;
176: }
177:
178: /** Check to see if the UserSession object is valid. A UserSession is valid if the userId is not null.
179: * @return true if the userId is not null.
180: */
181: public boolean isValid() {
182: return m_userId != null;
183: }
184:
185: /** Returns an object that should contain user data useful to the consuming application.
186: * @return an object that should contain user data useful to the consuming application.
187: */
188: public Object getUserData() {
189: return m_userData;
190: }
191:
192: /** This stores a user reslated object in the frameworks UserSession object.
193: * @param userData This is the application specific object that contains extra user information.
194: */
195: public void setUserData(Object userData) {
196: m_userData = userData;
197: }
198:
199: /** Adds a component to the internal cache.
200: * @param comp the component to be added.
201: */
202: public void addComponent(Component comp) {
203: m_components.put(comp.getComponentId(), comp);
204: }
205:
206: /** Get a Component object based on a componentId from the list of created components that have been created by this user.
207: * @param compId the componentId.
208: * @return the Component object based on the componentId.
209: */
210: public Component getComponent(String compId) {
211: Component c = (Component) m_components.get(compId);
212: return c;
213: }
214:
215: /** Remove the component from the internal cache.
216: * @param comp The component to be removed.
217: */
218: public void removeComponent(Component comp) {
219: String componentId = comp.getComponentId();
220: m_components.remove(componentId);
221: m_widgetCache.remove(componentId);
222: }
223:
224: /** Get a named attribute from the servlet session.
225: * @param name The attribute name.
226: * @return The named object from HttpSession associated to the UserSession. Returns null if named obejct is not found.
227: */
228: public Object getSessionObject(String name) {
229: return m_sessionObject.getAttribute(name);
230: }
231:
232: /** Drop a named attribute from the servlet session.
233: * @param name The attribute name.
234: */
235: public void dropSessionObject(String name) {
236: if (m_sessionObject != null) {
237: Object obj = m_sessionObject.getAttribute(name);
238:
239: // invoke the cleanup() method for a form-bean
240: if (obj != null && obj instanceof FormBase)
241: ((FormBase) obj).cleanup();
242:
243: // remove the object from the session
244: m_sessionObject.removeAttribute(name);
245:
246: if (log.isDebugEnabled())
247: log.debug("Removed Object '" + name
248: + "' from Session Cache");
249: }
250: }
251:
252: /** This function kills the user session object.
253: * It remove itself from the HttpSession cache.
254: * It will kill all related components.
255: * It will de-register from the Session Manager.
256: * It will null out all internal references.
257: */
258: public void kill() {
259: // Remove from HttpSession
260: if (m_sessionObject != null) {
261: if (log.isDebugEnabled())
262: log
263: .debug("Removing the UserSession from the HttpSession. This should in turn call the valueUnbound method.");
264: m_sessionObject.removeAttribute(USER_ATTRIBUTE);
265: }
266: }
267:
268: /** This function kills all related components.
269: */
270: public void killAllComponents() {
271: if (m_components != null) {
272: // Create an array of componentIds
273: String[] componentIds = new String[m_components.size()];
274: Iterator itr = m_components.keySet().iterator();
275: for (int i = 0; itr.hasNext(); i++)
276: componentIds[i] = (String) itr.next();
277:
278: // Now loop thru the array.. Get the component.. & kill it (if its still alive)
279: // Note: its quite possible that a component could have been killed by another !!!
280: for (int i = 0; i < componentIds.length; i++) {
281: Component c = (Component) m_components
282: .get(componentIds[i]);
283: if (c != null) {
284: if (log.isDebugEnabled())
285: log.debug("Killing Component : "
286: + c.getComponentId()
287: + " - "
288: + c.getComponentDefinition()
289: .getComponentName());
290: c.quit();
291: }
292: }
293: }
294: // Object[] components = m_components.values().toArray();
295: // for(int i = 0; i < components.length; i++) {
296: // Component c = (Component) components[i];
297: // if (log.isDebugEnabled())
298: // log.debug("Killing Component : " + c.getComponentId() + " - " + c.getComponentDefinition().getComponentName() );
299: // // Quit Component
300: // c.quit();
301: // }
302: }
303:
304: // public String getCurrentToken() {
305: // return Long.toString(m_token);
306: // }
307: //
308: // public String getNewToken() {
309: // return Long.toString(++m_token);
310: // }
311:
312: /** Returns a new componentId.
313: * @return a new componentId.
314: */
315: public String getNextComponentId() {
316: return Long.toString(++m_lastComponentId);
317: }
318:
319: /** Return an existing WidgetCache for the key.
320: * Create a new WidgetCache if it doesnt already exist.
321: * The key will preferrably be a componentId.
322: * @param key The key to be used for the widget cache.
323: * @return a WidgetCache for the key.
324: */
325: public WidgetCache getWidgetCache(String key) {
326: WidgetCache wc = (WidgetCache) m_widgetCache.get(key);
327: if (wc == null) {
328: wc = new WidgetCache();
329: m_widgetCache.put(key, wc);
330: }
331: return wc;
332: }
333:
334: /** Returns the image for the input key.
335: * The ImageModel may store the image contents into the UserSession (instead of the local filesystem).
336: * The getImage.jsp will then use this method to display the image.
337: * @param key The key.
338: * @return the image for the input key.
339: */
340: public byte[] getImageContents(String key) {
341: if (m_imageContents != null) {
342: Object[] image = (Object[]) m_imageContents.get(key);
343: return (byte[]) image[0];
344: } else {
345: return null;
346: }
347: }
348:
349: /** Returns the image mime-type for the input key.
350: * The ImageModel may store the image contents into the UserSession (instead of the local filesystem).
351: * The getImage.jsp will then use this method to display the image.
352: * @param key The key.
353: * @return the image mime-type for the input key.
354: */
355: public String getImageMimeType(String key) {
356: if (m_imageContents != null) {
357: Object[] image = (Object[]) m_imageContents.get(key);
358: return (String) image[1];
359: } else {
360: return null;
361: }
362: }
363:
364: /** Adds the image and its mime-type to the UserSession.
365: * The ImageModel may store the image contents into the UserSession (instead of the local filesystem).
366: * The getImage.jsp will then use this method to display the image.
367: * @param key The key to be used for storing the image.
368: * @param imageContents The image.
369: * @param mimeType The mime-type.
370: */
371: public void addImage(String key, byte[] imageContents,
372: String mimeType) {
373: if (m_imageContents == null)
374: m_imageContents = new HashMap();
375: Object[] image = new Object[] { imageContents, mimeType };
376: m_imageContents.put(key, image);
377: }
378:
379: /** Removes the image for the input key.
380: * @param key The key.
381: */
382: public void removeImage(String key) {
383: if (m_imageContents != null)
384: m_imageContents.remove(key);
385: }
386:
387: /** This ensures that a cleanup is performed at the time of garbage-collection.
388: * @throws Throwable if any error occurs.
389: */
390: public void finalize() throws Throwable {
391: kill();
392: super .finalize();
393: }
394:
395: /** Display all internal Session Info in the System.out stream.
396: */
397: public void showInfo() {
398: PrintStream p = System.out;
399: p.println("---User Info----");
400: p.println("User Id: " + m_userId);
401:
402: p.println("Components...");
403: for (Iterator i = m_components.keySet().iterator(); i.hasNext();) {
404: String key = (String) i.next();
405: Component c = (Component) m_components.get(key);
406: p.println(" Id: " + c.getComponentId());
407: p.println(" Name: " + c.getClass().getName());
408: p.println("");
409: }
410: }
411:
412: /** Added for the administration tool to get data about the session
413: * @return The HttpSession to which this object belongs.
414: */
415: public HttpSession getHttpSession() {
416: return m_sessionObject;
417: }
418:
419: /** Added for the administration tool to get data about the components
420: * @return The list of components for the user.
421: */
422: public Collection getComponents() {
423: if (m_components == null)
424: return null;
425: else
426: return m_components.values();
427: }
428:
429: /** Get the host address of this user, this is based on the host obtained in the first request
430: * that was used to establish the session
431: * @return host address of this user.
432: */
433: public String getUserHostAddr() {
434: return m_hostAddr;
435: }
436:
437: /** This will perform garbage collection of idle components. Idle components are those components which have had no activity performed in the last 'timeOutMinutes' minutes.
438: * @param timeOutMinutes The minutes used to determine idle components.
439: */
440: public void garbageCollectIdleComponents(int timeOutMinutes) {
441: // create an array of Component objects
442: Component[] components = new Component[m_components.size()];
443: Iterator itr = m_components.values().iterator();
444: for (int i = 0; itr.hasNext(); i++)
445: components[i] = (Component) itr.next();
446:
447: for (int i = 0; i < components.length; i++) {
448: Component component = components[i];
449:
450: if (component.isActive()) {
451: // Collection of componentIds. This will avoid circular references
452: Collection checkedComponentIdsList = new ArrayList();
453:
454: if (hasComponentTimedOut(component, DateTime.addMinute(
455: new DateTime(), -timeOutMinutes),
456: checkedComponentIdsList)) {
457: if (log.isDebugEnabled())
458: log.debug("Garbage collecting component: "
459: + component.getComponentId());
460: component.quit();
461: }
462: }
463: }
464: }
465:
466: private boolean hasComponentTimedOut(Component component,
467: DateTime time, Collection checkedComponentIdsList) {
468: boolean timedOut;
469: if (!component.isActive()) {
470: // assume Inactive component = TimedOut component
471: timedOut = true;
472: } else {
473: timedOut = component.returnLastActivityDate()
474: .isBefore(time);
475: if (timedOut
476: && !checkedComponentIdsList.contains(component
477: .getComponentId())) {
478: checkedComponentIdsList.add(component.getComponentId());
479: Collection childComponents = component
480: .returnChildComponents();
481: if (childComponents != null) {
482: for (Iterator i = childComponents.iterator(); i
483: .hasNext();) {
484: Component childComponent = (Component) i.next();
485: timedOut = hasComponentTimedOut(childComponent,
486: time, checkedComponentIdsList);
487: if (!timedOut)
488: break;
489: }
490: }
491: }
492: }
493: return timedOut;
494: }
495:
496: /** This is invoked, whenever an instance of this class is added to the HttpSession object.
497: * Currently, this method will do nothing.
498: * @param httpSessionBindingEvent the event that identifies the session.
499: */
500: public void valueBound(
501: HttpSessionBindingEvent httpSessionBindingEvent) {
502: }
503:
504: /** This is invoked, whenever an instance of this class is removed from the HttpSession object.
505: * This can happen by an explicit session.removeAttribute(), or if the HttpSession is invalidated or if the HttpSession times out.
506: * It will kill all related components.
507: * It will de-register from the Session Manager.
508: * It will null out all internal references.
509: * @param httpSessionBindingEvent the event that identifies the session.
510: */
511: public void valueUnbound(
512: HttpSessionBindingEvent httpSessionBindingEvent) {
513: if (log.isDebugEnabled())
514: log
515: .debug("The valueUnbound method has been invoked. This will kill the UserSession.");
516:
517: // kill this usersession
518: killUserSession();
519: }
520:
521: /** This function kills the user session object.
522: * It will kill all related components.
523: * It will de-register from the Session Manager.
524: * It will null out all internal references.
525: */
526: private void killUserSession() {
527: log.debug("Killing User Session : " + m_userId);
528:
529: // kill all related components
530: killAllComponents();
531:
532: // Remove from Session Manager
533: SessionManager.removeSession(this );
534:
535: // Remove extra references.
536: m_userId = null;
537: m_userData = null;
538: m_sessionObject = null;
539: m_components = null;
540: m_widgetCache = null;
541: m_imageContents = null;
542: m_sessionObject = null;
543: }
544:
545: /** Getter for property variation.
546: * @return Value of property variation.
547: */
548: public String getVariation() {
549: return m_variation;
550: }
551:
552: /** Setter for property variation.
553: * @param variation New value of property variation.
554: */
555: public void setVariation(String variation) {
556: m_variation = variation;
557: }
558:
559: }
|