//========================================================================
//$Id: StateAdaptor.java,v 1.4 2004/05/09 20:30:47 gregwilkins Exp $
//Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//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 org.mortbay.j2ee.session;

//----------------------------------------

import java.rmi.RemoteException;
import java.util.Enumeration;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionContext;

import org.jboss.logging.Logger;

//----------------------------------------
//this class is responsible for presenting a State container to a
//servlet as a HttpSession - since this interface is not an ideal one
//to use for the container interceptors. It constrains all the inputs
//to this API as specified...

//Since this is the front end of the Container, maybe it should be so
//called ? or maybe I should just call it [Http]Session?

//we should cache our id locally...

public class StateAdaptor implements Manager.Session
{
    protected static final Logger _log = Logger.getLogger(StateAdaptor.class);

    Manager _manager;

    State _state = null;

    boolean _new = true;

    // we cache these for speed...
    final String _id;

    StateAdaptor(String id, Manager manager, int maxInactiveInterval,
            long lastAccessedTime)
    {
        _id = id;
        _manager = manager;
    }

    void setState(State state)
    {
        _state = state;
    }

    State getState()
    {
        return _state;
    }

    // hmmmm...
    // why does Greg call this?
    // could it be compressed into a subsequent call ?
    public boolean isValid()
    {
        if (_state == null) return false;

        StateInterceptor si = (StateInterceptor) _state;
        si.setManager(_manager);
        si.setSession(this);

        try
        {
            _state.getLastAccessedTime(); // this should cause an interceptor/the session to check
            return true;
        }
        catch (IllegalStateException ignore)
        {
            return false;
        }
        catch (Exception e)
        {
            _log.error("problem contacting HttpSession", e);
            return false;
        }
    }

    // HttpSession API

    public long getCreationTime() throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getCreationTime();
        }
        catch (RemoteException e)
        {
            _log.error("could not get CreationTime", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public String getId() throws IllegalStateException
    {
        checkState();

        // locally cached and invariant
        return _id;
    }

    public long getLastAccessedTime() throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getLastAccessedTime();
        }
        catch (RemoteException e)
        {
            _log.error("could not get LastAccessedTime", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    // clarify with Tomcat, whether this is on a per Session or SessionManager basis...
    public void setMaxInactiveInterval(int interval)
    {
        checkState();

        try
        {
            _state.setMaxInactiveInterval(interval);
        }
        catch (RemoteException e)
        {
            _log.error("could not set MaxInactiveInterval", e);
        }
    }

    public int getMaxInactiveInterval()
    {
        checkState();

        try
        {
            return _state.getMaxInactiveInterval();
        }
        catch (RemoteException e)
        {
            // Can I throw an exception of some type here - instead of
            // returning rubbish ? - TODO
            _log.error("could not get MaxInactiveInterval", e);
            return 0;
        }
    }

    public Object getAttribute(String name) throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getAttribute(name);
        }
        catch (RemoteException e)
        {
            _log.error("could not get Attribute", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public Object getValue(String name) throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getAttribute(name);
        }
        catch (RemoteException e)
        {
            _log.error("could not get Value", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public Enumeration getAttributeNames() throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getAttributeNameEnumeration();
        }
        catch (RemoteException e)
        {
            _log.error("could not get AttributeNames", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public String[] getValueNames() throws IllegalStateException
    {
        checkState();

        try
        {
            return _state.getAttributeNameStringArray();
        }
        catch (RemoteException e)
        {
            _log.error("could not get ValueNames", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public void setAttribute(String name, Object value)
            throws IllegalStateException
    {
        checkState();

        try
        {
            if (value == null)
                _state.removeAttribute(name, false);
            else
            {
                if (name == null)
                    throw new IllegalArgumentException(
                            "invalid attribute name: " + name);

                _state.setAttribute(name, value, false);
            }
        }
        catch (RemoteException e)
        {
            _log.error("could not set Attribute", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public void putValue(String name, Object value)
            throws IllegalStateException
    {
        checkState();

        if (name == null)
            throw new IllegalArgumentException("invalid attribute name: "
                    + name);

        if (value == null)
            throw new IllegalArgumentException("invalid attribute value: "
                    + value);

        try
        {
            _state.setAttribute(name, value, false);
        }
        catch (RemoteException e)
        {
            _log.error("could not put Value", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public void removeAttribute(String name) throws IllegalStateException
    {
        checkState();

        try
        {
            _state.removeAttribute(name, false);
        }
        catch (RemoteException e)
        {
            _log.error("could not remove Attribute", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public void removeValue(String name) throws IllegalStateException
    {
        checkState();

        try
        {
            _state.removeAttribute(name, false);
        }
        catch (RemoteException e)
        {
            _log.error("could not remove Value", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    public void invalidate() throws IllegalStateException
    {
        if (_log.isTraceEnabled())
            _log.trace("user invalidated session: " + getId());
        _manager.destroySession(this);
    }

    /**
     *
     * Returns <code>true</code> if the client does not yet know about the
     * session or if the client chooses not to join the session.  For
     * example, if the server used only cookie-based sessions, and
     * the client had disabled the use of cookies, then a session would
     * be new on each request.
     *
     * @return 				<code>true</code> if the
     *					server has created a session,
     *					but the client has not yet joined
     *
     * @exception IllegalStateException	if this method is called on an
     *					already invalidated session
     *
     */

    public boolean isNew() throws IllegalStateException
    {
        return _new;
    }

    public ServletContext getServletContext()
    {
        return _manager.getServletContext();
    }

    public HttpSessionContext getSessionContext()
    {
        return _manager.getSessionContext();
    }

    // this one's for Greg...
    public void access()
    {
        long time = System.currentTimeMillis(); // we could get this from Manager - less accurate
        setLastAccessedTime(time);

        _new = false; // synchronise - TODO
    }

    public void setLastAccessedTime(long time) throws IllegalStateException
    {
        checkState();

        try
        {
            _state.setLastAccessedTime(time);
        }
        catch (RemoteException e)
        {
            _log.error("could not set LastAccessedTime", e);
            throw new IllegalStateException("problem with distribution layer");
        }
    }

    protected void checkState() throws IllegalStateException
    {
        if (_state == null)
            throw new IllegalStateException("invalid session");
        else
        {

            // this is a hack to get new interceptor stack to work... - TODO
            //          StateInterceptor si=(StateInterceptor)_state;
            //          si.setManager(_manager);
            //          si.setSession(this);
            StateInterceptor si = null;
            if (_state instanceof StateInterceptor)
            {
                si = (StateInterceptor) _state;
                si.setManager(_manager);
                si.setSession(this);
            }
            else
            {
                si = new StateInterceptor();
                si.setManager(_manager);
                si.setSession(this);
                si.setState(_state);
                _state = si;
            }
        }
    }

    public String toString()
    {
        return "<" + getClass() + "->" + _state + ">";
    }

    // I'm still not convinced that this is the correct place for this
    // method- but I can;t think of a better way - maybe in the next
    // iteration...

    //  MigrationInterceptor _mi=null;

    //  public void
    //  registerMigrationListener(MigrationInterceptor mi)
    //  {
    //  _mi=mi;
    //  }

    public void migrate()
    {
        //     if (_mi!=null)
        //       _mi.migrate(); // yeugh - TODO
    }
}
