001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.impl;
017:
018: import java.io.IOException;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027: import java.util.SortedSet;
028: import java.util.TreeSet;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.directwebremoting.ScriptBuffer;
033: import org.directwebremoting.extend.EnginePrivate;
034: import org.directwebremoting.extend.MarshallException;
035: import org.directwebremoting.extend.RealScriptSession;
036: import org.directwebremoting.extend.ScriptConduit;
037:
038: /**
039: * An implementation of ScriptSession and RealScriptSession.
040: * <p>There are synchronization constraints on this class. See the field
041: * comments of the type: <code>GuardedBy("lock")</code>.
042: * <p>In addition you should note that {@link DefaultScriptSession} and
043: * {@link DefaultScriptSessionManager} make calls to each other and you should
044: * take care not to break any constraints in inheriting from these classes.
045: * @author Joe Walker [joe at getahead dot ltd dot uk]
046: */
047: public class DefaultScriptSession implements RealScriptSession {
048: /**
049: * Simple constructor
050: * @param id The new unique identifier for this session
051: * @param manager The manager that created us
052: * @param page The URL of the page on which we sit
053: * @param httpSessionId The cookie based id of the browser
054: */
055: protected DefaultScriptSession(String id,
056: DefaultScriptSessionManager manager, String page,
057: String httpSessionId) {
058: this .id = id;
059: if (id == null) {
060: throw new IllegalArgumentException("id can not be null");
061: }
062:
063: this .httpSessionId = httpSessionId;
064: this .page = page;
065: this .manager = manager;
066: this .creationTime = System.currentTimeMillis();
067: this .lastAccessedTime = creationTime;
068: }
069:
070: /* (non-Javadoc)
071: * @see org.directwebremoting.ScriptSession#getAttribute(java.lang.String)
072: */
073: public Object getAttribute(String name) {
074: invalidateIfNeeded();
075: synchronized (attributes) {
076: return attributes.get(name);
077: }
078: }
079:
080: /* (non-Javadoc)
081: * @see org.directwebremoting.ScriptSession#setAttribute(java.lang.String, java.lang.Object)
082: */
083: public void setAttribute(String name, Object value) {
084: invalidateIfNeeded();
085: synchronized (attributes) {
086: attributes.put(name, value);
087: }
088: }
089:
090: /* (non-Javadoc)
091: * @see org.directwebremoting.ScriptSession#removeAttribute(java.lang.String)
092: */
093: public void removeAttribute(String name) {
094: invalidateIfNeeded();
095: synchronized (attributes) {
096: attributes.remove(name);
097: }
098: }
099:
100: /* (non-Javadoc)
101: * @see org.directwebremoting.ScriptSession#getAttributeNames()
102: */
103: public Iterator<String> getAttributeNames() {
104: invalidateIfNeeded();
105: synchronized (attributes) {
106: Set<String> keys = Collections.unmodifiableSet(attributes
107: .keySet());
108: return keys.iterator();
109: }
110: }
111:
112: /* (non-Javadoc)
113: * @see org.directwebremoting.ScriptSession#invalidate()
114: */
115: public void invalidate() {
116: synchronized (invalidLock) {
117: invalidated = true;
118: manager.invalidate(this );
119: }
120: }
121:
122: /* (non-Javadoc)
123: * @see org.directwebremoting.ScriptSession#isInvalidated()
124: */
125: public boolean isInvalidated() {
126: synchronized (invalidLock) {
127: return invalidated;
128: }
129: }
130:
131: /* (non-Javadoc)
132: * @see org.directwebremoting.ScriptSession#getId()
133: */
134: public String getId() {
135: return id;
136: }
137:
138: /* (non-Javadoc)
139: * @see org.directwebremoting.ScriptSession#getCreationTime()
140: */
141: public long getCreationTime() {
142: invalidateIfNeeded();
143: return creationTime;
144: }
145:
146: /* (non-Javadoc)
147: * @see org.directwebremoting.ScriptSession#getLastAccessedTime()
148: */
149: public long getLastAccessedTime() {
150: synchronized (invalidLock) {
151: // For many accesses here we check to see if we should invalidate
152: // ourselves, but getLastAccessedTime() is used as part of the process
153: // that DefaultScriptSessionManager goes through in order to check
154: // everything for validity. So if we do this check here then DSSM will
155: // give a ConcurrentModificationException if anything does timeout
156: // checkNotInvalidated();
157: return lastAccessedTime;
158: }
159: }
160:
161: /* (non-Javadoc)
162: * @see org.directwebremoting.ScriptSession#addScript(java.lang.String)
163: */
164: public void addScript(ScriptBuffer script) {
165: invalidateIfNeeded();
166:
167: if (script == null) {
168: throw new NullPointerException("null script");
169: }
170:
171: // First we try to add the script to an existing conduit
172: synchronized (scriptLock) {
173: if (conduits.isEmpty()) {
174: // Are there any other script sessions in the same browser
175: // that could proxy the script for us?
176: Collection<RealScriptSession> sessions = manager
177: .getScriptSessionsByHttpSessionId(httpSessionId);
178: ScriptBuffer proxyScript = EnginePrivate
179: .createForeignWindowProxy(getWindowName(),
180: script);
181:
182: boolean written = false;
183: for (Iterator<RealScriptSession> it = sessions
184: .iterator(); !written && it.hasNext();) {
185: RealScriptSession session = it.next();
186: written = session.addScriptImmediately(proxyScript);
187: }
188:
189: if (!written) {
190: scripts.add(script);
191: }
192: } else {
193: // Try all the conduits, starting with the first
194: boolean written = false;
195: for (Iterator<ScriptConduit> it = conduits.iterator(); !written
196: && it.hasNext();) {
197: ScriptConduit conduit = it.next();
198: try {
199: written = conduit.addScript(script);
200: } catch (Exception ex) {
201: it.remove();
202: log
203: .debug("Failed to write to ScriptConduit, removing from list: "
204: + conduit);
205: }
206: }
207:
208: if (!written) {
209: scripts.add(script);
210: }
211: }
212: }
213: }
214:
215: /* (non-Javadoc)
216: * @see org.directwebremoting.extend.RealScriptSession#addScriptConduit(org.directwebremoting.extend.ScriptConduit)
217: */
218: public void addScriptConduit(ScriptConduit conduit)
219: throws IOException {
220: invalidateIfNeeded();
221:
222: synchronized (scriptLock) {
223: writeScripts(conduit);
224: conduits.add(conduit);
225: }
226: }
227:
228: /* (non-Javadoc)
229: * @see org.directwebremoting.extend.RealScriptSession#writeScripts(org.directwebremoting.extend.ScriptConduit)
230: */
231: public void writeScripts(ScriptConduit conduit) throws IOException {
232: invalidateIfNeeded();
233:
234: synchronized (scriptLock) {
235: for (Iterator<ScriptBuffer> it = scripts.iterator(); it
236: .hasNext();) {
237: ScriptBuffer script = it.next();
238:
239: try {
240: if (conduit.addScript(script)) {
241: it.remove();
242: } else {
243: // If we didn't write this one, don't bother with any more
244: break;
245: }
246: } catch (MarshallException ex) {
247: log.warn(
248: "Failed to convert data. Dropping Javascript: "
249: + script, ex);
250: it.remove();
251: }
252: }
253: }
254: }
255:
256: /* (non-Javadoc)
257: * @see org.directwebremoting.extend.RealScriptSession#removeScriptConduit(org.directwebremoting.extend.ScriptConduit)
258: */
259: public void removeScriptConduit(ScriptConduit conduit) {
260: invalidateIfNeeded();
261:
262: synchronized (scriptLock) {
263: boolean removed = conduits.remove(conduit);
264: if (!removed) {
265: log.debug("Removing unattached ScriptConduit: "
266: + conduit);
267: debug();
268: }
269: }
270: }
271:
272: /* (non-Javadoc)
273: * @see org.directwebremoting.extend.RealScriptSession#hasWaitingScripts()
274: */
275: public boolean hasWaitingScripts() {
276: synchronized (scriptLock) {
277: return !scripts.isEmpty();
278: }
279: }
280:
281: /* (non-Javadoc)
282: * @see org.directwebremoting.ScriptSession#getPage()
283: */
284: public String getPage() {
285: return page;
286: }
287:
288: /* (non-Javadoc)
289: * @see org.directwebremoting.extend.RealScriptSession#setWindowName(java.lang.String)
290: */
291: public void setWindowName(String windowName) {
292: this .windowName = windowName;
293: }
294:
295: /* (non-Javadoc)
296: * @see org.directwebremoting.extend.RealScriptSession#getWindowName()
297: */
298: public String getWindowName() {
299: return windowName;
300: }
301:
302: /* (non-Javadoc)
303: * @see org.directwebremoting.extend.RealScriptSession#updateLastAccessedTime()
304: */
305: public void updateLastAccessedTime() {
306: // It's a bad idea to call native methods with locks held
307: long temp = System.currentTimeMillis();
308:
309: synchronized (invalidLock) {
310: lastAccessedTime = temp;
311: }
312: }
313:
314: /**
315: * Check that we are still valid and throw an IllegalStateException if not.
316: * At the same time set the lastAccessedTime flag.
317: * @throws IllegalStateException If this object has become invalid
318: */
319: protected void invalidateIfNeeded() {
320: if (invalidated) {
321: return;
322: }
323:
324: // It's a bad idea to call native methods with locks held
325: long now = System.currentTimeMillis();
326:
327: synchronized (invalidLock) {
328: long age = now - lastAccessedTime;
329: if (age > manager.getScriptSessionTimeout()) {
330: invalidate();
331: }
332: }
333: }
334:
335: /**
336: * Some debug output
337: */
338: private void debug() {
339: if (log.isDebugEnabled()) {
340: log.debug("Known ScriptConduits:");
341: for (ScriptConduit scriptConduit : conduits) {
342: log.debug("- " + scriptConduit);
343: }
344: }
345: }
346:
347: /* (non-Javadoc)
348: * @see java.lang.Object#hashCode()
349: */
350: @Override
351: public int hashCode() {
352: return 572 + id.hashCode();
353: }
354:
355: /* (non-Javadoc)
356: * @see java.lang.Object#equals(java.lang.Object)
357: */
358: @Override
359: public boolean equals(Object obj) {
360: if (obj == null) {
361: return false;
362: }
363:
364: if (obj == this ) {
365: return true;
366: }
367:
368: if (!this .getClass().equals(obj.getClass())) {
369: return false;
370: }
371:
372: DefaultScriptSession that = (DefaultScriptSession) obj;
373:
374: return this .id.equals(that.id);
375:
376: }
377:
378: /* (non-Javadoc)
379: * @see java.lang.Object#toString()
380: */
381: @Override
382: public String toString() {
383: return "DefaultScriptSession[id=" + id + "]";
384: }
385:
386: /**
387: * The server side attributes for this page.
388: * <p>GuardedBy("attributes")
389: */
390: protected final Map<String, Object> attributes = Collections
391: .synchronizedMap(new HashMap<String, Object>());
392:
393: /**
394: * When the the web page that we represent last contact us using DWR?
395: * <p>GuardedBy("invalidLock")
396: */
397: private long lastAccessedTime = 0L;
398:
399: /**
400: * Have we been made invalid?
401: * <p>GuardedBy("invalidLock")
402: */
403: private boolean invalidated = false;
404:
405: /**
406: * The object that we use to synchronize against when we want to work with
407: * the invalidation state of this object
408: */
409: private final Object invalidLock = new Object();
410:
411: /**
412: * The script conduits that we can use to transfer data to the browser.
413: * <p>GuardedBy("scriptLock")
414: */
415: protected final SortedSet<ScriptConduit> conduits = new TreeSet<ScriptConduit>();
416:
417: /**
418: * The list of waiting scripts.
419: * <p>GuardedBy("scriptLock")
420: */
421: protected final List<ScriptBuffer> scripts = new ArrayList<ScriptBuffer>();
422:
423: /**
424: * The object that we use to synchronize against when we want to alter
425: * the path of scripts (to conduits or the scripts list)
426: */
427: private final Object scriptLock = new Object();
428:
429: /**
430: * What is our page session id?
431: * <p>This should not need careful synchronization since it is unchanging
432: */
433: protected final String id;
434:
435: /**
436: * When we we created?
437: * <p>This should not need careful synchronization since it is unchanging
438: */
439: protected final long creationTime;
440:
441: /**
442: * The page being viewed.
443: */
444: protected final String page;
445:
446: /**
447: * We track window names to link script sessions together and to help
448: * foil the 2 connection limit
449: */
450: private String windowName;
451:
452: /**
453: * The cookie based id of the browser, or null if no http session is active
454: */
455: private String httpSessionId;
456:
457: /**
458: * The session manager that collects sessions together
459: * <p>This should not need careful synchronization since it is unchanging
460: */
461: protected final DefaultScriptSessionManager manager;
462:
463: /**
464: * The log stream
465: */
466: private static final Log log = LogFactory
467: .getLog(DefaultScriptSession.class);
468:
469: /* (non-Javadoc)
470: * @see org.directwebremoting.extend.RealScriptSession#addScriptImmediately(org.directwebremoting.ScriptBuffer)
471: */
472: public boolean addScriptImmediately(ScriptBuffer script) {
473: // TODO Auto-generated method stub
474: return false;
475: }
476: }
|