001: // ========================================================================
002: // $Id: AbstractReplicatedStore.java,v 1.5 2004/06/22 16:23:44 jules_gosnell Exp $
003: // Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
004: // ------------------------------------------------------------------------
005: // Licensed under the Apache License, Version 2.0 (the "License");
006: // you may not use this file except in compliance with the License.
007: // You may obtain a copy of the License at
008: // http://www.apache.org/licenses/LICENSE-2.0
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014: // ========================================================================
015:
016: package org.mortbay.j2ee.session;
017:
018: //----------------------------------------
019:
020: import java.lang.reflect.Method;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026:
027: import org.jboss.logging.Logger;
028:
029: //----------------------------------------
030:
031: // implement scavenging
032: // implement setMaxInactiveInterval
033: // look for NYI/TODO
034:
035: // this infrastructure could probably be used across JMS aswell -
036: // think about it...
037:
038: /**
039: * Maintain synchronisation with other States representing the same session by
040: * publishing changes made to ourself and updating ourself according to
041: * notifications published by the other State objects.
042: *
043: * @author <a href="mailto:jules@mortbay.com">Jules Gosnell</a>
044: * @version 1.0
045: */
046:
047: abstract public class AbstractReplicatedStore extends AbstractStore {
048: protected final static Logger _log = Logger
049: .getLogger(AbstractReplicatedStore.class);
050:
051: protected ClassLoader _loader;
052:
053: public AbstractReplicatedStore() {
054: super ();
055: _loader = Thread.currentThread().getContextClassLoader();
056: }
057:
058: public ClassLoader getLoader() {
059: return _loader;
060: }
061:
062: public void setLoader(ClassLoader loader) {
063: _loader = loader;
064: }
065:
066: // ----------------------------------------
067: // tmp hack to prevent infinite loop
068: private final static ThreadLocal _replicating = new ThreadLocal();
069:
070: public static boolean getReplicating() {
071: return _replicating.get() == Boolean.TRUE;
072: }
073:
074: public static void setReplicating(boolean replicating) {
075: _replicating.set(replicating ? Boolean.TRUE : Boolean.FALSE);
076: }
077:
078: // ----------------------------------------
079:
080: public Object clone() {
081: AbstractReplicatedStore ars = (AbstractReplicatedStore) super
082: .clone();
083: ars.setLoader(getLoader());
084: return ars;
085: }
086:
087: protected Map _sessions = new HashMap();
088:
089: public int getSessions() {
090: return _sessions.size();
091: }
092:
093: // ----------------------------------------
094: // Store API - Store LifeCycle
095:
096: public void destroy() // corresponds to ctor
097: {
098: _log.trace("destroying...");
099: _sessions.clear();
100: _sessions = null;
101: setManager(null);
102: super .destroy();
103: _log.trace("...destroyed");
104: }
105:
106: // ----------------------------------------
107: // Store API - State LifeCycle
108:
109: public State newState(String id, int maxInactiveInterval)
110: throws Exception {
111: long creationTime = System.currentTimeMillis();
112:
113: if (!AbstractReplicatedStore.getReplicating()) {
114: Object[] argInstances = { id, new Long(creationTime),
115: new Integer(maxInactiveInterval),
116: new Integer(_actualMaxInactiveInterval) };
117: publish(null, CREATE_SESSION, argInstances);
118: }
119:
120: createSession(id, creationTime, maxInactiveInterval,
121: _actualMaxInactiveInterval);
122:
123: // if we get one - all we have to do is loadState - because we
124: // will have just created it...
125: return loadState(id);
126: }
127:
128: public State loadState(String id) {
129: // pull it out of our cache - if it is not there, it doesn't
130: // exist/hasn't been distributed...
131:
132: Object tmp;
133: synchronized (_sessions) {
134: tmp = _sessions.get(id);
135: }
136: return (State) tmp;
137: }
138:
139: public void storeState(State state) {
140: try {
141: String id = state.getId();
142: synchronized (_sessions) {
143: _sessions.put(id, state);
144: }
145: } catch (Exception e) {
146: _log.error("error storing session", e);
147: }
148: }
149:
150: public void removeState(State state) throws Exception {
151: String id = state.getId();
152:
153: if (!AbstractReplicatedStore.getReplicating()) {
154: Object[] argInstances = { id };
155: publish(null, DESTROY_SESSION, argInstances);
156: }
157:
158: destroySession(id);
159: }
160:
161: // ----------------------------------------
162: // Store API - garbage collection
163:
164: public void scavenge() throws Exception {
165: _log.trace("starting distributed scavenging...");
166: Collection copy;
167: synchronized (_sessions) {
168: copy = new ArrayList(_sessions.values());
169: }
170: if (_log.isTraceEnabled()) {
171: int n;
172: synchronized (_subscribers) {
173: n = _subscribers.size();
174: }
175: _log.trace(copy.size() + " distributed sessions, " + n
176: + " subscribers");
177: }
178: int n = 0;
179: for (Iterator i = copy.iterator(); i.hasNext();) {
180: LocalState state = (LocalState) i.next();
181: if (!state.isValid(_scavengerExtraTime)) {
182: String id = state.getId();
183: if (_log.isDebugEnabled())
184: _log.debug("scavenging distributed session " + id);
185: destroySession(id);
186: i.remove();
187: ++n;
188: }
189: }
190: if (_log.isTraceEnabled())
191: _log.trace("scavenged " + n + " distributed sessions");
192: _log.trace("...finished distributed scavenging");
193: }
194:
195: // ----------------------------------------
196: // Store API - hacks... - NYI/TODO
197:
198: public void passivateSession(StateAdaptor sa) {
199: }
200:
201: public boolean isDistributed() {
202: return true;
203: }
204:
205: // ----------------------------------------
206: // utils
207:
208: public String getContextPath() {
209: return getManager().getContextPath();
210: }
211:
212: // ----------------------------------------
213: // change notification API
214:
215: protected static Map _methodToInteger = new HashMap();
216:
217: protected static Method[] _integerToMethod = new Method[8];
218:
219: protected static Method CREATE_SESSION;
220:
221: protected static Method DESTROY_SESSION;
222:
223: protected static Method TOUCH_SESSIONS;
224:
225: protected static Method SET_LAST_ACCESSED_TIME;
226:
227: static {
228: // this is absolutely horrible and will break if anyone changes
229: // the shape of the interface - but it is a quick, easy and
230: // efficient hack - so I am using it while I think of a better
231: // way...
232: try {
233: int index = 0;
234: Method m = null;
235:
236: // class methods...
237: m = CREATE_SESSION = AbstractReplicatedStore.class
238: .getMethod("createSession", new Class[] {
239: String.class, Long.TYPE, Integer.TYPE,
240: Integer.TYPE });
241: _integerToMethod[index] = m;
242: _methodToInteger.put(m.getName(), new Integer(index));
243: index++;
244:
245: m = DESTROY_SESSION = AbstractReplicatedStore.class
246: .getMethod("destroySession",
247: new Class[] { String.class });
248: _integerToMethod[index] = m;
249: _methodToInteger.put(m.getName(), new Integer(index));
250: index++;
251:
252: m = TOUCH_SESSIONS = AbstractReplicatedStore.class
253: .getMethod("touchSessions", new Class[] {
254: String[].class, Long.TYPE });
255: _integerToMethod[index] = m;
256: _methodToInteger.put(m.getName(), new Integer(index));
257: index++;
258:
259: // instance methods...
260: m = SET_LAST_ACCESSED_TIME = State.class.getMethod(
261: "setLastAccessedTime", new Class[] { Long.TYPE });
262: _integerToMethod[index] = m;
263: _methodToInteger.put(m.getName(), new Integer(index));
264: index++;
265:
266: m = State.class.getMethod("setMaxInactiveInterval",
267: new Class[] { Integer.TYPE });
268: _integerToMethod[index] = m;
269: _methodToInteger.put(m.getName(), new Integer(index));
270: index++;
271:
272: m = State.class.getMethod("setAttribute", new Class[] {
273: String.class, Object.class, Boolean.TYPE });
274: _integerToMethod[index] = m;
275: _methodToInteger.put(m.getName(), new Integer(index));
276: index++;
277:
278: m = State.class.getMethod("setAttributes",
279: new Class[] { Map.class });
280: _integerToMethod[index] = m;
281: _methodToInteger.put(m.getName(), new Integer(index));
282: index++;
283:
284: m = State.class.getMethod("removeAttribute", new Class[] {
285: String.class, Boolean.TYPE });
286: _integerToMethod[index] = m;
287: _methodToInteger.put(m.getName(), new Integer(index));
288: index++;
289: } catch (Exception e) {
290: System.err
291: .println("AbstractReplicatedStore: something went wrong building dispatch tables");
292: e.printStackTrace(System.err);
293: }
294: }
295:
296: abstract protected void publish(String id, Method method,
297: Object[] argInstances);
298:
299: protected void dispatch(String id, Integer methodId,
300: Object[] argInstances) {
301: try {
302: AbstractReplicatedStore.setReplicating(true);
303:
304: Object target = null;
305: if (id == null) {
306: // either this is a class method
307: target = this ;
308: } else {
309: // or an instance method..
310: synchronized (_subscribers) {
311: target = _subscribers.get(id);
312: }
313: }
314:
315: try {
316: Method method = _integerToMethod[methodId.intValue()];
317: if (target == null)
318: _log.warn("null target for " + method);
319: else
320: method.invoke(target, argInstances);
321: } catch (Exception e) {
322: _log
323: .error(
324: "this should never happen - code version mismatch ?",
325: e);
326: }
327: } finally {
328: AbstractReplicatedStore.setReplicating(false);
329: }
330: }
331:
332: public void createSession(String id, long creationTime,
333: int maxInactiveInterval, int actualMaxInactiveInterval) {
334: if (_log.isTraceEnabled())
335: _log.trace("creating replicated session: " + id);
336: State state = new LocalState(id, creationTime,
337: maxInactiveInterval, actualMaxInactiveInterval);
338: synchronized (_sessions) {
339: _sessions.put(id, state);
340: }
341:
342: if (AbstractReplicatedStore.getReplicating()) {
343: // _log.info("trying to promote replicated session");
344: getManager().getHttpSession(id); // should cause creation of corresponding InterceptorStack
345: }
346: }
347:
348: public void destroySession(String id) {
349: if (_log.isTraceEnabled())
350: _log.trace("destroying replicated session: " + id);
351: if (getManager().sessionExists(id))
352: getManager()
353: .destroySession(getManager().getHttpSession(id));
354: synchronized (_sessions) {
355: _sessions.remove(id);
356: }
357: }
358:
359: public void touchSessions(String[] ids, long time) {
360: // _log.info("touching sessions...: "+ids);
361: for (int i = 0; i < ids.length; i++) {
362: String id = ids[i];
363: Object target;
364: // I could synch the whole block. This is slower, but will not
365: // hold up everything else...
366: synchronized (_subscribers) {
367: target = _subscribers.get(id);
368: }
369: try {
370: ((StateInterceptor) target).setLastAccessedTime(time);
371: } catch (Exception e) {
372: _log.warn("unable to touch session: " + id
373: + " probably already removed");
374: }
375: }
376: }
377:
378: //----------------------------------------
379: // subscription - Listener management...
380:
381: protected Map _subscribers = new HashMap();
382:
383: public void subscribe(String id, Object o) {
384: _log.trace("subscribing: " + id);
385: synchronized (_subscribers) {
386: _subscribers.put(id, o);
387: }
388: }
389:
390: public void unsubscribe(String id) {
391: _log.trace("unsubscribing: " + id);
392: synchronized (_subscribers) {
393: _subscribers.remove(id);
394: }
395: }
396: }
|