gt:     if (! _webApp.isInitializing())
context: /*
   * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
   *
   * This file is part of Resin(R) Open Source
   *
   * Each copy or derived work must preserve the copyright notice and this
   * notice unmodified.
   *
   * Resin Open Source 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.
   *
   * Resin Open Source 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, or any warranty
   * of NON-INFRINGEMENT.  See the GNU General Public License for more
   * details.
   *
   * You should have received a copy of the GNU General Public License
   * along with Resin Open Source; if not, write to the
   *
   *   Free Software Foundation, Inc.
   *   59 Temple Place, Suite 330
   *   Boston, MA 02111-1307  USA
   *
   * @author Scott Ferguson
   */
  
  package com.caucho.server.session;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.nio.charset.Charset;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.concurrent.atomic.AtomicLong;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import javax.servlet.SessionCookieConfig;
  import javax.servlet.SessionTrackingMode;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpSessionActivationListener;
  import javax.servlet.http.HttpSessionAttributeListener;
  import javax.servlet.http.HttpSessionEvent;
  import javax.servlet.http.HttpSessionListener;
  
  import com.caucho.cloud.network.ClusterServer;
  import com.caucho.cloud.topology.CloudServer;
  import com.caucho.config.ConfigException;
  import com.caucho.config.Configurable;
  import com.caucho.config.types.Period;
  import com.caucho.distcache.AbstractCache;
  import com.caucho.distcache.ByteStreamCache;
  import com.caucho.distcache.ClusterCache;
  import com.caucho.distcache.ExtCacheEntry;
  import com.caucho.distcache.ResinCacheBuilder.Scope;
  import com.caucho.env.meter.AverageSensor;
  import com.caucho.env.meter.MeterService;
  import com.caucho.hessian.io.HessianDebugInputStream;
  import com.caucho.hessian.io.SerializerFactory;
  import com.caucho.json.JsonOutput;
  import com.caucho.management.server.SessionManagerMXBean;
  import com.caucho.security.Authenticator;
  import com.caucho.server.cluster.ServletService;
  import com.caucho.server.distcache.CacheBacking;
  import com.caucho.server.distcache.CacheImpl;
  import com.caucho.server.distcache.PersistentStoreConfig;
  import com.caucho.server.webapp.WebApp;
  import com.caucho.util.Alarm;
  import com.caucho.util.AlarmListener;
  import com.caucho.util.Crc64;
  import com.caucho.util.CurrentTime;
  import com.caucho.util.L10N;
  import com.caucho.util.LruCache;
  import com.caucho.util.RandomUtil;
  import com.caucho.util.WeakAlarm;
  import com.caucho.vfs.TempOutputStream;
  
  // import com.caucho.server.http.ServletServer;
  // import com.caucho.server.http.VirtualHost;
  
  /**
   * Manages sessions in a web-webApp.
   */
  public final class SessionManager implements SessionCookieConfig, AlarmListener
  {
    private static final L10N L = new L10N(SessionManager.class);
    private static final Logger log
      = Logger.getLogger(SessionManager.class.getName());
  
    private static final int FALSE = 0;
    private static final int COOKIE = 1;
    private static final int TRUE = 2;
  
    private static final int UNSET = 0;
    private static final int SET_TRUE = 1;
    private static final int SET_FALSE = 2;
  
    private static final int SAVE_BEFORE_HEADERS = 0x1;
    private static final int SAVE_BEFORE_FLUSH = 0x2;
    private static final int SAVE_AFTER_REQUEST = 0x4;
    private static final int SAVE_ON_SHUTDOWN = 0x8;
    
    private static final int []DECODE;
  
    private final WebApp _webApp;
    private final SessionManagerAdmin _admin;
  
    private final ServletService _servletContainer;
    private final ClusterServer _selfServer;
    private final int _selfIndex;
  
    private CacheImpl _sessionStore;
  
    // active sessions
    private LruCache<String,SessionImpl> _sessions;
    // iterator to purge sessions (to reduce gc)
    private Iterator<SessionImpl> _sessionIter;
    // array list for session timeout
    private ArrayList<SessionImpl> _sessionList = new ArrayList<SessionImpl>();
    // generate cookies
    private boolean _enableSessionCookies = true;
    // allow session rewriting
    private boolean _enableSessionUrls = true;
  
    private boolean _isAppendServerIndex = false;
    private boolean _isTwoDigitSessionIndex = false;
  
    // invalidate the session after the listeners have been called
    private boolean _isInvalidateAfterListener;
  
    // maximum number of sessions
    private int _sessionMax = 8192;
    // how long a session will be inactive before it times out
    private long _sessionTimeout = 30 * 60 * 1000;
  
    private String _cookieName = "JSESSIONID";
    private String _sslCookieName;
  
    // Rewriting strings.
    private String _sessionSuffix = ";jsessionid=";
    private String _sessionPrefix;
  
    // default cookie version
    private int _cookieVersion;
    private String _cookieDomain;
    private String _cookieDomainRegexp;
    private boolean _isCookieUseContextPath;
    private String _cookiePath;
    private long _cookieMaxAge;
    private int _isCookieHttpOnly;
    private String _cookieComment;
    private String _cookiePort;
    private int _reuseSessionId = COOKIE;
    private int _cookieLength = 21;
    
    private AtomicLong _sessionIdSequence = new AtomicLong();
    
    private HashSet<SessionTrackingMode> _trackingModes;
    //Servlet 3.0 plain | ssl session tracking cookies become secure when set to true
    private boolean _isSecure;
  
    // persistence configuration
  
    private int _sessionSaveMode = SAVE_AFTER_REQUEST;
  
    private boolean _isPersistenceEnabled = false;
    private boolean _isSaveTriplicate = true;
    private boolean _isSaveBackup = true;
    private boolean _isDestroyOnLru = true;
  
    // If true, serialization errors should not be logged
    // XXX: changed for JSF
    private boolean _ignoreSerializationErrors = true;
    private boolean _isHessianSerialization = false;
    private SerializerFactory _hessianFactory;
    private boolean _isSerializeCollectionType = true;
  
    // List of the HttpSessionListeners from the configuration file
    private ArrayList<HttpSessionListener> _listeners;
  
    // List of the HttpSessionListeners from the configuration file
    private ArrayList<HttpSessionActivationListener> _activationListeners;
  
    // List of the HttpSessionAttributeListeners from the configuration file
    private ArrayList<HttpSessionAttributeListener> _attributeListeners;
  
    //
    // Compatibility fields
    //
  
    // private Store _sessionStore;
    private int _alwaysLoadSession;
    private int _alwaysSaveSession;
  
    private boolean _isClosed;
  
    private String _distributionId;
  
    private Alarm _alarm;
  
    // statistics
    private volatile long _sessionCreateCount;
    private volatile long _sessionTimeoutCount;
    private volatile long _sessionInvalidateCount;
  
    private final AverageSensor _sessionSaveSample;
    private final Charset UTF_8 = Charset.forName("UTF-8");
  
    /**
     * Creates and initializes a new session manager
     *
     * @param webApp the web-webApp webApp
     */
    public SessionManager(WebApp webApp)
      throws Exception
    {
      _webApp = webApp;
  
      _servletContainer = webApp.getServer();
  
      if (_servletContainer == null) {
        throw new IllegalStateException(L.l("Server is not active in this context {0}",
                                            Thread.currentThread().getContextClassLoader()));
      }
      _selfServer = _servletContainer.getSelfServer();
      _selfIndex = _selfServer.getIndex();
  
      // copy defaults from store for backward compat
      PersistentStoreConfig cfg = _servletContainer.getPersistentStore();
      if (cfg != null) {
        setAlwaysSaveSession(cfg.isAlwaysSave());
  
        _isSaveBackup = cfg.isSaveBackup();
        _isSaveTriplicate = cfg.isSaveTriplicate();
      }
  
      _sessionSuffix = _servletContainer.getSessionURLPrefix();
      _sessionPrefix = _servletContainer.getAlternateSessionURLPrefix();
  
      _cookieName = _servletContainer.getSessionCookie();
      _sslCookieName = _servletContainer.getSSLSessionCookie();
        
      long initSequence = CurrentTime.getCurrentTime();
      
      if (CurrentTime.isTest()) {
        initSequence -= initSequence % 1000;
      }
      
      _sessionIdSequence.set(initSequence);
      /*
      if (_sslCookieName != null && ! _sslCookieName.equals(_cookieName))
        _isSecure = true;
        */
  
      String hostName = webApp.getHostName();
      String contextPath = webApp.getContextPath();
  
      if (hostName == null || hostName.equals(""))
        hostName = "default";
  
      String name = hostName + contextPath;
  
      if (_distributionId == null)
        _distributionId = name;
  
      _alarm = new WeakAlarm(this);
      _sessionSaveSample
        = MeterService.createAverageMeter("Resin|Http|Session Save", "Size");
  
      _admin = new SessionManagerAdmin(this);
    }
  
    /**
     * Returns the admin.
     */
    public SessionManagerMXBean getAdmin()
    {
      return _admin;
    }
  
    /**
     * Returns the session prefix, ie.. ";jsessionid=".
     */
    public String getSessionPrefix()
    {
      return _sessionSuffix;
    }
  
    /**
     * Returns the alternate session prefix, before the URL for wap.
     */
    public String getAlternateSessionPrefix()
    {
      return _sessionPrefix;
    }
  
    /**
     * Returns the cookie version.
     */
    public int getCookieVersion()
    {
      return _cookieVersion;
    }
  
    /**
     * Sets the cookie version.
     */
    public void setCookieVersion(int cookieVersion)
    {
      _cookieVersion = cookieVersion;
    }
  
    /**
     * Sets the cookie ports.
     */
    public void setCookiePort(String port)
    {
      _cookiePort = port;
    }
  
    /**
     * Sets the cookie ports.
     */
    public void setCookieUseContextPath(boolean isCookieUseContextPath)
    {
      _isCookieUseContextPath = isCookieUseContextPath;
    }
  
    /**
     * Gets the cookie ports.
     */
    public String getCookiePort()
    {
      return _cookiePort;
    }
  
    /**
     * Returns the debug log
     */
    public Logger getDebug()
    {
      return log;
    }
  
    /**
     * Returns the SessionManager's webApp
     */
    WebApp getWebApp()
    {
      return _webApp;
    }
    
    ClassLoader getClassLoader()
    {
      return getWebApp().getClassLoader();
    }
  
    /**
     * Returns the SessionManager's authenticator
     */
    Authenticator getAuthenticator()
    {
      return _webApp.getAuthenticator();
    }
  
    /**
     * Returns the session cache
     */
    ByteStreamCache getCache()
    {
      if (_isPersistenceEnabled) {
        return _sessionStore;
      }
      else {
        return null;
      }
    }
  
    /**
     * True if sessions should always be loadd.
     */
    boolean isAlwaysLoadSession()
    {
      return _alwaysLoadSession == SET_TRUE;
    }
  
    /**
     * True if sessions should always be loadd.
     */
    public void setAlwaysLoadSession(boolean load)
    {
      _alwaysLoadSession = load ? SET_TRUE : SET_FALSE;
    }
  
    /**
     * True if sessions should always be saved.
     */
    boolean getAlwaysSaveSession()
    {
      return _alwaysSaveSession == SET_TRUE;
    }
  
    /**
     * True if sessions should always be saved.
     */
    public void setAlwaysSaveSession(boolean save)
    {
      _alwaysSaveSession = save ? SET_TRUE : SET_FALSE;
    }
  
    /**
     * True if sessions should be saved on shutdown.
     */
    public boolean isSaveOnShutdown()
    {
      return (_sessionSaveMode & SAVE_ON_SHUTDOWN) != 0;
    }
  
    /**
     * True if sessions should only be saved on shutdown.
     */
    public boolean isSaveOnlyOnShutdown()
    {
      return (_sessionSaveMode & SAVE_ON_SHUTDOWN) == SAVE_ON_SHUTDOWN;
    }
  
    /**
     * True if sessions should be saved before the HTTP headers.
     */
    public boolean isSaveBeforeHeaders()
    {
      return (_sessionSaveMode & SAVE_BEFORE_HEADERS) != 0;
    }
  
    /**
     * True if sessions should be saved before each flush.
     */
    public boolean isSaveBeforeFlush()
    {
      return (_sessionSaveMode & SAVE_BEFORE_FLUSH) != 0;
    }
  
    /**
     * True if sessions should be saved after the request.
     */
    public boolean isSaveAfterRequest()
    {
      return (_sessionSaveMode & SAVE_AFTER_REQUEST) != 0;
    }
  
    /**
     * Determines how many digits are used to encode the server
     */
    boolean isTwoDigitSessionIndex()
    {
     return _isTwoDigitSessionIndex;
    }
  
    /**
     * Sets the save-mode: before-flush, before-headers, after-request,
     * on-shutdown
     */
    public void setSaveMode(String mode)
      throws ConfigException
    {
      /* XXX: probably don't want to implement this.
      if ("before-flush".equals(mode)) {
        _sessionSaveMode = (SAVE_BEFORE_FLUSH|
                            SAVE_BEFORE_HEADERS|
                            SAVE_AFTER_REQUEST|
                            SAVE_ON_SHUTDOWN);
      }
      else
      */
  
      if ("before-headers".equals(mode)) {
        _sessionSaveMode = (SAVE_BEFORE_HEADERS);
      }
      else if ("after-request".equals(mode)) {
        _sessionSaveMode = (SAVE_AFTER_REQUEST);
      }
      else if ("on-shutdown".equals(mode)) {
        _sessionSaveMode = (SAVE_ON_SHUTDOWN);
      }
      else
        throw new ConfigException(L.l("'{0}' is an unknown session save-mode.  Values are: before-headers, after-request, and on-shutdown.",
                                      mode));
  
    }
  
    /**
     * Returns the string value of the save-mode.
     */
    public String getSaveMode()
    {
      if (isSaveBeforeFlush())
        return "before-flush";
      else if (isSaveBeforeHeaders())
        return "before-headers";
      else if (isSaveAfterRequest())
        return "after-request";
      else if (isSaveOnShutdown())
        return "on-shutdown";
      else
        return "unknown";
    }
  
    /**
     * True if sessions should only be saved on shutdown.
     */
    public void setSaveOnlyOnShutdown(boolean save)
    {
      log.warning("<save-only-on-shutdown> is deprecated.  Use <save-mode>on-shutdown</save-mode> instead");
  
      if (save)
        _sessionSaveMode = SAVE_ON_SHUTDOWN;
    }
  
    /**
     * True if sessions should only be saved on shutdown.
     */
    public void setSaveOnShutdown(boolean save)
    {
      log.warning("<save-on-shutdown> is deprecated.  Use <save-only-on-shutdown> instead");
  
      setSaveOnlyOnShutdown(save);
    }
  
    /**
     * Sets the serialization type.
     */
    public void setSerializationType(String type)
    {
      if ("hessian".equals(type))
        _isHessianSerialization = true;
      else if ("java".equals(type))
        _isHessianSerialization = false;
      else
        throw new ConfigException(L.l("'{0}' is an unknown valud for serialization-type.  The valid types are 'hessian' and 'java'.",
                                      type));
    }
    
    public void setSerializeCollectionType(boolean isEnable)
    {
      _isSerializeCollectionType = isEnable;
    }
  
    /**
     * Returns true for Hessian serialization.
     */
    public boolean isHessianSerialization()
    {
      return _isHessianSerialization;
    }
  
    /**
     * True if the session should be invalidated after the listener.
     */
    public void setInvalidateAfterListener(boolean inv)
    {
      _isInvalidateAfterListener = inv;
    }
  
    /**
     * True if the session should be invalidated after the listener.
     */
    public boolean isInvalidateAfterListener()
    {
      return _isInvalidateAfterListener;
    }
  
    /**
     * Returns the current number of active sessions.
     */
    public int getActiveSessionCount()
    {
      if (_sessions == null)
        return -1;
      else
        return _sessions.size();
    }
  
    /**
     * Returns the active sessions.
     */
    public int getSessionActiveCount()
    {
      return getActiveSessionCount();
    }
  
    /**
     * Returns the created sessions.
     */
    public long getSessionCreateCount()
    {
      return _sessionCreateCount;
    }
  
    /**
     * Returns the timeout sessions.
     */
    public long getSessionTimeoutCount()
    {
      return _sessionTimeoutCount;
    }
  
    /**
     * Returns the invalidate sessions.
     */
    public long getSessionInvalidateCount()
    {
      return _sessionInvalidateCount;
    }
  
    /**
     * Adds a new HttpSessionListener.
     */
    public void addListener(HttpSessionListener listener)
    {
      if (_listeners == null) {
        _listeners = new ArrayList<HttpSessionListener>();
      }
  
      _listeners.add(listener);
    }
  
    /**
     * Adds a new HttpSessionListener.
     */
    ArrayList<HttpSessionListener> getListeners()
    {
      return _listeners;
    }
  
    /**
     * Adds a new HttpSessionActivationListener.
     */
    public void addActivationListener(HttpSessionActivationListener listener)
    {
      if (_activationListeners == null)
        _activationListeners = new ArrayList<HttpSessionActivationListener>();
  
      _activationListeners.add(listener);
    }
  
    /**
     * Returns the activation listeners.
     */
    ArrayList<HttpSessionActivationListener> getActivationListeners()
    {
      return _activationListeners;
    }
  
    /**
     * Adds a new HttpSessionAttributeListener.
     */
    public void addAttributeListener(HttpSessionAttributeListener listener)
    {
      if (_attributeListeners == null)
        _attributeListeners = new ArrayList<HttpSessionAttributeListener>();
  
      _attributeListeners.add(listener);
    }
  
    /**
     * Gets the HttpSessionAttributeListener.
     */
    ArrayList<HttpSessionAttributeListener> getAttributeListeners()
    {
      return _attributeListeners;
    }
  
    /**
     * True if serialization errors should just fail silently.
     */
    boolean getIgnoreSerializationErrors()
    {
      return _ignoreSerializationErrors;
    }
  
    /**
     * True if serialization errors should just fail silently.
     */
    public void setIgnoreSerializationErrors(boolean ignore)
    {
      _ignoreSerializationErrors = ignore;
    }
  
    /**
     * True if the server should reuse the current session id if the
     * session doesn't exist.
     */
    public int getReuseSessionId()
    {
      return _reuseSessionId;
    }
  
    /**
     * True if the server should reuse the current session id if the
     * session doesn't exist.
     */
    public boolean reuseSessionId(boolean fromCookie)
    {
      int reuseSessionId = _reuseSessionId;
  
      return reuseSessionId == TRUE || fromCookie && reuseSessionId == COOKIE;
    }
  
    /**
     * True if the server should reuse the current session id if the
     * session doesn't exist.
     */
    public void setReuseSessionId(String reuse)
      throws ConfigException
    {
      if (reuse == null)
        _reuseSessionId = COOKIE;
      else if (reuse.equalsIgnoreCase("true")
               || reuse.equalsIgnoreCase("yes")
               || reuse.equalsIgnoreCase("cookie"))
        _reuseSessionId = COOKIE;
      else if (reuse.equalsIgnoreCase("false") || reuse.equalsIgnoreCase("no"))
        _reuseSessionId = FALSE;
      else if (reuse.equalsIgnoreCase("all"))
        _reuseSessionId = TRUE;
      else
        throw new ConfigException(L.l("'{0}' is an invalid value for reuse-session-id.  'true' or 'false' are the allowed values.",
                                      reuse));
    }
  
    /**
     * Returns true if the sessions are closed.
     */
    public boolean isClosed()
    {
      return _isClosed;
    }
  
    /**
     * Sets the cluster store.
     */
    public void setUsePersistentStore(boolean enable)
      throws Exception
    {
      _isPersistenceEnabled = enable;
    }
    
    public boolean isUsePersistentStore()
    {
      return isPersistenceEnabled();
    }
  
    public boolean isPersistenceEnabled()
    {
      return _isPersistenceEnabled;
    }
    
    public void setDestroyOnLru(boolean isDestroy)
    {
      _isDestroyOnLru = isDestroy;
    }
    
    public boolean isDestroyOnLru()
    {
      return _isDestroyOnLru || ! isPersistenceEnabled();
    }
  
    public String getDistributionId()
    {
      return _distributionId;
    }
  
    public void setDistributionId(String distributionId)
    {
      _distributionId = distributionId;
    }
  
    /**
     * Returns the default session timeout in milliseconds.
     */
    public long getSessionTimeout()
    {
      return _sessionTimeout;
    }
  
    /**
     * Set the default session timeout in minutes
     */
    public void setSessionTimeout(long timeout)
    {
      if (timeout <= 0 || Integer.MAX_VALUE / 2 < timeout)
        _sessionTimeout = Long.MAX_VALUE / 2;
      else
        _sessionTimeout = 60000L * timeout;
    }
  
    /**
     * Returns the idle time.
     */
    public long getMaxIdleTime()
    {
      return _sessionTimeout;
    }
  
    /**
     * Returns the maximum number of sessions.
     */
    public int getSessionMax()
    {
      return _sessionMax;
    }
  
    /**
     * Returns the maximum number of sessions.
     */
    public void setSessionMax(int max)
    {
      if (max < 1)
        throw new ConfigException(L.l("session-max '{0}' is too small.  session-max must be a positive number", max));
  
      _sessionMax = max;
    }
  
    /**
     * Returns true if sessions use the cookie header.
     */
    public boolean enableSessionCookies()
    {
      return _enableSessionCookies;
    }
  
    /**
     * Returns true if sessions use the cookie header.
     */
    public void setEnableCookies(boolean enableCookies)
    {
      _enableSessionCookies = enableCookies;
    }
  
    /**
     * Returns true if sessions can use the session rewriting.
     */
    public boolean enableSessionUrls()
    {
      return _enableSessionUrls;
    }
  
    /**
     * Returns true if sessions can use the session rewriting.
     */
    public void setEnableUrlRewriting(boolean enableUrls)
    {
      _enableSessionUrls = enableUrls;
    }
  
    //SessionCookieConfig implementation (Servlet 3.0)
    @Override
    public void setName(String name)
    {
      if (! _webApp.isInitializing())
        throw new IllegalStateException();
  
      setCookieName(name);
    }
  
    @Override
    public String getName()
    {
      return getCookieName();
    }
  
    @Override
    public void setDomain(String domain)
    {
      if (! _webApp.isInitializing())
        throw new IllegalStateException();
  
      setCookieDomain(domain);
    }
  
    @Override
    public String getDomain()
    {
      return getCookieDomain();
    }
  
    @Override
    public void setPath(String path)
    {
      if (! _webApp.isInitializing())
        throw new IllegalStateException();
  
      _cookiePath = path;
    }
  
    @Override
    public String getPath()
    {
      return _cookiePath;
    }
  
    @Override
    public void setComment(String comment)
    {
      if (! _webApp.isInitializing())
        throw new IllegalStateException();
  
      _cookieComment = comment;
    }
  
    @Override
    public String getComment()
    {
      return _cookieComment;
    }
  
    @Override
    public void setHttpOnly(boolean httpOnly)
    {
      if (! _webApp.isInitializing())
        throw new IllegalStateException();
  
      setCookieHttpOnly(httpOnly);
    }
  
    @Override
    public boolean isHttpOnly()
    {
      return isCookieHttpOnly();
    }
  
    @Override
    public void setSecure(boolean secure)
    {
  