/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.servlet.sip.core.session; import java.io.IOException; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpSession; import javax.servlet.sip.ServletTimer; import javax.servlet.sip.SipApplicationSessionActivationListener; import javax.servlet.sip.SipApplicationSessionAttributeListener; import javax.servlet.sip.SipApplicationSessionBindingEvent; import javax.servlet.sip.SipApplicationSessionBindingListener; import javax.servlet.sip.SipApplicationSessionEvent; import javax.servlet.sip.SipApplicationSessionListener; import javax.servlet.sip.SipSession; import javax.servlet.sip.URI; import org.apache.catalina.security.SecurityUtil; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.address.RFC2396UrlDecoder; import org.mobicents.servlet.sip.annotation.ConcurrencyControlMode; import org.mobicents.servlet.sip.message.MobicentsSipApplicationSessionFacade; import org.mobicents.servlet.sip.startup.SipContext; import org.mobicents.servlet.sip.utils.JvmRouteUtil; /** * <p>Implementation of the SipApplicationSession interface. * An instance of this sip application session can only be retrieved through the Session Manager * (extended class from Tomcat's manager classes implementing the <code>Manager</code> interface) * to constrain the creation of sip application session and to make sure that all sessions created * can be retrieved only through the session manager<p/> * * <p> * As a SipApplicationSession represents a call (that can contain multiple call legs, in the B2BUA case by example), * the call id and the app name are used as a unique key for a given SipApplicationSession instance. * </p> * * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> */ public class SipApplicationSessionImpl implements MobicentsSipApplicationSession { private static transient Logger logger = Logger.getLogger(SipApplicationSessionImpl.class); /** * Timer task that will notify the listeners that the sip application session has expired * It is an improved timer task that is delayed every time setLastAccessedTime is called on it. * It is delayed of lastAccessedTime + lifetime * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> */ public class SipApplicationSessionTimerTask implements Runnable { public void run() { if(logger.isDebugEnabled()) { logger.debug("initial kick off of SipApplicationSessionTimerTask running for sip application session " + getId()); } long now = System.currentTimeMillis(); if(expirationTime > now) { // if the session has been accessed since we started it, put it to sleep long sleep = getDelay(); if(logger.isDebugEnabled()) { logger.debug("expirationTime is " + expirationTime + ", now is " + now + " sleeping for " + sleep / 1000 + " seconds"); } expirationTimerTask = new SipApplicationSessionTimerTask(); expirationTimerFuture = (ScheduledFuture<MobicentsSipApplicationSession>) sipContext.getThreadPoolExecutor().schedule(expirationTimerTask, sleep, TimeUnit.MILLISECONDS); } else { tryToExpire(); } } private void tryToExpire() { notifySipApplicationSessionListeners(SipApplicationSessionEventType.EXPIRATION); //It is possible that the application grant an extension to the lifetime of the session, thus the sip application //should not be treated as expired. if(getDelay() <= 0) { expired = true; if(isValid) { sipContext.enterSipApp(null, null, null, true, false); try { invalidate(); } finally { sipContext.exitSipApp(null, null); } } } } public long getDelay() { return expirationTime - System.currentTimeMillis(); } } protected Map<String, Object> sipApplicationSessionAttributeMap; protected transient ConcurrentHashMap<String,SipSessionKey> sipSessions; protected transient Set<String> httpSessions; protected SipApplicationSessionKey key; protected long lastAccessedTime; protected long creationTime; protected long expirationTime; protected boolean expired; protected transient SipApplicationSessionTimerTask expirationTimerTask; protected transient ScheduledFuture<MobicentsSipApplicationSession> expirationTimerFuture; protected transient ConcurrentHashMap<String, ServletTimer> servletTimers; protected boolean isValid; protected boolean invalidateWhenReady = true; protected boolean readyToInvalidate = false; // protected transient ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 1, // 90, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); /** * The first sip application for subsequent requests. */ protected transient SipContext sipContext; protected String currentRequestHandler; protected transient Semaphore semaphore; protected transient MobicentsSipApplicationSessionFacade facade = null; protected long sipApplicationSessionTimeout = -1; // Does it need to be synchronized? protected Map<String,Object> getAttributeMap() { if(sipApplicationSessionAttributeMap == null) { sipApplicationSessionAttributeMap = new ConcurrentHashMap<String,Object>() ; } return sipApplicationSessionAttributeMap; } protected SipApplicationSessionImpl(SipApplicationSessionKey key, SipContext sipContext) { sipSessions = new ConcurrentHashMap<String,SipSessionKey>(); if(sipContext != null && !ConcurrencyControlMode.None.equals(sipContext.getConcurrencyControlMode())) { semaphore = new Semaphore(1); } this.key = key; this.sipContext = sipContext; this.currentRequestHandler = sipContext.getMainServlet(); lastAccessedTime = creationTime = System.currentTimeMillis(); expired = false; isValid = true; // the sip context can be null if the AR returned an application that was not deployed if(sipContext != null) { //scheduling the timer for session expiration if(sipContext.getSipApplicationSessionTimeout() > 0) { sipApplicationSessionTimeout = sipContext.getSipApplicationSessionTimeout() * 60 * 1000; expirationTimerTask = new SipApplicationSessionTimerTask(); if(logger.isDebugEnabled()) { logger.debug("Scheduling sip application session "+ key +" to expire in " + (sipApplicationSessionTimeout / 1000 / 60) + " minutes"); } expirationTimerFuture = (ScheduledFuture<MobicentsSipApplicationSession>) sipContext.getThreadPoolExecutor().schedule(expirationTimerTask, sipApplicationSessionTimeout, TimeUnit.MILLISECONDS); } else { if(logger.isDebugEnabled()) { logger.debug("The sip application session "+ key +" will never expire "); } // If the session timeout value is 0 or less, then an application session timer // never starts for the SipApplicationSession object and the container does // not consider the object to ever have expired // expirationTime = -1; } notifySipApplicationSessionListeners(SipApplicationSessionEventType.CREATION); } } /** * Notifies the listeners that a lifecycle event occured on that sip application session * @param sipApplicationSessionEventType the type of event that happened */ public void notifySipApplicationSessionListeners(SipApplicationSessionEventType sipApplicationSessionEventType) { List<SipApplicationSessionListener> listeners = sipContext.getListeners().getSipApplicationSessionListeners(); if(listeners.size() > 0) { ClassLoader oldLoader = java.lang.Thread.currentThread().getContextClassLoader(); java.lang.Thread.currentThread().setContextClassLoader(sipContext.getLoader().getClassLoader()); SipApplicationSessionEvent event = new SipApplicationSessionEvent(this.getSession()); if(logger.isDebugEnabled()) { logger.debug("notifying sip application session listeners of context " + key.getApplicationName() + " of following event " + sipApplicationSessionEventType); } for (SipApplicationSessionListener sipApplicationSessionListener : listeners) { try { if(SipApplicationSessionEventType.CREATION.equals(sipApplicationSessionEventType)) { sipApplicationSessionListener.sessionCreated(event); } else if (SipApplicationSessionEventType.DELETION.equals(sipApplicationSessionEventType)) { sipApplicationSessionListener.sessionDestroyed(event); } else if (SipApplicationSessionEventType.EXPIRATION.equals(sipApplicationSessionEventType)) { sipApplicationSessionListener.sessionExpired(event); } else if (SipApplicationSessionEventType.READYTOINVALIDATE.equals(sipApplicationSessionEventType)) { sipApplicationSessionListener.sessionReadyToInvalidate(event); } } catch (Throwable t) { logger.error("SipApplicationSessionListener threw exception", t); } } java.lang.Thread.currentThread().setContextClassLoader(oldLoader); } } public void addSipSession(MobicentsSipSession mobicentsSipSession) { SipSessionKey key = this.sipSessions.putIfAbsent(mobicentsSipSession.getKey().toString(), mobicentsSipSession.getKey()); if(key == null) { if(logger.isDebugEnabled()) { logger.debug("Added sip session " + mobicentsSipSession.getKey() + " to sip app session " + key); } } readyToInvalidate = false; // sipSessionImpl.setSipApplicationSession(this); } public SipSessionKey removeSipSession (MobicentsSipSession mobicentsSipSession) { if(sipSessions != null) { SipSessionKey key = this.sipSessions.remove(mobicentsSipSession.getKey().toString()); if(logger.isDebugEnabled()) { logger.debug("Removed sip session " + mobicentsSipSession.getKey() + " from sip app session " + key); } return key; } return null; } public void addHttpSession(HttpSession httpSession) { if(httpSessions == null) { httpSessions = new CopyOnWriteArraySet<String>(); } this.httpSessions.add(JvmRouteUtil.removeJvmRoute(httpSession.getId())); readyToInvalidate = false; // TODO: We assume that there is only one HTTP session in the app session. In this case // we are safe to only assign jvmRoute once here. When we support multiple http sessions // we will need something more sophisticated. setJvmRoute(JvmRouteUtil.extractJvmRoute(httpSession.getId())); } public boolean removeHttpSession(HttpSession httpSession) { if(httpSessions != null) { return this.httpSessions.remove(JvmRouteUtil.removeJvmRoute(httpSession.getId())); } return false; } public HttpSession findHttpSession (String sessionId) { String id = JvmRouteUtil.removeJvmRoute(sessionId); if(httpSessions != null) { if(httpSessions.contains(id)) { try { return (HttpSession)sipContext.getSipManager().findSession(id); } catch (IOException e) { logger.error("An Unexpected exception happened while retrieving the http session " + id, e); } } } return null; } /** * {@inheritDoc} */ public void encodeURI(URI uri) { uri.setParameter(SIP_APPLICATION_KEY_PARAM_NAME, RFC2396UrlDecoder.encode(getId())); } /** * {@inheritDoc} * Adds a get parameter to the URL like this: * http://hostname/link -> http://hostname/link?org.mobicents.servlet.sip.ApplicationSessionKey=0 * http://hostname/link?something=1 -> http://hostname/link?something=1&org.mobicents.servlet.sip.ApplicationSessionKey=0 */ public URL encodeURL(URL url) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } String urlStr = url.toExternalForm(); try { URL ret; if (urlStr.contains("?")) { ret = new URL(url + "&" + SIP_APPLICATION_KEY_PARAM_NAME + "=" + getId().toString()); } else { ret = new URL(url + "?" + SIP_APPLICATION_KEY_PARAM_NAME + "=" + getId().toString()); } return ret; } catch (Exception e) { throw new IllegalArgumentException("Failed encoding URL : " + url, e); } } /** * {@inheritDoc} */ public Object getAttribute(String name) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } return this.getAttributeMap().get(name); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getAttributeNames() */ public Iterator<String> getAttributeNames() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } return this.getAttributeMap().keySet().iterator(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getCreationTime() */ public long getCreationTime() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } return creationTime; } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getExpirationTime() */ public long getExpirationTime() { if(!isValid) { throw new IllegalStateException("this sip application session is not valid anymore"); } if(expirationTimerTask == null) { return 0; } long expirationTime = expirationTimerTask.getDelay(); if(expirationTime <= 0) { return 0; } if(expired) { return Long.MIN_VALUE; } return expirationTime; } /** * {@inheritDoc} */ public String getId() { return key.toString(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getLastAccessedTime() */ public long getLastAccessedTime() { return lastAccessedTime; } private void setLastAccessedTime(long lastAccessTime) { this.lastAccessedTime = lastAccessTime; //JSR 289 Section 6.3 : starting the sip app session expiry timer anew if(sipApplicationSessionTimeout > 0) { expirationTime = lastAccessedTime + sipApplicationSessionTimeout; } } /** * Update the accessed time information for this session. This method * should be called by the context when a request comes in for a particular * session, even if the application does not reference it. */ //TODO : Section 6.3 : Whenever the last accessed time for a SipApplicationSession is updated, it is considered refreshed i.e., //the expiry timer for that SipApplicationSession starts anew. // this method should be called as soon as there is any modifications to the Sip Application Session public void access() { setLastAccessedTime(System.currentTimeMillis()); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getSessions() */ public Iterator<?> getSessions() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } return sipSessions.values().iterator(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getSessions(java.lang.String) */ public Iterator<?> getSessions(String protocol) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(protocol == null) { throw new NullPointerException("protocol given in argument is null"); } if("SIP".equalsIgnoreCase(protocol)) { return getSipSessions().iterator(); } else if("HTTP".equalsIgnoreCase(protocol)) { return getHttpSessions().iterator(); } else { throw new IllegalArgumentException(protocol + " sessions are not handled by this container"); } } protected Set<MobicentsSipSession> getSipSessions() { Set<MobicentsSipSession> retSipSessions = new HashSet<MobicentsSipSession>(); if(sipSessions != null) { for(SipSessionKey sipSessionKey : sipSessions.values()) { MobicentsSipSession sipSession = sipContext.getSipManager().getSipSession(sipSessionKey, false, null, this); if(sipSession != null) { retSipSessions.add(sipSession); } } } return retSipSessions; } protected Set<HttpSession> getHttpSessions() { Set<HttpSession> retHttpSessions = new HashSet<HttpSession>(); if(httpSessions != null) { for(String id : httpSessions) { try { HttpSession httpSession = (HttpSession)sipContext.getSipManager().findSession(id); if(httpSession != null) { retHttpSessions.add(httpSession); } } catch (IOException e) { logger.error("An Unexpected exception happened while retrieving the http session " + id, e); } } } return retHttpSessions; } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getSipSession(java.lang.String) */ public SipSession getSipSession(String id) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(logger.isDebugEnabled()) { logger.debug("Trying to find a session with the id " + id); dumpSipSessions(); } SipSessionKey sipSessionKey = sipSessions.get(id); if(sipSessionKey != null) { return sipContext.getSipManager().getSipSession(sipSessionKey, false, null, this); } else { return null; } } private void dumpSipSessions() { if(logger.isDebugEnabled()) { logger.debug("sessions contained in the following app session " + key); for (String sessionKey : sipSessions.keySet()) { logger.debug("session key " + sessionKey); } } } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getTimers() */ public Collection<ServletTimer> getTimers() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(servletTimers != null) { return servletTimers.values(); } return new HashMap<String, ServletTimer>().values(); } /** * Add a servlet timer to this application session * @param servletTimer the servlet timer to add */ public void addServletTimer(ServletTimer servletTimer){ if(servletTimers == null) { servletTimers = new ConcurrentHashMap<String, ServletTimer>(); } servletTimers.putIfAbsent(servletTimer.getId(), servletTimer); } /** * Remove a servlet timer from this application session * @param servletTimer the servlet timer to remove */ public void removeServletTimer(ServletTimer servletTimer){ if(servletTimers != null) { servletTimers.remove(servletTimer.getId()); } updateReadyToInvalidateState(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#invalidate() */ public void invalidate() { //JSR 289 Section 6.1.2.2.1 //When the IllegalStateException is thrown, the application is guaranteed //that the state of the SipApplicationSession object will be unchanged from its state prior to the invalidate() //method call. Even session objects that were eligible for invalidation will not have been invalidated. if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(logger.isInfoEnabled()) { logger.info("Invalidating the following sip application session " + key); } //doing the invalidation for(MobicentsSipSession session: getSipSessions()) { if(session.isValid()) { session.invalidate(); } } for(HttpSession session: getHttpSessions()) { if(session instanceof ConvergedSession) { ConvergedSession convergedSession = (ConvergedSession) session; if(convergedSession.isValid()) { convergedSession.invalidate(); } } else { try { session.invalidate(); } catch(IllegalStateException ignore) { //we ignore this exception, ugly but the only way to test for validity of the session since we cannot cast to catalina Session //See Issue 523 http://code.google.com/p/mobicents/issues/detail?id=523 } } } if(this.sipApplicationSessionAttributeMap != null) { for (String key : getAttributeMap().keySet()) { removeAttribute(key); } } notifySipApplicationSessionListeners(SipApplicationSessionEventType.DELETION); isValid = false; //cancelling the timers if(servletTimers != null) { for (Map.Entry<String, ServletTimer> servletTimerEntry : servletTimers.entrySet()) { ServletTimer timerEntry = servletTimerEntry.getValue(); if(timerEntry != null) { timerEntry.cancel(); } } } if(!expired && expirationTimerTask != null) { cancelExpirationTimer(); } /* * Compute how long this session has been alive, and update * session manager's related properties accordingly */ long timeNow = System.currentTimeMillis(); int timeAlive = (int) ((timeNow - creationTime)/1000); SipManager manager = sipContext.getSipManager(); synchronized (manager) { if (timeAlive > manager.getSipApplicationSessionMaxAliveTime()) { manager.setSipApplicationSessionMaxAliveTime(timeAlive); } int numExpired = manager.getExpiredSipApplicationSessions(); numExpired++; manager.setExpiredSipApplicationSessions(numExpired); int average = manager.getSipApplicationSessionAverageAliveTime(); average = ((average * (numExpired-1)) + timeAlive)/numExpired; manager.setSipApplicationSessionAverageAliveTime(average); } sipContext.getSipManager().removeSipApplicationSession(key); sipContext.getSipSessionsUtil().removeCorrespondingSipApplicationSession(key); expirationTimerTask = null; expirationTimerFuture = null; if(httpSessions != null) { httpSessions.clear(); } // key = null; if(servletTimers != null) { servletTimers.clear(); } if(sipApplicationSessionAttributeMap != null) { sipApplicationSessionAttributeMap.clear(); } if(sipSessions != null) { sipSessions.clear(); } // executorService.shutdown(); // executorService = null; httpSessions = null; sipSessions = null; sipApplicationSessionAttributeMap = null; servletTimers = null; if(logger.isInfoEnabled()) { logger.info("The following sip application session " + key + " has been invalidated"); } currentRequestHandler = null; if(semaphore != null) { semaphore.release(); semaphore = null; } facade = null; } // Counts the number of cancelled tasks private static int numCancelled = 0; private void cancelExpirationTimer() { //CANCEL needs to remove the shceduled timer see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6602600 //to improve perf // expirationTimerTask.cancel(); boolean removed = sipContext.getThreadPoolExecutor().remove(expirationTimerTask); if(logger.isDebugEnabled()) { logger.debug("expiration timer on sip application session " + key + " removed : " + removed); } boolean cancelled = expirationTimerFuture.cancel(true); if(logger.isDebugEnabled()) { logger.debug("expiration timer on sip application session " + key + " Cancelled : " + cancelled); } // Purge is expensive when called frequently, only call it every now and then. // We do not sync the numCancelled variable. We dont care about correctness of // the number, and we will still call purge rought once on every 25 cancels. numCancelled++; if(numCancelled % 25 == 0) { sipContext.getThreadPoolExecutor().purge(); } } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#isValid() */ public boolean isValid() { return isValid; } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#removeAttribute(java.lang.String) */ public void removeAttribute(String name) { if (!isValid) throw new IllegalStateException( "Can not bind object to session that has been invalidated!!"); if (name == null) // throw new NullPointerException("Name of attribute to bind cant be // null!!!"); return; SipApplicationSessionBindingEvent event = null; Object value = this.getAttributeMap().remove(name); // Call the valueUnbound() method if necessary if (value != null && value instanceof SipApplicationSessionBindingListener) { event = new SipApplicationSessionBindingEvent(this, name); ((SipApplicationSessionBindingListener) value).valueUnbound(event); } SipListenersHolder listeners = sipContext.getListeners(); List<SipApplicationSessionAttributeListener> listenersList = listeners.getSipApplicationSessionAttributeListeners(); if(listenersList.size() > 0) { if(event == null) { event = new SipApplicationSessionBindingEvent(this, name); } for (SipApplicationSessionAttributeListener listener : listenersList) { if(logger.isDebugEnabled()) { logger.debug("notifying SipApplicationSessionAttributeListener " + listener.getClass().getCanonicalName() + " of attribute removed on key "+ key); } try { listener.attributeRemoved(event); } catch (Throwable t) { logger.error("SipApplicationSessionAttributeListener threw exception", t); } } } } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#setAttribute(java.lang.String, java.lang.Object) */ public void setAttribute(String key, Object attribute) { if (!isValid) throw new IllegalStateException( "Can not bind object to session that has been invalidated!!"); if (key == null) throw new NullPointerException( "Name of attribute to bind cant be null!!!"); if (attribute == null) throw new NullPointerException( "Attribute that is to be bound cant be null!!!"); // Construct an event with the new value SipApplicationSessionBindingEvent event = null; // Call the valueBound() method if necessary if (attribute instanceof SipApplicationSessionBindingListener) { // Don't call any notification if replacing with the same value Object oldValue = getAttributeMap().get(key); if (attribute != oldValue) { event = new SipApplicationSessionBindingEvent(this, key); try { ((SipApplicationSessionBindingListener) attribute).valueBound(event); } catch (Throwable t){ logger.error("SipSessionBindingListener threw exception", t); } } } Object previousValue = this.getAttributeMap().put(key, attribute); if (previousValue != null && previousValue != attribute && previousValue instanceof SipApplicationSessionBindingListener) { try { ((SipApplicationSessionBindingListener) previousValue).valueUnbound (new SipApplicationSessionBindingEvent(this, key)); } catch (Throwable t) { logger.error("SipSessionBindingListener threw exception", t); } } SipListenersHolder listeners = sipContext.getListeners(); List<SipApplicationSessionAttributeListener> listenersList = listeners.getSipApplicationSessionAttributeListeners(); if(listenersList.size() > 0) { if(event == null) { event = new SipApplicationSessionBindingEvent(this, key); } if (previousValue == null) { for (SipApplicationSessionAttributeListener listener : listenersList) { if(logger.isDebugEnabled()) { logger.debug("notifying SipApplicationSessionAttributeListener " + listener.getClass().getCanonicalName() + " of attribute added on key "+ key); } try { listener.attributeAdded(event); } catch (Throwable t) { logger.error("SipApplicationSessionAttributeListener threw exception", t); } } } else { for (SipApplicationSessionAttributeListener listener : listenersList) { if(logger.isDebugEnabled()) { logger.debug("notifying SipApplicationSessionAttributeListener " + listener.getClass().getCanonicalName() + " of attribute replaced on key "+ key); } try { listener.attributeReplaced(event); } catch (Throwable t) { logger.error("SipApplicationSessionAttributeListener threw exception", t); } } } } } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#setExpires(int) */ public int setExpires(int deltaMinutes) { if(!isValid) { throw new IllegalStateException("Impossible to change the sip application " + "session timeout when it has been invalidated !"); } expired = false; if(logger.isDebugEnabled()) { logger.debug("Postponing the expiratin of the sip application session " + key +" to expire in " + deltaMinutes + " minutes."); } if(deltaMinutes <= 0) { if(logger.isDebugEnabled()) { logger.debug("The sip application session "+ key +" won't expire anymore "); } // If the session timeout value is 0 or less, then an application session timer // never starts for the SipApplicationSession object and the container // does not consider the object to ever have expired // this.expirationTime = -1; if(expirationTimerTask != null) { cancelExpirationTimer(); expirationTimerTask = null; } return Integer.MAX_VALUE; } else { long deltaMilliseconds = 0; // if(expirationTimerTask != null) { //extending the app session life time // expirationTime = expirationTimerFuture.getDelay(TimeUnit.MILLISECONDS) + deltaMinutes * 1000 * 60; deltaMilliseconds = deltaMinutes * 1000L * 60; // } else { //the app session was scheduled to never expire and now an expiration time is set // deltaMilliseconds = deltaMinutes * 1000L * 60; // } if(expirationTimerTask != null) { expirationTime = System.currentTimeMillis() + deltaMilliseconds; if(logger.isDebugEnabled()) { logger.debug("Re-Scheduling sip application session "+ key +" to expire in " + deltaMinutes + " minutes"); logger.debug("Re-Scheduling sip application session "+ key +" will expires in " + deltaMilliseconds + " milliseconds"); logger.debug("sip application session "+ key +" will expires at " + expirationTime); } cancelExpirationTimer(); expirationTimerTask = null; expirationTimerFuture = null; } expirationTimerTask = new SipApplicationSessionTimerTask(); expirationTimerFuture = (ScheduledFuture<MobicentsSipApplicationSession>) sipContext.getThreadPoolExecutor().schedule(expirationTimerTask, deltaMilliseconds, TimeUnit.MILLISECONDS); return deltaMinutes; } } public boolean hasTimerListener() { return this.sipContext.getListeners().getTimerListener() != null; } public SipContext getSipContext() { return sipContext; } void expirationTimerFired() { notifySipApplicationSessionListeners(SipApplicationSessionEventType.EXPIRATION); } /** * @return the key */ public SipApplicationSessionKey getKey() { return key; } /** * @param key the key to set */ public void setKey(SipApplicationSessionKey key) { this.key = key; } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getApplicationName() */ public String getApplicationName() { return key.getApplicationName(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipApplicationSession#getTimer(java.lang.String) */ public ServletTimer getTimer(String id) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(servletTimers != null) { return servletTimers.get(id); } return null; } /** * Perform the internal processing required to passivate * this session. */ public void passivate() { // Notify ActivationListeners SipApplicationSessionEvent event = null; if(this.sipApplicationSessionAttributeMap != null) { Set<String> keySet = getAttributeMap().keySet(); for (String key : keySet) { Object attribute = getAttributeMap().get(key); if (attribute instanceof SipApplicationSessionActivationListener) { if (event == null) event = new SipApplicationSessionEvent(this); try { ((SipApplicationSessionActivationListener)attribute) .sessionWillPassivate(event); } catch (Throwable t) { logger.error("SipApplicationSessionActivationListener threw exception", t); } } } } } /** * Perform internal processing required to activate this * session. */ public void activate() { // Notify ActivationListeners SipApplicationSessionEvent event = null; if(sipApplicationSessionAttributeMap != null) { Set<String> keySet = sipApplicationSessionAttributeMap.keySet(); for (String key : keySet) { Object attribute = sipApplicationSessionAttributeMap.get(key); if (attribute instanceof SipApplicationSessionActivationListener) { if (event == null) event = new SipApplicationSessionEvent(this); try { ((SipApplicationSessionActivationListener)attribute) .sessionDidActivate(event); } catch (Throwable t) { logger.error("SipApplicationSessionActivationListener threw exception", t); } } } } } public boolean getInvalidateWhenReady() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } return invalidateWhenReady; } /** * {@inheritDoc} */ public Object getSession(String id, Protocol protocol) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } if(id == null) { throw new NullPointerException("id is null"); } if(protocol == null) { throw new NullPointerException("protocol is null"); } switch (protocol) { case SIP : return getSipSession(id); case HTTP : return findHttpSession(id); } return null; } public boolean isReadyToInvalidate() { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } updateReadyToInvalidateState(); return readyToInvalidate; } public void setInvalidateWhenReady(boolean invalidateWhenReady) { if(!isValid) { throw new IllegalStateException("SipApplicationSession already invalidated !"); } this.invalidateWhenReady = invalidateWhenReady; } public void onSipSessionReadyToInvalidate(MobicentsSipSession mobicentsSipSession) { removeSipSession(mobicentsSipSession); updateReadyToInvalidateState(); } private void updateReadyToInvalidateState() { if(isValid && !readyToInvalidate) { for(MobicentsSipSession sipSession : getSipSessions()) { if(sipSession.isValid() && !sipSession.isReadyToInvalidate()) { if(logger.isDebugEnabled()) { logger.debug("Sip Session not ready to be invalidated : " + sipSession.getKey()); } return; } } // Fix for Issue 813 : SipApplicationSession invalidates too soon when HttpSession are present // (http://code.google.com/p/mobicents/issues/detail?id=813) for(HttpSession httpSession: getHttpSessions()) { if(httpSession instanceof ConvergedSession) { ConvergedSession convergedSession = (ConvergedSession) httpSession; if(convergedSession.isValid()) { if(logger.isDebugEnabled()) { logger.debug("Http Session not ready to be invalidated : " + convergedSession.getId()); } return; } } } if(servletTimers == null || this.servletTimers.size() <= 0) { if(logger.isDebugEnabled()) { logger.debug("All sip sessions and http session are ready to be invalidated, no timers alive, can invalidate this application session " + key); } this.readyToInvalidate = true; } else { if(logger.isDebugEnabled()) { logger.debug(servletTimers.size() + " Timers still alive, cannot invalidate this application session " + key); } } } else { if(logger.isDebugEnabled()) { logger.debug("Sip application session already invalidated "+ key); } this.readyToInvalidate = true; } } public void tryToInvalidate() { if(isValid && invalidateWhenReady) { notifySipApplicationSessionListeners(SipApplicationSessionEventType.READYTOINVALIDATE); if(readyToInvalidate) { boolean allSipSessionsInvalidated = true; for(MobicentsSipSession sipSession : getSipSessions()) { if(sipSession.isValid()) { allSipSessionsInvalidated = false; break; } } boolean allHttpSessionsInvalidated = true; for(HttpSession httpSession : getHttpSessions()) { ConvergedSession convergedSession = (ConvergedSession) httpSession; if(convergedSession.isValid()) { allHttpSessionsInvalidated = false; break; } } if(allSipSessionsInvalidated && allHttpSessionsInvalidated) { this.invalidate(); } } } } /** * @return the expired */ public boolean isExpired() { return expired; } /** * @param currentServletHandler the currentServletHandler to set */ public void setCurrentRequestHandler(String currentRequestHandler) { this.currentRequestHandler = currentRequestHandler; } /** * @return the currentServletHandler */ public String getCurrentRequestHandler() { return currentRequestHandler; } // public ThreadPoolExecutor getExecutorService() { // return executorService; // } /** * @return the semaphore */ public Semaphore getSemaphore() { return semaphore; } public MobicentsSipApplicationSessionFacade getSession() { if (facade == null){ if (SecurityUtil.isPackageProtectionEnabled()){ final MobicentsSipApplicationSession fsession = this; facade = (MobicentsSipApplicationSessionFacade)AccessController.doPrivileged(new PrivilegedAction(){ public Object run(){ return new MobicentsSipApplicationSessionFacade(fsession); } }); } else { facade = new MobicentsSipApplicationSessionFacade(this); } } return (facade); } protected String jvmRoute; public String getJvmRoute() { return this.jvmRoute; } public void setJvmRoute(String jvmRoute) { // Assign the new jvmRoute this.jvmRoute = jvmRoute; } @Override public boolean equals(Object obj) { if(obj instanceof MobicentsSipApplicationSession) { return ((MobicentsSipApplicationSession)obj).getKey().equals(getKey()); } return false; } @Override public int hashCode() { return getKey().hashCode(); } @Override public String toString() { return getId(); } }