Index: lams_common/src/java/org/lamsfoundation/lams/commonContext.xml =================================================================== diff -u -ra99d5750b6665025226cb1a4854cbc683debfaad -rebbe77ec5ad6506eca4b642562cf9898c0d7d587 --- lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision a99d5750b6665025226cb1a4854cbc683debfaad) +++ lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision ebbe77ec5ad6506eca4b642562cf9898c0d7d587) @@ -189,4 +189,13 @@ + + + 60 + + Index: lams_common/src/java/org/lamsfoundation/lams/web/session/SessionManager.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/web/session/SessionManager.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/web/session/SessionManager.java (revision ebbe77ec5ad6506eca4b642562cf9898c0d7d587) @@ -0,0 +1,395 @@ +/* + *Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + *This program is free software; you can redistribute it and/or modify + *it under the terms of the GNU General Public License as published by + *the Free Software Foundation; either version 2 of the License, or + *(at your option) any later version. + * + *This program is distributed in the hope that it will be useful, + *but WITHOUT ANY WARRANTY; without even the implied warranty of + *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *GNU General Public License for more details. + * + *You should have received a copy of the GNU General Public License + *along with this program; if not, write to the Free Software + *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + *USA + * + *http://www.gnu.org/licenses/gpl.txt + */ +package org.lamsfoundation.lams.web.session; + + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionContext; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; + +import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; +/** + * + * @author Steve.Ni + * + * $version$ + */ +public class SessionManager{ + + private static final Logger log = Logger.getLogger(SessionManager.class); + + //signlton + private static SessionManager sessionMgr; + + public static final String SYS_SESSION_NAME = "sys_session"; + + //KEY: sessionId, each session will have an identified id. VALUE: SessionImpl instance, which contains + //true session key/value pairs. + private Map sessionContainer = new ConcurrentReaderHashMap(); + //Save current session id + private ThreadLocal currentSessionIdContainer = new ThreadLocal(); + + //The system monitoring thread instance + private Monitor monitor; + //The sleep time in seconds period to monitoring the thread. + //This attribute could be set in spring context.xml. + private short monitorPeriod = 20; + + /** + * Get the singleton instance of this class. + * @return + */ + private static SessionManager getInstance(){ + if(sessionMgr == null) + log.error("init SessionManager failed"); + + return sessionMgr; + } + /** + * Get system level HttpSession by current session id. + * @return HttpSession instanceof org.lamsfoundation.lams.systemsession.SessionManager#SessionImpl + */ + public static HttpSession getSession(){ + String sessionId = (String) getInstance().currentSessionIdContainer.get(); + return getSession(sessionId); + } + /** + * Get system session by given session id. + * @param sessionId + * @return system session. Return an null if the given sessionid can not map to an existed session. + */ + public static HttpSession getSession(String sessionId){ + if(sessionId == null){ + log.error("Failed on finding current system session with null sessionId"); + return null; + } + return (HttpSession) getInstance().sessionContainer.get(sessionId); + + } + + static void createSession(String sessionId){ + //initialize a new one + HttpSession session = getInstance().new SessionImpl(sessionId); + getInstance().sessionContainer.put(sessionId,session); + } + + /** + * Return SessionVisitor of currentSessionId. + * An internal method, only avaliable in package. + * @return + */ + static SessionVisitor getSessionVisitor() { + return (SessionVisitor)getSession(); + } + /** + * An internal method, only avaliable in package. + * @param currentSessionId + */ + static void setCurrentSessionId(String currentSessionId) { + getInstance().currentSessionIdContainer.set(currentSessionId); + } + /** + * This class initialize method called by Spring framework. + */ + public void init(){ + if(sessionMgr == null){ + //only start once + sessionMgr = this; + if (monitorPeriod > 0){ + monitor = new Monitor(); + monitor.start(); + } + } + } + /** + * This class destroy method called by Spring framework. + */ + public void destroy(){ + if(monitor != null){ + sessionMgr = null; + monitor.stop(); + monitor = null; + } + + } + + public short getMonitorPeriod() { + return monitorPeriod; + } + public void setMonitorPeriod(short monitorPeriod) { + this.monitorPeriod = monitorPeriod; + } + + //************************************************************************ + // SYSTEM SESSION MONITOR CLASS + //************************************************************************ + class Monitor implements Runnable{ + private static final String THREAD_NAME = "LAMS SYSTEM SESSION MONITOR"; + private Thread monitoringThread; + private boolean stopSign = false; + public void start(){ + monitoringThread = new Thread(this,THREAD_NAME); + stopSign = false; + monitoringThread.start(); + } + public void run() { + while (!stopSign) { + try { + //check whether session is expired + Iterator iter = sessionContainer.values().iterator(); + while(iter.hasNext()) { + SessionImpl session = (SessionImpl) iter.next(); + if(session.getMaxInactiveInterval() > 0){ + if ((System.currentTimeMillis() - session.getLastAccessedTime() - + session.getMaxInactiveInterval() * 1000L) > 0) + session.invalidate(); + } + } + } catch (Throwable e) { + log.warn("Monitor thread exception: " + e); + } + if (!stopSign) { + try { + Thread.sleep(monitorPeriod * 1000L); + } catch (Exception e) { + // do nothing + } + } + } + } + + public void stop(){ + + if (monitoringThread != null){ + stopSign = true; + monitoringThread.interrupt(); + try{ + monitoringThread.join(); + }catch (InterruptedException ignore){ + log.error("Exception when interruptting Session Monitoring Thread"); + } + monitoringThread = null; + } + } + } + //************************************************************************ + // SYSTEM SESSION IMPLEMENTAION CLASS + //************************************************************************ + class SessionImpl implements HttpSession,SessionVisitor { + + private String sessionId; + private long createTime; + private long accessTime; + private int timeout; + + private Map valueMap; + + public SessionImpl(String sessionId){ + this.sessionId = sessionId; + createTime = System.currentTimeMillis(); + accessTime = createTime; + timeout = Configuration.getAsInt(ConfigurationKeys.INACTIVE_TIME); + valueMap = new ConcurrentReaderHashMap(); + } + /** + * {@inheritDoc} + */ + public long getCreationTime() { + return createTime; + } + /** + * {@inheritDoc} + */ + public String getId() { + return sessionId; + } + /** + * {@inheritDoc} + */ + public long getLastAccessedTime() { + return accessTime; + } + /** + * {@inheritDoc} + */ + public void setMaxInactiveInterval(int timeout) { + this.timeout = timeout; + } + /** + * {@inheritDoc} + */ + public int getMaxInactiveInterval() { + return timeout; + } + + /** + * {@inheritDoc} + */ + public Object getAttribute(String name) { + return valueMap.get(name); + } + /** + * {@inheritDoc} + */ + public Enumeration getAttributeNames() { + + return new Enumeration(){ + Iterator iter = valueMap.keySet().iterator(); + public boolean hasMoreElements() { + return iter.hasNext(); + } + + public Object nextElement() { + return iter.next(); + } + + }; + } + /** + * {@inheritDoc} + */ + public void setAttribute(String name, Object value) { + if(value == null) + removeAttribute(name); + + Object old = valueMap.put(name, value); + + fireBound(name, value); + + if (old != null){ + fireUnbound(name, old); + } + } + /** + * {@inheritDoc} + */ + public void removeAttribute(String name) { + Object value = valueMap.remove(name); + if(value != null) + fireUnbound(name, value); + } + /** + * {@inheritDoc} + */ + public void invalidate() { + + Iterator iter = valueMap.entrySet().iterator(); + while(iter.hasNext()){ + Map.Entry entry = (Map.Entry) iter.next(); + fireUnbound((String) entry.getKey(),entry.getValue()); + } + valueMap.clear(); + } + /** + * Notice: This method always return false + * {@inheritDoc} + */ + public boolean isNew() { + return false; + } + + /** + * {@inheritDoc} + */ + public void putValue(String name, Object value) { + setAttribute(name,value); + } + /** + * {@inheritDoc} + */ + public void removeValue(String name) { + removeAttribute(name); + } + /** + * {@inheritDoc} + */ + public Object getValue(String name) { + return getAttribute(name); + } + /** + * {@inheritDoc} + */ + public String[] getValueNames() { + return (String[])valueMap.keySet().toArray(new String[valueMap.size()]); + } + /** + * {@inheritDoc} + */ + public HttpSessionContext getSessionContext() { + return new HttpSessionContext(){ + + public HttpSession getSession(String sessionId) { + return SessionImpl.this; + } + + public Enumeration getIds() { + return new Enumeration(){ + public boolean hasMoreElements() { + return false; + } + public Object nextElement() { + return null; + } + }; + } + + }; + } + /** + * Notice: This method always return null. + * {@inheritDoc} + */ + public ServletContext getServletContext() { + return null; + } + //********************************************************** + // SessionVisitor method + public void accessed() { + accessTime = System.currentTimeMillis(); + } + //********************************************************** + // private method + private void fireUnbound(String name, Object value) { + if(value instanceof HttpSessionBindingListener){ + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this,name,value); + ((HttpSessionBindingListener)value).valueUnbound(event); + } + } + private void fireBound(String name, Object value) { + if(value instanceof HttpSessionBindingListener){ + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this,name,value); + ((HttpSessionBindingListener)value).valueBound(event); + } + } + + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/web/session/SessionVisitor.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/web/session/SessionVisitor.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/web/session/SessionVisitor.java (revision ebbe77ec5ad6506eca4b642562cf9898c0d7d587) @@ -0,0 +1,34 @@ +/* + *Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + *This program is free software; you can redistribute it and/or modify + *it under the terms of the GNU General Public License as published by + *the Free Software Foundation; either version 2 of the License, or + *(at your option) any later version. + * + *This program is distributed in the hope that it will be useful, + *but WITHOUT ANY WARRANTY; without even the implied warranty of + *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *GNU General Public License for more details. + * + *You should have received a copy of the GNU General Public License + *along with this program; if not, write to the Free Software + *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + *USA + * + *http://www.gnu.org/licenses/gpl.txt + */ +package org.lamsfoundation.lams.web.session; +/** + * Provide some internal method to access session private values. + * + * @author Steve.Ni + * $version$ + */ +interface SessionVisitor { + /** + * This method will reset session last access time. + * Currently(23/09/2005), this method is only used in SystemSessionFilter. + */ + public void accessed(); +} Index: lams_common/src/java/org/lamsfoundation/lams/web/session/SystemSessionFilter.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/web/session/SystemSessionFilter.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/web/session/SystemSessionFilter.java (revision ebbe77ec5ad6506eca4b642562cf9898c0d7d587) @@ -0,0 +1,145 @@ +/* + *Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + *This program is free software; you can redistribute it and/or modify + *it under the terms of the GNU General Public License as published by + *the Free Software Foundation; either version 2 of the License, or + *(at your option) any later version. + * + *This program is distributed in the hope that it will be useful, + *but WITHOUT ANY WARRANTY; without even the implied warranty of + *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *GNU General Public License for more details. + * + *You should have received a copy of the GNU General Public License + *along with this program; if not, write to the Free Software + *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + *USA + * + *http://www.gnu.org/licenses/gpl.txt + */ +package org.lamsfoundation.lams.web.session; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.sf.hibernate.id.UUIDHexGenerator; + +/** + * + * @author Steve.Ni + * + * $version$ + */ +public class SystemSessionFilter implements Filter { + + /** The name of the cookie we use to keep sakai session. */ + public static final String SYS_SESSION_COOKIE = "SYSSESSIONID"; + + public void init(FilterConfig config) throws ServletException { + } + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + + // Skip non-http request/response + if (!((req instanceof HttpServletRequest) && (res instanceof HttpServletResponse))){ + chain.doFilter(req, res); + return; + } + + Cookie cookie = findCookie((HttpServletRequest) req,SYS_SESSION_COOKIE); + String currentSessionId = null; + if(cookie != null){ + currentSessionId = cookie.getValue(); + Object obj = SessionManager.getSession(currentSessionId); + //if cookie exist, but session does not. This usually menas seesion expired. + //then delete the cookie first and set it null in order to create a new one + if(obj == null){ + removeCookie((HttpServletResponse) res,SYS_SESSION_COOKIE); + cookie = null; + } + } + //can not be in else! + if(cookie == null){ + //create new session and set it into cookie + currentSessionId = (String) new UUIDHexGenerator().generate(null,null); + SessionManager.createSession(currentSessionId); + cookie = createCookie((HttpServletResponse) res,SYS_SESSION_COOKIE,currentSessionId); + } + + SessionManager.setCurrentSessionId(currentSessionId); + //reset session last access time + SessionVisitor sessionVisitor = SessionManager.getSessionVisitor(); + sessionVisitor.accessed(); + + //do following part of chain + chain.doFilter(req,res); + + SessionManager.setCurrentSessionId(null); + + } + + public void destroy() { + //do nothing + } + + /** + * Find a cookie by given cookie name from request. + * + * @param req + * @param name The cookie name + * @return The cookie of this name in the request, or null if not found. + */ + private Cookie findCookie(HttpServletRequest req, String name) + { + Cookie[] cookies = req.getCookies(); + if (cookies != null) { + for (int i = 0; i < cookies.length; i++) { + if (cookies[i].getName().equals(name)) { + return cookies[i]; + } + } + } + + return null; + } + /** + * Remove cookie by given name from request + * @param res + * @param name + * @return the removed cookies + */ + private Cookie removeCookie(HttpServletResponse res, String name){ + Cookie cookie = new Cookie(name, ""); + cookie.setPath("/"); + cookie.setMaxAge(0); + res.addCookie(cookie); + + return cookie; + } + /** + * Create a new cookie for request. + * @param res + * @param name cookie name + * @param value cookie value + * @return the created cookie. + */ + private Cookie createCookie(HttpServletResponse res, String name, String value){ + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setMaxAge(-1); + res.addCookie(cookie); + + return cookie; + } +}