//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server.session;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;

import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.util.log.Logger;

/**
 *
 * <p>
 * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
 * </p>
 *
 */
@SuppressWarnings("deprecation")
public abstract class AbstractSession implements AbstractSessionManager.SessionIf
{
    final static Logger LOG = SessionHandler.LOG;
    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
    private  String _clusterId; // ID without any node (ie "worker") id appended
    private  String _nodeId;    // ID of session with node(ie "worker") id appended
    private final AbstractSessionManager _manager;
    private boolean _idChanged;
    private final long _created;
    private long _cookieSet;
    private long _accessed;         // the time of the last access
    private long _lastAccessed;     // the time of the last access excluding this one
    private boolean _invalid;
    private boolean _doInvalidate;
    private long _maxIdleMs;
    private boolean _newSession;
    private int _requests;



    /* ------------------------------------------------------------- */
    protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
    {
        _manager = abstractSessionManager;

        _newSession=true;
        _created=System.currentTimeMillis();
        _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request);
        _accessed=_created;
        _lastAccessed=_created;
        _requests=1;
        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
        if (LOG.isDebugEnabled())
            LOG.debug("new session & id "+_nodeId+" "+_clusterId);
    }

    /* ------------------------------------------------------------- */
    protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
    {
        _manager = abstractSessionManager;
        _created=created;
        _clusterId=clusterId;
        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null);
        _accessed=accessed;
        _lastAccessed=accessed;
        _requests=1;
        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
        if (LOG.isDebugEnabled())
            LOG.debug("new session "+_nodeId+" "+_clusterId);
    }

    /* ------------------------------------------------------------- */
    /**
     * asserts that the session is valid
     */
    protected void checkValid() throws IllegalStateException
    {
        if (_invalid)
            throw new IllegalStateException();
    }
    
    /* ------------------------------------------------------------- */
    /** Check to see if session has expired as at the time given.
     * @param time
     * @return
     */
    protected boolean checkExpiry(long time)
    {
        if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
            return true;
        return false;
    }

    /* ------------------------------------------------------------- */
    @Override
    public AbstractSession getSession()
    {
        return this;
    }

    /* ------------------------------------------------------------- */
    public long getAccessed()
    {
        synchronized (this)
        {
            return _accessed;
        }
    }

    /* ------------------------------------------------------------- */
    public abstract Map<String,Object> getAttributeMap();


 
    

    /* ------------------------------------------------------------ */
    public abstract int getAttributes();
  

 

    /* ------------------------------------------------------------ */
    public abstract Set<String> getNames();
  

    /* ------------------------------------------------------------- */
    public long getCookieSetTime()
    {
        return _cookieSet;
    }

    /* ------------------------------------------------------------- */
    @Override
    public long getCreationTime() throws IllegalStateException
    {
        checkValid();
        return _created;
    }

    /* ------------------------------------------------------------ */
    @Override
    public String getId() throws IllegalStateException
    {
        return _manager._nodeIdInSessionId?_nodeId:_clusterId;
    }

    /* ------------------------------------------------------------- */
    public String getNodeId()
    {
        return _nodeId;
    }

    /* ------------------------------------------------------------- */
    public String getClusterId()
    {
        return _clusterId;
    }

    /* ------------------------------------------------------------- */
    @Override
    public long getLastAccessedTime() throws IllegalStateException
    {
        checkValid();
        return _lastAccessed;
    }
    
    /* ------------------------------------------------------------- */
    public void setLastAccessedTime(long time)
    {
        _lastAccessed = time;
    }

    /* ------------------------------------------------------------- */
    @Override
    public int getMaxInactiveInterval()
    {
        return (int)(_maxIdleMs/1000);
    }

    /* ------------------------------------------------------------ */
    /*
     * @see javax.servlet.http.HttpSession#getServletContext()
     */
    @Override
    public ServletContext getServletContext()
    {
        return _manager._context;
    }

    /* ------------------------------------------------------------- */
    @Deprecated
    @Override
    public HttpSessionContext getSessionContext() throws IllegalStateException
    {
        checkValid();
        return AbstractSessionManager.__nullSessionContext;
    }

    /* ------------------------------------------------------------- */
    /**
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #getAttribute}
     */
    @Deprecated
    @Override
    public Object getValue(String name) throws IllegalStateException
    {
        return getAttribute(name);
    }

 

    /* ------------------------------------------------------------ */
    public void renewId(HttpServletRequest request)
    {
        _manager._sessionIdManager.renewSessionId(getClusterId(), getNodeId(), request); 
        setIdChanged(true);
    }
       
    /* ------------------------------------------------------------- */
    public SessionManager getSessionManager()
    {
        return _manager;
    }

    /* ------------------------------------------------------------ */
    protected void setClusterId (String clusterId)
    {
        _clusterId = clusterId;
    }
    
    /* ------------------------------------------------------------ */
    protected void setNodeId (String nodeId)
    {
        _nodeId = nodeId;
    }
    

    /* ------------------------------------------------------------ */
    protected boolean access(long time)
    {
        synchronized(this)
        {
            if (_invalid)
                return false;
            _newSession=false;
            _lastAccessed=_accessed;
            _accessed=time;

            if (checkExpiry(time))
            {
                invalidate();
                return false;
            }
            _requests++;
            return true;
        }
    }

    /* ------------------------------------------------------------ */
    protected void complete()
    {
        synchronized(this)
        {
            _requests--;
            if (_doInvalidate && _requests<=0  )
                doInvalidate();
        }
    }


    /* ------------------------------------------------------------- */
    protected void timeout() throws IllegalStateException
    {
        // remove session from context and invalidate other sessions with same ID.
        _manager.removeSession(this,true);

        // Notify listeners and unbind values
        boolean do_invalidate=false;
        synchronized (this)
        {
            if (!_invalid)
            {
                if (_requests<=0)
                    do_invalidate=true;
                else
                    _doInvalidate=true;
            }
        }
        if (do_invalidate)
            doInvalidate();
    }

    /* ------------------------------------------------------------- */
    @Override
    public void invalidate() throws IllegalStateException
    {
        checkValid();
        // remove session from context and invalidate other sessions with same ID.
        _manager.removeSession(this,true);
        doInvalidate();
    }

    /* ------------------------------------------------------------- */
    protected void doInvalidate() throws IllegalStateException
    {
        try
        {
            if (LOG.isDebugEnabled())
                LOG.debug("invalidate {}",_clusterId);
            if (isValid())
                clearAttributes();
        }
        finally
        {
            synchronized (this)
            {
                // mark as invalid
                _invalid=true;
            }
        }
    }

    /* ------------------------------------------------------------- */
    public abstract void clearAttributes();
   

    /* ------------------------------------------------------------- */
    public boolean isIdChanged()
    {
        return _idChanged;
    }

    /* ------------------------------------------------------------- */
    @Override
    public boolean isNew() throws IllegalStateException
    {
        checkValid();
        return _newSession;
    }

    /* ------------------------------------------------------------- */
    /**
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #setAttribute}
     */
    @Deprecated
    @Override
    public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
    {
        changeAttribute(name,value);
    }

    /* ------------------------------------------------------------ */
    @Override
    public void removeAttribute(String name)
    {
        setAttribute(name,null);
    }

    /* ------------------------------------------------------------- */
    /**
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #removeAttribute}
     */
    @Deprecated
    @Override
    public void removeValue(java.lang.String name) throws IllegalStateException
    {
        removeAttribute(name);
    }
    
    /* ------------------------------------------------------------ */
    @Override
    public Enumeration<String> getAttributeNames()
    {
        synchronized (this)
        {
            checkValid();
            return doGetAttributeNames();
        }
    }
    
    /* ------------------------------------------------------------- */
    /**
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #getAttributeNames}
     */
    @Deprecated
    @Override
    public String[] getValueNames() throws IllegalStateException
    {
        synchronized(this)
        {
            checkValid();
            Enumeration<String> anames = doGetAttributeNames();
            if (anames == null)
                return new String[0];
            ArrayList<String> names = new ArrayList<String>();
            while (anames.hasMoreElements())
                names.add(anames.nextElement());
            return names.toArray(new String[names.size()]);
        }
    }
    

    /* ------------------------------------------------------------ */
    public abstract Object doPutOrRemove(String name, Object value);
 

    /* ------------------------------------------------------------ */
    public abstract Object doGet(String name);
    
    
    /* ------------------------------------------------------------ */
    public abstract Enumeration<String> doGetAttributeNames();
    
    
    /* ------------------------------------------------------------ */
    @Override
    public Object getAttribute(String name)
    {
        synchronized (this)
        {
            checkValid();
            return doGet(name);
        }
    }
   

    /* ------------------------------------------------------------ */
    @Override
    public void setAttribute(String name, Object value)
    {
        changeAttribute(name,value);
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @param name
     * @param value
     * @deprecated use changeAttribute(String,Object) instead
     * @return
     */
    protected boolean updateAttribute (String name, Object value)
    {
        Object old=null;
        synchronized (this)
        {
            checkValid();
            old=doPutOrRemove(name,value);
        }

        if (value==null || !value.equals(old))
        {
            if (old!=null)
                unbindValue(name,old);
            if (value!=null)
                bindValue(name,value);

            _manager.doSessionAttributeListeners(this,name,old,value);
            return true;
        }
        return false;
    }
    
    
    /* ------------------------------------------------------------ */
    /**
     * Either set (perhaps replace) or remove the value of the attribute
     * in the session. The appropriate session attribute listeners are
     * also called.
     * 
     * @param name
     * @param value
     * @return
     */
    protected Object changeAttribute (String name, Object value)
    {
        Object old=null;
        synchronized (this)
        {
            checkValid();
            old=doPutOrRemove(name,value);
        }

        callSessionAttributeListeners(name, value, old);

        return old;
    }

    /* ------------------------------------------------------------ */
    /**
     * Call binding and attribute listeners based on the new and old
     * values of the attribute.
     * 
     * @param name name of the attribute
     * @param newValue  new value of the attribute
     * @param oldValue previous value of the attribute
     */
    protected void callSessionAttributeListeners (String name, Object newValue, Object oldValue)
    {
        if (newValue==null || !newValue.equals(oldValue))
        {
            if (oldValue!=null)
                unbindValue(name,oldValue);
            if (newValue!=null)
                bindValue(name,newValue);

            _manager.doSessionAttributeListeners(this,name,oldValue,newValue);
        }
    }
  

    /* ------------------------------------------------------------- */
    public void setIdChanged(boolean changed)
    {
        _idChanged=changed;
    }

    /* ------------------------------------------------------------- */
    @Override
    public void setMaxInactiveInterval(int secs)
    {
        _maxIdleMs=(long)secs*1000L;
    }

    /* ------------------------------------------------------------- */
    @Override
    public String toString()
    {
        return this.getClass().getName()+":"+getId()+"@"+hashCode();
    }

    /* ------------------------------------------------------------- */
    /** If value implements HttpSessionBindingListener, call valueBound() */
    public void bindValue(java.lang.String name, Object value)
    {
        if (value!=null&&value instanceof HttpSessionBindingListener)
            ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
    }

    /* ------------------------------------------------------------ */
    public boolean isValid()
    {
        return !_invalid;
    }

    /* ------------------------------------------------------------- */
    protected void cookieSet()
    {
        synchronized (this)
        {
            _cookieSet=_accessed;
        }
    }

    /* ------------------------------------------------------------ */
    public int getRequests()
    {
        synchronized (this)
        {
            return _requests;
        }
    }

    /* ------------------------------------------------------------ */
    public void setRequests(int requests)
    {
        synchronized (this)
        {
            _requests=requests;
        }
    }

    /* ------------------------------------------------------------- */
    /** If value implements HttpSessionBindingListener, call valueUnbound() */
    public void unbindValue(java.lang.String name, Object value)
    {
        if (value!=null&&value instanceof HttpSessionBindingListener)
            ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
    }

    /* ------------------------------------------------------------- */
    public void willPassivate()
    {
        synchronized(this)
        {
            HttpSessionEvent event = new HttpSessionEvent(this);
            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
            {
                Object value = iter.next();
                if (value instanceof HttpSessionActivationListener)
                {
                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                    listener.sessionWillPassivate(event);
                }
            }
        }
    }

    /* ------------------------------------------------------------- */
    public void didActivate()
    {
        synchronized(this)
        {
            HttpSessionEvent event = new HttpSessionEvent(this);
            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
            {
                Object value = iter.next();
                if (value instanceof HttpSessionActivationListener)
                {
                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                    listener.sessionDidActivate(event);
                }
            }
        }
    }


}
