Package org.huihoo.willow.session

Source Code of org.huihoo.willow.session.SessionManager

//----------------------------BEGIN LICENSE----------------------------
/*
* Willow : the Open Source WorkFlow Project
* Distributable under GNU LGPL license by gun.org
*
* Copyright (C) 2004-2010 huihoo.org
* Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com>
*
* ====================================================================
* Project Homepage : http://www.huihoo.org/willow
* Source Forge     : http://sourceforge.net/projects/huihoo
* Mailing list     : willow@lists.sourceforge.net
*/
//----------------------------END  LICENSE-----------------------------
package org.huihoo.willow.session;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Context;
import org.huihoo.willow.Engine;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.StringManager;
import org.huihoo.workflow.runtime.WorkflowSession;

/**
* @author reic
*
* To change the template for this generated type comment go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
public class SessionManager implements Lifecycle, PropertyChangeListener
{
  protected Log log = LogFactory.getLog(SessionManager.class);

  protected DataInputStream randomIS = null;
  protected String devRandomSource = "/dev/urandom";

  /**
   * The number of random bytes to include when generating a
   * session identifier.
   */
  protected static final int SESSION_ID_BYTES = 16;

  /**
   * The Engine with which this Manager is associated.
   */
  protected Engine engine;

  /**
   * The debugging detail level for this component.
   */
  protected int debug = 0;

  /**
   * A String initialization parameter used to increase the entropy of
   * the initialization of our random number generator.
   */
  protected String entropy = null;

  /**
   * The descriptive information string for this implementation.
   */
  private static final String info = "SessionManager/1.0";

  /**
   * The default maximum inactive interval (in seconds) for WillowSessions created by
   * this Manager.
   */
  protected int maxInactiveInterval = 24*60*60; //one day

  /**
   * The descriptive name of this SessionManager(for logging).
   */
  protected static String name = "SessionManager";

  /**
   * A random number generator to use when generating session identifiers.
   */
  protected Random random = null;

  /**
   * The Java class name of the random number generator class to be used
   * when generating session identifiers.
   */
  protected String randomClass = "java.security.SecureRandom";

  /**
   * The set of currently active WillowSessions for this Manager, keyed by
   * session identifier.
   */
  protected HashMap sessions = new HashMap();

  // Number of sessions created by this manager
  protected int sessionCounter = 0;

  protected int maxActive = 0;

  // number of duplicated session ids - anything >0 means we have problems
  protected int duplicates = 0;

  protected boolean initialized = false;

  /**
   * The string manager for this package.
   */
  protected static StringManager sm = StringManager.getManager(Constants.PACKAGE);

  /**
   * The property change support for this component.
   */
  protected PropertyChangeSupport support = new PropertyChangeSupport(this);

  // ------------------------------------------------------------- Properties
  /**
   * Return the Engine with which this Manager is associated.
   */
  public Engine getEngine()
  {

    return (this.engine);

  }

  /**
   * Return the debugging detail level for this component.
   */
  public int getDebug()
  {

    return (this.debug);

  }

  /**
   * Set the debugging detail level for this component.
   *
   * @param debug The new debugging detail level
   */
  public void setDebug(int debug)
  {

    this.debug = debug;

  }

  /**
   * Return the default maximum inactive interval (in seconds)
   * for WillowSessions created by this Manager.
   */
  public int getMaxInactiveInterval()
  {

    return (this.maxInactiveInterval);

  }

  /**
   * Set the default maximum inactive interval (in seconds)
   * for WillowSessions created by this Manager.
   *
   * @param interval The new default value
   */
  public void setMaxInactiveInterval(int interval)
  {

    int oldMaxInactiveInterval = this.maxInactiveInterval;
    this.maxInactiveInterval = interval;
    support.firePropertyChange(
      "maxInactiveInterval",
      new Integer(oldMaxInactiveInterval),
      new Integer(this.maxInactiveInterval));

  }

  /** Use /dev/random-type special device. This is new code, but may reduce the
  *  big delay in generating the random.
  *
  *  You must specify a path to a random generator file. Use /dev/urandom
  *  for linux ( or similar ) systems. Use /dev/random for maximum security
  *  ( it may block if not enough "random" exist ). You can also use
  *  a pipe that generates random.
  *
  *  The code will check if the file exists, and default to java Random
  *  if not found. There is a significant performance difference, very
  *  visible on the first call to getWillowSession ( like in the first RULE )
  *  - so use it if available.
  */
  public void setRandomFile(String s)
  {

      try
      {
        devRandomSource = s;
        File f = new File(devRandomSource);
        if (!f.exists())
          return;
        randomIS = new DataInputStream(new FileInputStream(f));
        randomIS.readLong();
        if (log.isDebugEnabled())
          log.debug("Opening " + devRandomSource);
      }
      catch (IOException ex)
      {
        randomIS = null;
      }
  }

  public String getRandomFile()
  {
    return devRandomSource;
  }

  /**
   * Return the random number generator instance we should use for
   * generating session identifiers.  If there is no such generator
   * currently defined, construct and seed a new one.
   */
  public synchronized Random getRandom()
  {
    if (this.random == null)
    {
      synchronized (this)
      {
        if (this.random == null)
        {
          // Calculate the new random number generator seed
          long seed = System.currentTimeMillis();
          long t1 = seed;
          try
          {
            // Construct and seed a new random number generator
            Class clazz = Class.forName(randomClass);
            this.random = (Random) clazz.newInstance();
            this.random.setSeed(seed);
          }
          catch (Exception e)
          {
            // Fall back to the simple case
            log.error(sm.getString("sessionManager.random", randomClass), e);
            this.random = new java.util.Random();
            this.random.setSeed(seed);
          }
          long t2 = System.currentTimeMillis();
          if ((t2 - t1) > 100)
            log.debug(sm.getString("sessionManager.seeding", randomClass) + " " + (t2 - t1));
        }
      }
    }

    return (this.random);

  }

  /**
   * Return the random number generator class name.
   */
  public String getRandomClass()
  {

    return (this.randomClass);

  }

  /**
   * Set the random number generator class name.
   *
   * @param randomClass The new random number generator class name
   */
  public void setRandomClass(String randomClass)
  {

    String oldRandomClass = this.randomClass;
    this.randomClass = randomClass;
    support.firePropertyChange("randomClass", oldRandomClass, this.randomClass);

  }

  /**
   * Add this WillowSession to the set of active WillowSessions for this Manager.
   *
   * @param session WillowSession to be added
   */
  public void add(WorkflowSession session)
  {

    synchronized (sessions)
    {
      sessions.put(session.getId(), session);
      if (sessions.size() > maxActive)
      {
        maxActive = sessions.size();
      }
    }
  }

  /**
   * Return the active WillowSession, associated with this Manager, with the
   * specified session id (if any); otherwise return <code>null</code>.
   *
   * @param id The session id for the session to be returned
   *
   * @exception IllegalStateException if a new session cannot be
   *  instantiated for any reason
   * @exception IOException if an input/output error occurs while
   *  processing this request
   */
  public WillowSession findSession(String id)
  {

    if (id == null)
    {
      return (null);
    }
    synchronized (sessions)
    {
      WillowSession session = (WillowSession) sessions.get(id);
      return (session);
    }

  }

  /**
   * Return the set of active WillowSessions associated with this Manager.
   * If this Manager has no active WillowSessions, a zero-length array is returned.
   */
  public WillowSession[] findSessions()
  {

    WillowSession results[] = null;
    synchronized (sessions)
    {
      results = new WillowSession[sessions.size()];
      results = (WillowSession[]) sessions.values().toArray(results);
    }
    return (results);

  }

  /**
   * Remove this WillowSession from the active WillowSessions for this Manager.
   *
   * @param session WillowSession to be removed
   */
  public void remove(WorkflowSession session)
  {

    synchronized (sessions)
    {
      sessions.remove(session.getId());
    }

  }

  /**
   * Remove a property change listener from this component.
   *
   * @param listener The listener to remove
   */
  public void removePropertyChangeListener(PropertyChangeListener listener)
  {

    support.removePropertyChangeListener(listener);

  }

  // ------------------------------------------------------ Protected Methods

  protected void getRandomBytes(byte bytes[])
  {
    // Generate a byte array containing a session identifier
    if (devRandomSource != null && randomIS == null)
    {
      setRandomFile(devRandomSource);
    }
    if (randomIS != null)
    {
      try
      {
        int len = randomIS.read(bytes);
        if (len == bytes.length)
        {
          return;
        }
        log.debug("Got " + len + " " + bytes.length);
      }
      catch (Exception ex)
      {
      }
      devRandomSource = null;
      randomIS = null;
    }
    getRandom().nextBytes(bytes);
  }

  /**
   * Generate and return a new session identifier.
   */
  protected synchronized String generateSessionId()
  {
    byte bytes[] = new byte[SESSION_ID_BYTES];
    getRandomBytes(bytes);

    // Render the result as a String of hexadecimal digits
    StringBuffer result = new StringBuffer();
    for (int i = 0; i < bytes.length; i++)
    {
      byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
      byte b2 = (byte) (bytes[i] & 0x0f);
      if (b1 < 10)
        result.append((char) ('0' + b1));
      else
        result.append((char) ('A' + (b1 - 10)));
      if (b2 < 10)
        result.append((char) ('0' + b2));
      else
        result.append((char) ('A' + (b2 - 10)));
    }
    return (result.toString());

  }

  // -------------------------------------------------------- Package Methods

  /**
   * Log a message on the Logger associated with our Container (if any).
   *
   * @param message Message to be logged
   * @deprecated
   */
  protected void log(String message)
  {
    log.info(message);
  }

  /**
   * Log a message on the Logger associated with our Container (if any).
   *
   * @param message Message to be logged
   * @param throwable Associated exception
   * @deprecated
   */
  protected void log(String message, Throwable throwable)
  {
    log.info(message, throwable);
  }

  public void setSessionCounter(int sessionCounter)
  {
    this.sessionCounter = sessionCounter;
  }

  /**
   * Total sessions created by this manager.
   *
   * @return sessions created
   */
  public int getSessionCounter()
  {
    return sessionCounter;
  }

  /**
   * Number of duplicated session IDs generated by the random source.
   * Anything bigger than 0 means problems.
   *
   * @return
   */
  public int getDuplicates()
  {
    return duplicates;
  }

  public void setDuplicates(int duplicates)
  {
    this.duplicates = duplicates;
  }

  /**
   * Returns the number of active sessions
   *
   * @return number of sessions active
   */
  public int getActiveSessions()
  {
    return sessions.size();
  }

  /**
   * Max number of concurent active sessions
   *
   * @return
   */
  public int getMaxActive()
  {
    return maxActive;
  }

  public void setMaxActive(int maxActive)
  {
    this.maxActive = maxActive;
  }

  /**
   * For debugging: return a list of all session ids currently active
   *
   */
  public String listSessionIds()
  {
    StringBuffer sb = new StringBuffer();
    Iterator keys = sessions.keySet().iterator();
    while (keys.hasNext())
    {
      sb.append(keys.next()).append(" ");
    }
    return sb.toString();
  }

  public void expireSession(String sessionId)
  {
    WillowSession s = (WillowSession) sessions.get(sessionId);
    if (s == null)
    {
      log.info("WillowSession not found " + sessionId);
      return;
    }
    s.expire();
  }

  public String getLastAccessedTime(String sessionId)
  {
    WillowSession s = (WillowSession) sessions.get(sessionId);
    if (s == null)
    {
      log.info("WillowSession not found " + sessionId);
      return "";
    }
    return new Date(s.getLastAccessedTime()).toString();
  }

  // ----------------------------------------------------- Instance Variables

  /**
   * The lifecycle event support for this component.
   */
  protected LifecycleSupport lifecycle = new LifecycleSupport(this);

  /**
   * The maximum number of active WillowSessions allowed, or -1 for no limit.
   */
  protected int maxActiveWillowSessions = -1;

  /**
   * Path name of the disk file in which active sessions are saved
   * when we stop, and from which these sessions are loaded when we start.
   * A <code>null</code> value indicates that no persistence is desired.
   * If this pathname is relative, it will be resolved against the
   * temporary working directory provided by our context, available via
   * the <code>javax.servlet.context.tempdir</code> context attribute.
   */
  protected String pathname = "SESSIONS.ser";

  /**
   * Has this component been started yet?
   */
  protected boolean started = false;

  int rejectedWillowSessions = 0;
  int expiredWillowSessions = 0;
  long processingTime = 0;

  // ------------------------------------------------------------- Properties

  /**
   * Set the Engine with which this Manager has been associated.  If
   * it is a Engine (the usual case), listen for changes to the session
   * timeout property.
   *
   * @param engine The associated Engine
   */
  public void setEngine(Engine engine)
  {
    // Default processing provided by our superclass
    Engine oldEngine = this.engine;
    this.engine = engine;
    support.firePropertyChange("engine", oldEngine, this.engine);

    // Register with the new Engine (if any)
    if ((this.engine != null))
    {
       setMaxInactiveInterval(this.engine.getSessionTimeout() * 60);
       this.engine.addPropertyChangeListener(this);
    }

  }

  /**
   * Return descriptive information about this Manager implementation and
   * the corresponding version number, in the format
   * <code>&lt;description&gt;/&lt;version&gt;</code>.
   */
  public String getInfo()
  {

    return (this.info);

  }

  /**
   * Return the maximum number of active WillowSessions allowed, or -1 for
   * no limit.
   */
  public int getMaxActiveSessions()
  {

    return (this.maxActiveWillowSessions);

  }

  /** Number of session creations that failed due to maxActiveWillowSessions
   *
   * @return
   */
  public int getRejectedSessions()
  {
    return rejectedWillowSessions;
  }

  public void setRejectedSessions(int rejectedWillowSessions)
  {
    this.rejectedWillowSessions = rejectedWillowSessions;
  }

  /** Number of sessions that expired.
   *
   * @return
   */
  public int getExpiredSessions()
  {
    return expiredWillowSessions;
  }

  public void setExpiredSessions(int expiredWillowSessions)
  {
    this.expiredWillowSessions = expiredWillowSessions;
  }

  public long getProcessingTime()
  {
    return processingTime;
  }

  public void setProcessingTime(long processingTime)
  {
    this.processingTime = processingTime;
  }

  /**
   * Set the maximum number of actives WillowSessions allowed, or -1 for
   * no limit.
   *
   * @param max The new maximum number of sessions
   */
  public void setMaxActiveSessions(int max)
  {

    int oldMaxActiveWillowSessions = this.maxActiveWillowSessions;
    this.maxActiveWillowSessions = max;
    support.firePropertyChange(
      "maxActiveWillowSessions",
      new Integer(oldMaxActiveWillowSessions),
      new Integer(this.maxActiveWillowSessions));

  }

  /**
   * Return the descriptive short name of this Manager implementation.
   */
  public String getName()
  {

    return (name);

  }

  /**
   * Return the session persistence pathname, if any.
   */
  public String getPathname()
  {

    return (this.pathname);

  }

  /**
   * Set the session persistence pathname to the specified value.  If no
   * persistence support is desired, set the pathname to <code>null</code>.
   *
   * @param pathname New session persistence pathname
   */
  public void setPathname(String pathname)
  {

    String oldPathname = this.pathname;
    this.pathname = pathname;
    support.firePropertyChange("pathname", oldPathname, this.pathname);

  }

  // --------------------------------------------------------- Public Methods

  /**
   * Construct and return a new session object, based on the default
   * settings specified by this Manager's properties.  The session
   * id will be assigned by this method, and available via the getId()
   * method of the returned session.  If a new session cannot be created
   * for any reason, return <code>null</code>.
   *
   * @exception IllegalStateException if a new session cannot be
   *  instantiated for any reason
   */
  public WillowSession createSession()
  {

    if ((maxActiveWillowSessions >= 0) && (sessions.size() >= maxActiveWillowSessions))
    {
      rejectedWillowSessions++;
      throw new IllegalStateException(sm.getString("sessionManager.createWillowSession.ise"));
    }

    WillowSession session = new WillowSession(this);

    // Initialize the properties of the new session and return it
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(this.maxInactiveInterval);
    String sessionId = generateSessionId();

    synchronized (sessions)
    {
      while (sessions.get(sessionId) != null)
      { // Guarantee uniqueness
        duplicates++;
        sessionId = generateSessionId();
      }
    }

    session.setId(sessionId);
    sessionCounter++;

    return (session);

  }

  // ------------------------------------------------------ Lifecycle Methods

  /**
   * Add a lifecycle event listener to this component.
   *
   * @param listener The listener to add
   */
  public void addLifecycleListener(LifecycleListener listener)
  {

    lifecycle.addLifecycleListener(listener);

  }

  /**
   * Get the lifecycle listeners associated with this lifecycle. If this
   * Lifecycle has no listeners registered, a zero-length array is returned.
   */
  public LifecycleListener[] findLifecycleListeners()
  {

    return lifecycle.findLifecycleListeners();

  }

  /**
   * Remove a lifecycle event listener from this component.
   *
   * @param listener The listener to remove
   */
  public void removeLifecycleListener(LifecycleListener listener)
  {

    lifecycle.removeLifecycleListener(listener);

  }

  /**
   * Prepare for the beginning of active use of the public methods of this
   * component.  This method should be called after <code>configure()</code>,
   * and before any of the public methods of the component are utilized.
   *
   * @exception LifecycleException if this component detects a fatal error
   *  that prevents this component from being used
   */
  public void start() throws LifecycleException
  {
    // Validate and update our current component state
    if (started)
    {
      return;
    }
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    // Force initialization of the random number generator
    log.debug("Force random number initialization starting");
     generateSessionId();
    log.debug("Force random number initialization completed");

  }

  /**
   * Gracefully terminate the active use of the public methods of this
   * component.  This method should be the last one called on a given
   * instance of this component.
   *
   * @exception LifecycleException if this component detects a fatal error
   *  that needs to be reported
   */
  public void stop() throws LifecycleException
  {

    log.debug("Stopping");

    // Validate and update our current component state
    if (!started)
    {
      throw new LifecycleException(sm.getString("sessionManager.notStarted"));
    }
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;

    // Expire all active sessions
    WillowSession sessions[] = findSessions();
    for (int i = 0; i < sessions.length; i++)
    {
      WillowSession session = (WillowSession) sessions[i];
      if (!session.isValid())
        continue;
      try
      {
        session.expire();
      }
      catch (Throwable t)
      {
        ;
      }
    }

    // Require a new random number generator if we are restarted
    this.random = null;

    if (initialized)
    {
      //destroy();
    }
  }

  // ----------------------------------------- PropertyChangeListener Methods

  /**
   * Process property change events from our associated Context.
   *
   * @param event The property change event that has occurred
   */
  public void propertyChange(PropertyChangeEvent event)
  {

    // Validate the source of this event
    if (!(event.getSource() instanceof Context))
      return;

    // Process a relevant property change
    if (event.getPropertyName().equals("sessionTimeout"))
    {
      try
      {
        setMaxInactiveInterval(((Integer) event.getNewValue()).intValue() * 60);
      }
      catch (NumberFormatException e)
      {
        log.error(sm.getString("sessionManager.sessionTimeout", event.getNewValue().toString()));
      }
    }

  }

  // ------------------------------------------------------ Protected Methods

  /**
   * Invalidate all sessions that have expired.
   */
  public void processExpires()
  {
    long timeNow = System.currentTimeMillis();
    WillowSession sessions[] = findSessions();

    for (int i = 0; i < sessions.length; i++)
    {
      WillowSession session = (WillowSession) sessions[i];   
      if (!session.isValid())
      {
        try
        {
          expiredWillowSessions++;
          session.expire();
        }
        catch (Throwable t)
        {
          log.error(sm.getString("sessionManager.expireException"), t);
        }
      }
    }
    long timeEnd = System.currentTimeMillis();
    processingTime += (timeEnd - timeNow);

  }

}
TOP

Related Classes of org.huihoo.willow.session.SessionManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.