001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.ha.session;
019:
020: import java.io.Externalizable;
021: import java.io.IOException;
022: import java.io.NotSerializableException;
023: import java.io.ObjectInput;
024: import java.io.ObjectOutput;
025: import java.io.Serializable;
026: import java.security.Principal;
027: import java.util.ArrayList;
028: import java.util.Enumeration;
029: import java.util.HashMap;
030: import java.util.Hashtable;
031: import java.util.concurrent.locks.Lock;
032: import java.util.concurrent.locks.ReentrantReadWriteLock;
033: import javax.servlet.http.HttpSession;
034: import javax.servlet.http.HttpSessionContext;
035:
036: import org.apache.catalina.Manager;
037: import org.apache.catalina.ha.ClusterManager;
038: import org.apache.catalina.ha.ClusterSession;
039: import org.apache.catalina.realm.GenericPrincipal;
040: import org.apache.catalina.session.StandardSession;
041: import org.apache.catalina.tribes.io.ReplicationStream;
042: import org.apache.catalina.tribes.tipis.ReplicatedMapEntry;
043: import org.apache.catalina.util.Enumerator;
044: import org.apache.catalina.util.StringManager;
045: import org.apache.catalina.session.StandardManager;
046: import org.apache.catalina.session.ManagerBase;
047: import java.util.concurrent.atomic.AtomicInteger;
048:
049: /**
050: *
051: * Similar to the StandardSession except that this session will keep
052: * track of deltas during a request.
053: *
054: * @author Filip Hanik
055: * @version $Revision: 522786 $ $Date: 2007-03-27 08:52:10 +0200 (mar., 27 mars 2007) $
056: */
057:
058: public class DeltaSession extends StandardSession implements
059: Externalizable, ClusterSession, ReplicatedMapEntry {
060:
061: public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
062: .getLog(DeltaSession.class);
063:
064: /**
065: * The string manager for this package.
066: */
067: protected static StringManager sm = StringManager
068: .getManager(Constants.Package);
069:
070: // ----------------------------------------------------- Instance Variables
071:
072: /**
073: * only the primary session will expire, or be able to expire due to
074: * inactivity. This is set to false as soon as I receive this session over
075: * the wire in a session message. That means that someone else has made a
076: * request on another server.
077: */
078: private transient boolean isPrimarySession = true;
079:
080: /**
081: * The delta request contains all the action info
082: *
083: */
084: private transient DeltaRequest deltaRequest = null;
085:
086: /**
087: * Last time the session was replicatd, used for distributed expiring of
088: * session
089: */
090: private transient long lastTimeReplicated = System
091: .currentTimeMillis();
092:
093: protected Lock diffLock = new ReentrantReadWriteLock().writeLock();
094:
095: private long version;
096:
097: // ----------------------------------------------------------- Constructors
098:
099: /**
100: * Construct a new Session associated with the specified Manager.
101: *
102: * @param manager
103: * The manager with which this Session is associated
104: */
105: public DeltaSession() {
106: this (null);
107: }
108:
109: public DeltaSession(Manager manager) {
110: super (manager);
111: this .resetDeltaRequest();
112: }
113:
114: // ----------------------------------------------------- ReplicatedMapEntry
115:
116: /**
117: * Has the object changed since last replication
118: * and is not in a locked state
119: * @return boolean
120: */
121: public boolean isDirty() {
122: return getDeltaRequest().getSize() > 0;
123: }
124:
125: /**
126: * If this returns true, the map will extract the diff using getDiff()
127: * Otherwise it will serialize the entire object.
128: * @return boolean
129: */
130: public boolean isDiffable() {
131: return true;
132: }
133:
134: /**
135: * Returns a diff and sets the dirty map to false
136: * @return byte[]
137: * @throws IOException
138: */
139: public byte[] getDiff() throws IOException {
140: return getDeltaRequest().serialize();
141: }
142:
143: public ClassLoader[] getClassLoaders() {
144: if (manager instanceof BackupManager)
145: return ((BackupManager) manager).getClassLoaders();
146: else if (manager instanceof ClusterManagerBase)
147: return ((ClusterManagerBase) manager).getClassLoaders();
148: else if (manager instanceof StandardManager) {
149: StandardManager sm = (StandardManager) manager;
150: return ClusterManagerBase
151: .getClassLoaders(sm.getContainer());
152: } else if (manager instanceof ManagerBase) {
153: ManagerBase mb = (ManagerBase) manager;
154: return ClusterManagerBase
155: .getClassLoaders(mb.getContainer());
156: }//end if
157: return null;
158: }
159:
160: /**
161: * Applies a diff to an existing object.
162: * @param diff byte[]
163: * @param offset int
164: * @param length int
165: * @throws IOException
166: */
167: public void applyDiff(byte[] diff, int offset, int length)
168: throws IOException, ClassNotFoundException {
169: ReplicationStream stream = ((ClusterManager) getManager())
170: .getReplicationStream(diff, offset, length);
171: getDeltaRequest().readExternal(stream);
172: ClassLoader contextLoader = Thread.currentThread()
173: .getContextClassLoader();
174: try {
175: ClassLoader[] loaders = getClassLoaders();
176: if (loaders != null && loaders.length > 0)
177: Thread.currentThread()
178: .setContextClassLoader(loaders[0]);
179: getDeltaRequest().execute(this );
180: } finally {
181: Thread.currentThread().setContextClassLoader(contextLoader);
182: }
183: }
184:
185: /**
186: * Resets the current diff state and resets the dirty flag
187: */
188: public void resetDiff() {
189: resetDeltaRequest();
190: }
191:
192: /**
193: * Lock during serialization
194: */
195: public void lock() {
196: diffLock.lock();
197: }
198:
199: /**
200: * Unlock after serialization
201: */
202: public void unlock() {
203: diffLock.unlock();
204: }
205:
206: public void setOwner(Object owner) {
207: if (owner instanceof ClusterManager && getManager() == null) {
208: ClusterManager cm = (ClusterManager) owner;
209: this .setManager(cm);
210: this .setValid(true);
211: this .setPrimarySession(false);
212: this .access();
213: this .resetDeltaRequest();
214: this .endAccess();
215: }
216: }
217:
218: // ----------------------------------------------------- Session Properties
219:
220: /**
221: * returns true if this session is the primary session, if that is the case,
222: * the manager can expire it upon timeout.
223: */
224: public boolean isPrimarySession() {
225: return isPrimarySession;
226: }
227:
228: /**
229: * Sets whether this is the primary session or not.
230: *
231: * @param primarySession
232: * Flag value
233: */
234: public void setPrimarySession(boolean primarySession) {
235: this .isPrimarySession = primarySession;
236: }
237:
238: /**
239: * Set the session identifier for this session without notify listeners.
240: *
241: * @param id
242: * The new session identifier
243: */
244: public void setIdInternal(String id) {
245: this .id = id;
246: resetDeltaRequest();
247: }
248:
249: /**
250: * Set the session identifier for this session.
251: *
252: * @param id
253: * The new session identifier
254: */
255: public void setId(String id) {
256: super .setId(id);
257: resetDeltaRequest();
258: }
259:
260: /**
261: * Return the last client access time without invalidation check
262: * @see #getLastAccessedTime().
263: */
264: public long getLastAccessedTimeInternal() {
265: return (this .lastAccessedTime);
266: }
267:
268: public void setMaxInactiveInterval(int interval,
269: boolean addDeltaRequest) {
270: super .maxInactiveInterval = interval;
271: if (isValid && interval == 0) {
272: expire();
273: } else {
274: if (addDeltaRequest && (deltaRequest != null))
275: deltaRequest.setMaxInactiveInterval(interval);
276: }
277: }
278:
279: /**
280: * Set the <code>isNew</code> flag for this session.
281: *
282: * @param isNew
283: * The new value for the <code>isNew</code> flag
284: */
285: public void setNew(boolean isNew) {
286: setNew(isNew, true);
287: }
288:
289: public void setNew(boolean isNew, boolean addDeltaRequest) {
290: super .setNew(isNew);
291: if (addDeltaRequest && (deltaRequest != null))
292: deltaRequest.setNew(isNew);
293: }
294:
295: /**
296: * Set the authenticated Principal that is associated with this Session.
297: * This provides an <code>Authenticator</code> with a means to cache a
298: * previously authenticated Principal, and avoid potentially expensive
299: * <code>Realm.authenticate()</code> calls on every request.
300: *
301: * @param principal
302: * The new Principal, or <code>null</code> if none
303: */
304: public void setPrincipal(Principal principal) {
305: setPrincipal(principal, true);
306: }
307:
308: public void setPrincipal(Principal principal,
309: boolean addDeltaRequest) {
310: try {
311: lock();
312: super .setPrincipal(principal);
313: if (addDeltaRequest && (deltaRequest != null))
314: deltaRequest.setPrincipal(principal);
315: } finally {
316: unlock();
317: }
318: }
319:
320: /**
321: * Return the <code>isValid</code> flag for this session.
322: */
323: public boolean isValid() {
324: if (this .expiring) {
325: return true;
326: }
327: if (!this .isValid) {
328: return false;
329: }
330: if (ACTIVITY_CHECK && accessCount.get() > 0) {
331: return true;
332: }
333: if (maxInactiveInterval >= 0) {
334: long timeNow = System.currentTimeMillis();
335: int timeIdle = (int) ((timeNow - this AccessedTime) / 1000L);
336: if (isPrimarySession()) {
337: if (timeIdle >= maxInactiveInterval) {
338: expire(true);
339: }
340: } else {
341: if (timeIdle >= (2 * maxInactiveInterval)) {
342: //if the session has been idle twice as long as allowed,
343: //the primary session has probably crashed, and no other
344: //requests are coming in. that is why we do this. otherwise
345: //we would have a memory leak
346: expire(true, false);
347: }
348: }
349: }
350: return (this .isValid);
351: }
352:
353: // ------------------------------------------------- Session Public Methods
354:
355: /**
356: * Perform the internal processing required to invalidate this session,
357: * without triggering an exception if the session has already expired.
358: *
359: * @param notify
360: * Should we notify listeners about the demise of this session?
361: */
362: public void expire(boolean notify) {
363: expire(notify, true);
364: }
365:
366: public void expire(boolean notify, boolean notifyCluster) {
367: String expiredId = getIdInternal();
368: super .expire(notify);
369:
370: if (notifyCluster) {
371: if (log.isDebugEnabled())
372: log.debug(sm.getString("deltaSession.notifying",
373: ((ClusterManager) manager).getName(),
374: new Boolean(isPrimarySession()), expiredId));
375: if (manager instanceof DeltaManager) {
376: ((DeltaManager) manager).sessionExpired(expiredId);
377: }
378: }
379: }
380:
381: /**
382: * Release all object references, and initialize instance variables, in
383: * preparation for reuse of this object.
384: */
385: public void recycle() {
386: super .recycle();
387: deltaRequest.clear();
388: }
389:
390: /**
391: * Return a string representation of this object.
392: */
393: public String toString() {
394: StringBuffer sb = new StringBuffer();
395: sb.append("DeltaSession[");
396: sb.append(id);
397: sb.append("]");
398: return (sb.toString());
399: }
400:
401: // ------------------------------------------------ Session Package Methods
402:
403: public synchronized void readExternal(ObjectInput in)
404: throws IOException, ClassNotFoundException {
405: readObjectData(in);
406: }
407:
408: /**
409: * Read a serialized version of the contents of this session object from the
410: * specified object input stream, without requiring that the StandardSession
411: * itself have been serialized.
412: *
413: * @param stream
414: * The object input stream to read from
415: *
416: * @exception ClassNotFoundException
417: * if an unknown class is specified
418: * @exception IOException
419: * if an input/output error occurs
420: */
421: public void readObjectData(ObjectInput stream)
422: throws ClassNotFoundException, IOException {
423: readObject(stream);
424: }
425:
426: /**
427: * Write a serialized version of the contents of this session object to the
428: * specified object output stream, without requiring that the
429: * StandardSession itself have been serialized.
430: *
431: * @param stream
432: * The object output stream to write to
433: *
434: * @exception IOException
435: * if an input/output error occurs
436: */
437: public void writeObjectData(ObjectOutput stream) throws IOException {
438: writeObject(stream);
439: }
440:
441: public void resetDeltaRequest() {
442: if (deltaRequest == null) {
443: deltaRequest = new DeltaRequest(getIdInternal(), false);
444: } else {
445: deltaRequest.reset();
446: deltaRequest.setSessionId(getIdInternal());
447: }
448: }
449:
450: public DeltaRequest getDeltaRequest() {
451: if (deltaRequest == null)
452: resetDeltaRequest();
453: return deltaRequest;
454: }
455:
456: // ------------------------------------------------- HttpSession Properties
457:
458: // ----------------------------------------------HttpSession Public Methods
459:
460: /**
461: * Remove the object bound with the specified name from this session. If the
462: * session does not have an object bound with this name, this method does
463: * nothing.
464: * <p>
465: * After this method executes, and if the object implements
466: * <code>HttpSessionBindingListener</code>, the container calls
467: * <code>valueUnbound()</code> on the object.
468: *
469: * @param name
470: * Name of the object to remove from this session.
471: * @param notify
472: * Should we notify interested listeners that this attribute is
473: * being removed?
474: *
475: * @exception IllegalStateException
476: * if this method is called on an invalidated session
477: */
478: public void removeAttribute(String name, boolean notify) {
479: removeAttribute(name, notify, true);
480: }
481:
482: public void removeAttribute(String name, boolean notify,
483: boolean addDeltaRequest) {
484: // Validate our current state
485: if (!isValid())
486: throw new IllegalStateException(sm
487: .getString("standardSession.removeAttribute.ise"));
488: removeAttributeInternal(name, notify, addDeltaRequest);
489: }
490:
491: /**
492: * Bind an object to this session, using the specified name. If an object of
493: * the same name is already bound to this session, the object is replaced.
494: * <p>
495: * After this method executes, and if the object implements
496: * <code>HttpSessionBindingListener</code>, the container calls
497: * <code>valueBound()</code> on the object.
498: *
499: * @param name
500: * Name to which the object is bound, cannot be null
501: * @param value
502: * Object to be bound, cannot be null
503: *
504: * @exception IllegalArgumentException
505: * if an attempt is made to add a non-serializable object in
506: * an environment marked distributable.
507: * @exception IllegalStateException
508: * if this method is called on an invalidated session
509: */
510: public void setAttribute(String name, Object value) {
511: setAttribute(name, value, true, true);
512: }
513:
514: public void setAttribute(String name, Object value, boolean notify,
515: boolean addDeltaRequest) {
516:
517: // Name cannot be null
518: if (name == null)
519: throw new IllegalArgumentException(sm
520: .getString("standardSession.setAttribute.namenull"));
521:
522: // Null value is the same as removeAttribute()
523: if (value == null) {
524: removeAttribute(name);
525: return;
526: }
527:
528: try {
529: lock();
530: super .setAttribute(name, value, notify);
531: if (addDeltaRequest && (deltaRequest != null))
532: deltaRequest.setAttribute(name, value);
533: } finally {
534: unlock();
535: }
536: }
537:
538: // -------------------------------------------- HttpSession Private Methods
539:
540: /**
541: * Read a serialized version of this session object from the specified
542: * object input stream.
543: * <p>
544: * <b>IMPLEMENTATION NOTE </b>: The reference to the owning Manager is not
545: * restored by this method, and must be set explicitly.
546: *
547: * @param stream
548: * The input stream to read from
549: *
550: * @exception ClassNotFoundException
551: * if an unknown class is specified
552: * @exception IOException
553: * if an input/output error occurs
554: */
555: private void readObject(ObjectInput stream)
556: throws ClassNotFoundException, IOException {
557:
558: // Deserialize the scalar instance variables (except Manager)
559: authType = null; // Transient only
560: creationTime = ((Long) stream.readObject()).longValue();
561: lastAccessedTime = ((Long) stream.readObject()).longValue();
562: maxInactiveInterval = ((Integer) stream.readObject())
563: .intValue();
564: isNew = ((Boolean) stream.readObject()).booleanValue();
565: isValid = ((Boolean) stream.readObject()).booleanValue();
566: this AccessedTime = ((Long) stream.readObject()).longValue();
567: version = ((Long) stream.readObject()).longValue();
568: boolean hasPrincipal = stream.readBoolean();
569: principal = null;
570: if (hasPrincipal) {
571: principal = SerializablePrincipal.readPrincipal(stream,
572: getManager().getContainer().getRealm());
573: }
574:
575: // setId((String) stream.readObject());
576: id = (String) stream.readObject();
577: if (log.isDebugEnabled())
578: log.debug(sm.getString("deltaSession.readSession", id));
579:
580: // Deserialize the attribute count and attribute values
581: if (attributes == null)
582: attributes = new Hashtable();
583: int n = ((Integer) stream.readObject()).intValue();
584: boolean isValidSave = isValid;
585: isValid = true;
586: for (int i = 0; i < n; i++) {
587: String name = (String) stream.readObject();
588: Object value = (Object) stream.readObject();
589: if ((value instanceof String)
590: && (value.equals(NOT_SERIALIZED)))
591: continue;
592: attributes.put(name, value);
593: }
594: isValid = isValidSave;
595:
596: if (listeners == null) {
597: listeners = new ArrayList();
598: }
599:
600: if (notes == null) {
601: notes = new Hashtable();
602: }
603: activate();
604: }
605:
606: public synchronized void writeExternal(ObjectOutput out)
607: throws java.io.IOException {
608: writeObject(out);
609: }
610:
611: /**
612: * Write a serialized version of this session object to the specified object
613: * output stream.
614: * <p>
615: * <b>IMPLEMENTATION NOTE </b>: The owning Manager will not be stored in the
616: * serialized representation of this Session. After calling
617: * <code>readObject()</code>, you must set the associated Manager
618: * explicitly.
619: * <p>
620: * <b>IMPLEMENTATION NOTE </b>: Any attribute that is not Serializable will
621: * be unbound from the session, with appropriate actions if it implements
622: * HttpSessionBindingListener. If you do not want any such attributes, be
623: * sure the <code>distributable</code> property of the associated Manager
624: * is set to <code>true</code>.
625: *
626: * @param stream
627: * The output stream to write to
628: *
629: * @exception IOException
630: * if an input/output error occurs
631: */
632: private void writeObject(ObjectOutput stream) throws IOException {
633: // Write the scalar instance variables (except Manager)
634: stream.writeObject(new Long(creationTime));
635: stream.writeObject(new Long(lastAccessedTime));
636: stream.writeObject(new Integer(maxInactiveInterval));
637: stream.writeObject(new Boolean(isNew));
638: stream.writeObject(new Boolean(isValid));
639: stream.writeObject(new Long(this AccessedTime));
640: stream.writeObject(new Long(version));
641: stream.writeBoolean(getPrincipal() != null);
642: if (getPrincipal() != null) {
643: SerializablePrincipal.writePrincipal(
644: (GenericPrincipal) principal, stream);
645: }
646:
647: stream.writeObject(id);
648: if (log.isDebugEnabled())
649: log.debug(sm.getString("deltaSession.writeSession", id));
650:
651: // Accumulate the names of serializable and non-serializable attributes
652: String keys[] = keys();
653: ArrayList saveNames = new ArrayList();
654: ArrayList saveValues = new ArrayList();
655: for (int i = 0; i < keys.length; i++) {
656: Object value = null;
657: value = attributes.get(keys[i]);
658: if (value == null)
659: continue;
660: else if (value instanceof Serializable) {
661: saveNames.add(keys[i]);
662: saveValues.add(value);
663: }
664: }
665:
666: // Serialize the attribute count and the Serializable attributes
667: int n = saveNames.size();
668: stream.writeObject(new Integer(n));
669: for (int i = 0; i < n; i++) {
670: stream.writeObject((String) saveNames.get(i));
671: try {
672: stream.writeObject(saveValues.get(i));
673: } catch (NotSerializableException e) {
674: log.error(sm.getString(
675: "standardSession.notSerializable", saveNames
676: .get(i), id), e);
677: stream.writeObject(NOT_SERIALIZED);
678: log.error(" storing attribute '" + saveNames.get(i)
679: + "' with value NOT_SERIALIZED");
680: }
681: }
682:
683: }
684:
685: // -------------------------------------------------------- Private Methods
686:
687: /**
688: * Return the value of an attribute without a check for validity.
689: */
690: protected Object getAttributeInternal(String name) {
691: return (attributes.get(name));
692: }
693:
694: protected void removeAttributeInternal(String name, boolean notify,
695: boolean addDeltaRequest) {
696: try {
697: lock();
698: // Remove this attribute from our collection
699: Object value = attributes.get(name);
700: if (value == null)
701: return;
702:
703: super .removeAttributeInternal(name, notify);
704: if (addDeltaRequest && (deltaRequest != null))
705: deltaRequest.removeAttribute(name);
706:
707: } finally {
708: unlock();
709: }
710: }
711:
712: protected long getLastTimeReplicated() {
713: return lastTimeReplicated;
714: }
715:
716: public long getVersion() {
717: return version;
718: }
719:
720: protected void setLastTimeReplicated(long lastTimeReplicated) {
721: this .lastTimeReplicated = lastTimeReplicated;
722: }
723:
724: public void setVersion(long version) {
725: this .version = version;
726: }
727:
728: protected void setAccessCount(int count) {
729: if (accessCount == null && ACTIVITY_CHECK)
730: accessCount = new AtomicInteger();
731: if (accessCount != null)
732: super .accessCount.set(count);
733: }
734: }
735:
736: // -------------------------------------------------------------- Private Class
737:
738: /**
739: * This class is a dummy implementation of the <code>HttpSessionContext</code>
740: * interface, to conform to the requirement that such an object be returned when
741: * <code>HttpSession.getSessionContext()</code> is called.
742: *
743: * @author Craig R. McClanahan
744: *
745: * @deprecated As of Java Servlet API 2.1 with no replacement. The interface
746: * will be removed in a future version of this API.
747: */
748:
749: final class StandardSessionContext implements HttpSessionContext {
750:
751: private HashMap dummy = new HashMap();
752:
753: /**
754: * Return the session identifiers of all sessions defined within this
755: * context.
756: *
757: * @deprecated As of Java Servlet API 2.1 with no replacement. This method
758: * must return an empty <code>Enumeration</code> and will be
759: * removed in a future version of the API.
760: */
761: public Enumeration getIds() {
762: return (new Enumerator(dummy));
763: }
764:
765: /**
766: * Return the <code>HttpSession</code> associated with the specified
767: * session identifier.
768: *
769: * @param id
770: * Session identifier for which to look up a session
771: *
772: * @deprecated As of Java Servlet API 2.1 with no replacement. This method
773: * must return null and will be removed in a future version of
774: * the API.
775: */
776: public HttpSession getSession(String id) {
777: return (null);
778: }
779:
780: }
|