/*
 * Copyright 2000-2002,2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// package
package org.apache.jetspeed.services.statemanager;

// imports
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

/**
 * <p>
 * JetspeedHttpStateManagerService is an implementation of the
 * BaseStateManagerService which manages the states stored in the "current"
 * HttpSession.
 * </p>
 * <p>
 * Note: This implementation of the StateManagerService takes advantage of the
 * Servlet container's management of the HttpSession. When the session is
 * invalidated, the states we manage will be automatically cleaned up. When this
 * happens, the objects placed into our states will have their
 * SessionStateBindingListener mechanism invoked.
 * </p>
 * <p>
 * Note: This implementation segments the states by session. States created in
 * one session will NOT BE AVAILABLE from other sessions.
 * </p>
 * 
 * @version $Revision: 1.4 $
 * @see org.apache.jetspeed.services.statemanager.BaseStateManagerService
 * @see org.apache.jetspeed.services.statemanager.StateManagerService
 * @see org.apache.jetspeed.services.statemanager.SessionState
 * @author <a href="mailto:ggolden@apache.org">Glenn R. Golden</a>
 */
public class JetspeedHttpStateManagerService extends BaseStateManagerService
    implements Serializable {

  private static final long serialVersionUID = 6138392597252459984L;

  /**
   * Initialize the states storage.
   */
  @Override
  protected void initStates() {
  } // initStates

  /**
   * Cleanup the states storage.
   */
  @Override
  protected void shutdownStates() {
  } // shutdownStates

  /**
   * Access the current HttpSession.
   */
  private HttpSession getSession() {
    // get the current session that was installed for this thread
    HttpSession session =
      (HttpSession) m_httpSessions.get(Thread.currentThread());
    if (session == null) {
      return null;
    }

    // call isNew just to see if the session has been invalidated already
    try {
      session.isNew();
    } catch (IllegalStateException e) {
      return null;
    }

    return session;

  } // getSession

  /**
   * Convert the key to a name safe to store directly in the session.
   * 
   * @param key
   *          The state key.
   * @return a name safe to store directly in the session based on key.
   */
  private String getSessionKey(String key) {
    // we want our keys not to conflict with any other session usage...
    return JetspeedHttpStateManagerService.class.getName() + "." + key;

  } // getSessionKey

  /**
   * Access the Map which is the set of attributes for a state.
   * 
   * @param key
   *          The state key.
   * @return The Map which is the set of attributes for a state.
   */
  @Override
  protected Map getState(String key) {
    // get the session
    HttpSession session = getSession();
    if (session == null) {
      return null;
    }

    // get this state from our entry in the session
    StateEntry stateEntry =
      (StateEntry) session.getAttribute(getSessionKey(key));
    if (stateEntry == null) {
      return null;
    }

    return stateEntry.getMap();

  } // getState

  /**
   * Add a new state to the states we are managing.
   * 
   * @param key
   *          The state key.
   * @param state
   *          The Map which is the set of attributes for the state.
   */
  @Override
  protected void addState(String key, Map state) {
    // get the session
    HttpSession session = getSession();
    if (session == null) {
      return;
    }

    // create a stateEntry to hold our state Map
    StateEntry stateEntry = new StateEntry(key, state);

    // put it in the session
    session.setAttribute(getSessionKey(key), stateEntry);

  } // addState

  /**
   * Remove a state from the states we are managing.
   * 
   * @param key
   *          The state key.
   */
  @Override
  protected void removeState(String key) {
    // get the session
    HttpSession session = getSession();
    if (session == null) {
      return;
    }

    // remove the key from the session - the StateEntry will be notified
    session.removeAttribute(getSessionKey(key));

  } // removeState

  /**
   * Access an array of the keys of all states managed, those that start with
   * the parameter.
   * 
   * @param start
   *          The starting string used to select the keys.
   * @return an array of the keys of all states managed.
   */
  @Override
  protected String[] getStateKeys(String start) {
    // get the session
    HttpSession session = getSession();
    if (session == null) {
      return null;
    }

    // use this as the test pattern
    String pattern = getSessionKey(start);

    // for those that match, this starts the session key
    int subStart = getSessionKey("").length();

    // collect for return
    Vector rv = new Vector();

    // get the session names
    Enumeration names = session.getAttributeNames();
    while (names.hasMoreElements()) {
      String sessionName = (String) names.nextElement();

      // pick our states, and those whose key starts with the pattern
      if (sessionName.startsWith(pattern)) {
        rv.add(sessionName.substring(subStart));
      }
    }

    if (rv.size() == 0) {
      return null;
    }

    return (String[]) rv.toArray(new String[rv.size()]);

  } // getStateKeys

  /**
   * Store the Map for the state, and listen for HttpSessionBinding events
   */
  private class StateEntry implements HttpSessionBindingListener, Serializable {

    private static final long serialVersionUID = 8630600178442354718L;

    /** Store the map. */
    private Map m_map = null;

    /** The state key. */
    private String m_key = null;

    /**
     * Construct. s
     * 
     * @param key
     *          The state key.
     * @param map
     *          The map to hold.
     */
    public StateEntry(String key, Map map) {
      m_key = key;
      m_map = map;

    } // StateEntry

    /**
     * Access the map we are holding.
     * 
     * @return the Map we are holding.
     */
    public Map getMap() {
      return m_map;

    } // getMap

    /**
     * We don't care about when we are bound...
     */
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
    }

    /**
     * When we are unbound, unbind our state's (map's) attributes
     */
    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
      // notify all attribute and clear the state
      retireAttributes(m_key, m_map);
      m_map = null;
      m_key = null;
    }

  } // class StateEntry

} // JetspeedHttpStateManagerService

/**********************************************************************************
 * 
 * $Header:
 * /home/cvspublic/jakarta-jetspeed/src/java/org/apache/jetspeed/services
 * /statemanager/JetspeedHttpStateManagerService.java,v 1.4 2004/02/23 03:38:28
 * jford Exp $
 * 
 **********************************************************************************/

