All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.impl.DefaultScriptSessionManager Maven / Gradle / Ivy

Go to download

DWR is easy Ajax for Java. It makes it simple to call Java code directly from Javascript. It gets rid of almost all the boiler plate code between the web browser and your Java code.

There is a newer version: 3.0.2-RELEASE
Show newest version
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.directwebremoting.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.event.EventListenerList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ServerContext;
import org.directwebremoting.event.ScriptSessionEvent;
import org.directwebremoting.event.ScriptSessionListener;
import org.directwebremoting.extend.PageNormalizer;
import org.directwebremoting.extend.RealScriptSession;
import org.directwebremoting.extend.ScriptSessionManager;
import org.directwebremoting.util.IdGenerator;
import org.directwebremoting.util.SharedObjects;

/**
 * A default implementation of ScriptSessionManager.
 * 

There are synchronization constraints on this class that could be broken * by subclasses. Specifically anyone accessing either sessionMap * or pageSessionMap must be holding the sessionLock. *

In addition you should note that {@link DefaultScriptSession} and * {@link DefaultScriptSessionManager} make calls to each other and you should * take care not to break any constraints in inheriting from these classes. * @author Joe Walker [joe at getahead dot ltd dot uk] */ public class DefaultScriptSessionManager implements ScriptSessionManager { /** * Setup a timer that will invalidate sessions */ public DefaultScriptSessionManager() { Runnable runnable = new Runnable() { public void run() { maybeCheckTimeouts(); } }; ScheduledThreadPoolExecutor executor = SharedObjects.getScheduledThreadPoolExecutor(); executor.schedule(runnable, 60, TimeUnit.SECONDS); // Maybe we need be able to cancel the executor? // ScheduledFuture future = executor.schedule... } /* (non-Javadoc) * @see org.directwebremoting.ScriptSessionManager#getScriptSession(java.lang.String) */ public RealScriptSession getScriptSession(String id, String page, String httpSessionId) { maybeCheckTimeouts(); DefaultScriptSession scriptSession; synchronized (sessionLock) { scriptSession = sessionMap.get(id); if (scriptSession == null) { return null; } associateScriptSessionAndPage(scriptSession, page); associateScriptSessionAndHttpSession(scriptSession, httpSessionId); // Maybe we should update the access time of the ScriptSession? // scriptSession.updateLastAccessedTime(); // Since this call could come from outside of a call from the // browser, it's not really an indication that this session is still // alive, so we don't. } return scriptSession; } /* (non-Javadoc) * @see org.directwebremoting.extend.ScriptSessionManager#createScriptSession(org.directwebremoting.extend.RealWebContext) */ public RealScriptSession createScriptSession(String page, String httpSessionId) { String id = generator.generateId(16); DefaultScriptSession scriptSession = new DefaultScriptSession(id, this, page, httpSessionId); synchronized (sessionLock) { sessionMap.put(id, scriptSession); associateScriptSessionAndPage(scriptSession, page); associateScriptSessionAndHttpSession(scriptSession, httpSessionId); } // See notes on synchronization in invalidate() fireScriptSessionCreatedEvent(scriptSession); return scriptSession; } /** * Link a script session and an http session in some way * Exactly what we should do here is still something of a mystery. We don't * really have much experience on the best way to handle this, so currently * we're just setting a script session attribute that points at the * http session id, and not exposing it. *

This method is an ideal point to override and do something better. * @param scriptSession The script session to be linked to an http session * @param httpSessionId The http session from the browser with the given scriptSession */ protected void associateScriptSessionAndHttpSession(RealScriptSession scriptSession, String httpSessionId) { scriptSession.setAttribute(ATTRIBUTE_HTTPSESSIONID, httpSessionId); Set scriptSessionIds = sessionXRef.get(httpSessionId); if (scriptSessionIds == null) { scriptSessionIds = new HashSet(); sessionXRef.put(httpSessionId, scriptSessionIds); } scriptSessionIds.add(scriptSession.getId()); } /** * Unlink any http sessions from this script session * @see #associateScriptSessionAndHttpSession(RealScriptSession, String) * @param scriptSession The script session to be unlinked */ protected void disassociateScriptSessionAndHttpSession(RealScriptSession scriptSession) { Object httpSessionId = scriptSession.getAttribute(ATTRIBUTE_HTTPSESSIONID); if (httpSessionId == null) { return; } Set scriptSessionIds = sessionXRef.get(httpSessionId); if (scriptSessionIds == null) { log.debug("Warning: No script session ids for http session"); return; } scriptSessionIds.remove(scriptSession.getId()); if (scriptSessionIds.size() == 0) { sessionXRef.remove(httpSessionId); } scriptSession.setAttribute(ATTRIBUTE_HTTPSESSIONID, null); } /** * Link a script session to a web page. *

This allows people to call {@link ServerContext#getScriptSessionsByPage(String)} *

This method is an ideal point to override and do something better. * @param scriptSession The script session to be linked to a page * @param page The page (un-normalized) to be linked to */ protected void associateScriptSessionAndPage(RealScriptSession scriptSession, String page) { if (page == null) { return; } String normalizedPage = pageNormalizer.normalizePage(page); Set pageSessions = pageSessionMap.get(normalizedPage); if (pageSessions == null) { pageSessions = new HashSet(); pageSessionMap.put(normalizedPage, pageSessions); } pageSessions.add(scriptSession); scriptSession.setAttribute(ATTRIBUTE_PAGE, normalizedPage); } /** * Unlink any pages from this script session * @see #associateScriptSessionAndPage(RealScriptSession, String) * @param scriptSession The script session to be unlinked */ protected void disassociateScriptSessionAndPage(RealScriptSession scriptSession) { for (Set pageSessions : pageSessionMap.values()) { pageSessions.remove(scriptSession); } } /* (non-Javadoc) * @see org.directwebremoting.ScriptSessionManager#getScriptSessionsByPage(java.lang.String) */ public Collection getScriptSessionsByPage(String url) { String normalizedPage = pageNormalizer.normalizePage(url); synchronized (sessionLock) { Set pageSessions = pageSessionMap.get(normalizedPage); if (pageSessions == null) { pageSessions = new HashSet(); } Set reply = new HashSet(); reply.addAll(pageSessions); return reply; } } /** * Lookup all the windows associated with a given browser * @param httpSessionId The browser id to lookup * @return A list of script sessions for each open window */ public Collection getScriptSessionsByHttpSessionId(String httpSessionId) { Collection reply = new ArrayList(); Set scriptSessionIds = sessionXRef.get(httpSessionId); for (String scriptSessionId : scriptSessionIds) { DefaultScriptSession scriptSession = sessionMap.get(scriptSessionId); if (scriptSession != null) { reply.add(scriptSession); } } return reply; } /* (non-Javadoc) * @see org.directwebremoting.ScriptSessionManager#getAllScriptSessions() */ public Collection getAllScriptSessions() { synchronized (sessionLock) { Set reply = new HashSet(); reply.addAll(sessionMap.values()); return reply; } } /** * Remove the given session from the list of sessions that we manage, and * leave it for the GC vultures to pluck. * @param scriptSession The session to get rid of */ protected void invalidate(RealScriptSession scriptSession) { // Can we think of a reason why we need to sync both together? // It feels like a deadlock risk to do so synchronized (sessionLock) { // Due to the way systems get a number of script sessions for a page // and the perform a number of actions on them, we may get a number // of invalidation checks, and therefore calls to invalidate(). // We could protect ourselves from this by having a // 'hasBeenInvalidated' flag, but we're taking the simple option // here of just allowing multiple invalidations. sessionMap.remove(scriptSession.getId()); disassociateScriptSessionAndPage(scriptSession); disassociateScriptSessionAndHttpSession(scriptSession); } // Are there any risks from doing this outside the locks? // The initial analysis is that 'Destroyed' is past tense so you would // have expected it to have happened already. fireScriptSessionDestroyedEvent(scriptSession); } /** * If we call {@link #checkTimeouts()} too often is could bog things down so * we only check every one in a while (default 30 secs); this checks to see * of we need to check, and checks if we do. */ protected void maybeCheckTimeouts() { long now = System.currentTimeMillis(); if (now - scriptSessionCheckTime > lastSessionCheckAt) { checkTimeouts(); lastSessionCheckAt = now; } } /** * Do a check on all the known sessions to see if and have timeout and need * removing. */ protected void checkTimeouts() { long now = System.currentTimeMillis(); List timeouts = new ArrayList(); synchronized (sessionLock) { for (DefaultScriptSession session : sessionMap.values()) { if (session.isInvalidated()) { continue; } long age = now - session.getLastAccessedTime(); if (age > scriptSessionTimeout) { timeouts.add(session); } } for (ScriptSession scriptSession : timeouts) { DefaultScriptSession session = (DefaultScriptSession) scriptSession; session.invalidate(); } } } /* (non-Javadoc) * @see org.directwebremoting.ScriptSessionManager#getScriptSessionTimeout() */ public long getScriptSessionTimeout() { return scriptSessionTimeout; } /* (non-Javadoc) * @see org.directwebremoting.ScriptSessionManager#setScriptSessionTimeout(long) */ public void setScriptSessionTimeout(long scriptSessionTimeout) { this.scriptSessionTimeout = scriptSessionTimeout; } /* (non-Javadoc) * @see org.directwebremoting.extend.ScriptSessionManager#addScriptSessionListener(org.directwebremoting.event.ScriptSessionListener) */ public void addScriptSessionListener(ScriptSessionListener li) { scriptSessionListeners.add(ScriptSessionListener.class, li); } /* (non-Javadoc) * @see org.directwebremoting.extend.ScriptSessionManager#removeScriptSessionListener(org.directwebremoting.event.ScriptSessionListener) */ public void removeScriptSessionListener(ScriptSessionListener li) { scriptSessionListeners.remove(ScriptSessionListener.class, li); } /** * Accessor for the PageNormalizer. * @param pageNormalizer The new PageNormalizer */ public void setPageNormalizer(PageNormalizer pageNormalizer) { this.pageNormalizer = pageNormalizer; } /** * @param scriptSessionCheckTime the scriptSessionCheckTime to set */ public void setScriptSessionCheckTime(long scriptSessionCheckTime) { this.scriptSessionCheckTime = scriptSessionCheckTime; } /** * This should be called whenever a {@link ScriptSession} is created * @param scriptSession The newly created ScriptSession */ protected void fireScriptSessionCreatedEvent(ScriptSession scriptSession) { ScriptSessionEvent ev = new ScriptSessionEvent(scriptSession); Object[] listeners = scriptSessionListeners.getListenerList(); for (int i = 0; i < listeners.length - 2; i += 2) { if (listeners[i] == ScriptSessionListener.class) { ((ScriptSessionListener) listeners[i + 1]).sessionCreated(ev); } } } /** * This should be called whenever a {@link ScriptSession} is destroyed * @param scriptSession The newly destroyed ScriptSession */ protected void fireScriptSessionDestroyedEvent(ScriptSession scriptSession) { ScriptSessionEvent ev = new ScriptSessionEvent(scriptSession); Object[] listeners = scriptSessionListeners.getListenerList(); for (int i = 0; i < listeners.length - 2; i += 2) { if (listeners[i] == ScriptSessionListener.class) { ((ScriptSessionListener) listeners[i + 1]).sessionDestroyed(ev); } } } /** * Use of this attribute is currently discouraged, we may make this public * in a later release. Until then, it may change or be removed without warning. */ public static final String ATTRIBUTE_HTTPSESSIONID = "org.directwebremoting.ScriptSession.HttpSessionId"; /** * Use of this attribute is currently discouraged, we may make this public * in a later release. Until then, it may change or be removed without warning. */ public static final String ATTRIBUTE_PAGE = "org.directwebremoting.ScriptSession.Page"; /** * The list of current {@link ScriptSessionListener}s */ protected EventListenerList scriptSessionListeners = new EventListenerList(); /** * How we create script session ids. */ private static IdGenerator generator = new IdGenerator(); /** * By default we check for sessions that need expiring every 30 seconds */ protected static final long DEFAULT_SESSION_CHECK_TIME = 30000; /** * How we turn pages into the canonical form. */ protected PageNormalizer pageNormalizer; /** * How long do we wait before we timeout script sessions? */ private long scriptSessionTimeout = DEFAULT_TIMEOUT_MILLIS; /** * How often do we check for script sessions that need timing out */ protected long scriptSessionCheckTime = DEFAULT_SESSION_CHECK_TIME; /** * We check for sessions that need timing out every * {@link #scriptSessionCheckTime}; this is when we last checked. */ protected long lastSessionCheckAt = System.currentTimeMillis(); /** * What we synchronize against when we want to access either sessionMap or * pageSessionMap */ protected final Object sessionLock = new Object(); /** * Allows us to associate script sessions with http sessions. * The key is an http session id, the *

GuardedBy("sessionLock") */ private Map> sessionXRef = new HashMap>(); /** * The map of all the known sessions. * The key is the script session id, the value is the session data *

GuardedBy("sessionLock") */ private Map sessionMap = new HashMap(); /** * The map of pages that have sessions. * The key is a normalized page, the value the script sessions that are * known to be currently visiting the page *

GuardedBy("sessionLock") */ private Map> pageSessionMap = new HashMap>(); /** * The log stream */ private static final Log log = LogFactory.getLog(DefaultScriptSessionManager.class); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy