//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  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.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

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.io.IdleTimeout;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Locker.Lock;




/**
 * Session
 *
 * A heavy-weight Session object representing a HttpSession. Session objects
 * relating to a context are kept in a {@link SessionCache}. The purpose of
 * the SessionCache is to keep the working set of Session objects in memory
 * so that they may be accessed quickly, and facilitate the sharing of a
 * Session object amongst multiple simultaneous requests referring to the
 * same session id.
 * 
 * The {@link SessionHandler} coordinates 
 * the lifecycle of Session objects with the help of the SessionCache.
 * 
 * @see SessionHandler
 * @see org.eclipse.jetty.server.SessionIdManager
 */
public class Session implements SessionHandler.SessionIf
{
    private  final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
    
    
    /**
     * 
     */
    public final static String SESSION_CREATED_SECURE="org.eclipse.jetty.security.sessionCreatedSecure";
    
    
    /**
     * State
     *
     * Validity states of a session
     */
    public enum State {VALID, INVALID, INVALIDATING};
    
    
    

    
    protected SessionData _sessionData; //the actual data associated with a session
    protected SessionHandler _handler; //the manager of the session
    protected String _extendedId; //the _id plus the worker name
    protected long _requests;
    protected boolean _idChanged; 
    protected boolean _newSession;
    protected State _state = State.VALID; //state of the session:valid,invalid or being invalidated
    protected Locker _lock = new Locker(); //sync lock
    protected boolean _resident = false;
    protected SessionInactivityTimeout _sessionInactivityTimer = null;
    
    

    /* ------------------------------------------------------------- */
    /**
     * SessionInactivityTimeout
     * 
     * Each Session has a timer associated with it that fires whenever 
     * it has been idle (ie not referenced by a request) for a 
     * configurable amount of time, or the Session expires. 
     * 
     * @see SessionCache
     *
     */
    public class SessionInactivityTimeout extends IdleTimeout
    {
        /**
         * 
         */
        public SessionInactivityTimeout()
        {
            super(getSessionHandler().getScheduler());
        }

        /** 
         * @see org.eclipse.jetty.io.IdleTimeout#onIdleExpired(java.util.concurrent.TimeoutException)
         */
        @Override
        protected void onIdleExpired(TimeoutException timeout)
        {
            //called when the timer goes off
            if (LOG.isDebugEnabled()) LOG.debug("Timer expired for session {}", getId());
           getSessionHandler().sessionInactivityTimerExpired(Session.this);
        }

        /** 
         * @see org.eclipse.jetty.io.IdleTimeout#isOpen()
         */
        @Override
        public boolean isOpen()
        {
            // Called to determine if the timer should be reset
            // True if:
            // 1. the session is still valid
            // BUT if passivated out to disk, do we really want this timer to keep going off?
            try (Lock lock = _lock.lockIfNotHeld())
            {
                return isValid() && isResident();
            }
        }

        /** 
         * @see org.eclipse.jetty.io.IdleTimeout#setIdleTimeout(long)
         */
        @Override
        public void setIdleTimeout(long idleTimeout)
        {
            if (LOG.isDebugEnabled()) LOG.debug("setIdleTimeout called: old="+getIdleTimeout()+" new="+idleTimeout);
            super.setIdleTimeout(idleTimeout);
        }

    }



    /* ------------------------------------------------------------- */
    /**
     * Create a new session
     * @param handler the SessionHandler that manages this session
     * @param request the request the session should be based on
     * @param data the session data
     */
    public Session (SessionHandler handler, HttpServletRequest request, SessionData data)
    {
        _handler = handler;
        _sessionData = data;
        _newSession = true;
        _sessionData.setDirty(true);
        _requests = 1; //access will not be called on this new session, but we are obviously in a request
    }
    
    

    /* ------------------------------------------------------------- */
    /**
     * Re-inflate an existing session from some eg persistent store.
     * 
     * @param handler the SessionHandler managing the session
     * @param data the session data
     */
    public Session (SessionHandler handler, SessionData data)
    {
        _handler = handler;
        _sessionData = data;
    }
    

    /* ------------------------------------------------------------- */
    /**
     *  Returns the current number of requests that are active in the
     *  Session.
     *  
     * @return the number of active requests for this session
     */
    public long getRequests()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _requests;
        }
    }
    
    

    

    /* ------------------------------------------------------------- */
    public void setExtendedId (String extendedId)
    {
        _extendedId = extendedId;
    }
    
    /* ------------------------------------------------------------- */
    protected void cookieSet()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
           _sessionData.setCookieSet(_sessionData.getAccessed());
        }
    }
    /* ------------------------------------------------------------ */
    protected boolean access(long time)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            if (!isValid())
                return false;
            _newSession=false;
            long lastAccessed = _sessionData.getAccessed();
            if (_sessionInactivityTimer != null)
                _sessionInactivityTimer.notIdle();
            _sessionData.setAccessed(time);
            _sessionData.setLastAccessed(lastAccessed);
           _sessionData.calcAndSetExpiry(time);
            if (isExpiredAt(time))
            {
                invalidate();
                return false;
            }
            _requests++;
            return true;
        }
    }
    
    /* ------------------------------------------------------------ */
    protected void complete()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            _requests--;
        }
    }

    

    /* ------------------------------------------------------------- */
    /** Check to see if session has expired as at the time given.
     * 
     * @param time the time since the epoch in ms
     * @return true if expired
     */
    protected boolean isExpiredAt(long time)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _sessionData.isExpiredAt(time);
        }
    }
    
    
    /* ------------------------------------------------------------- */
    /** Check if the Session has been idle longer than a number of seconds.
     * 
     * @param sec the number of seconds
     * @return true if the session has been idle longer than the interval
     */
    protected boolean isIdleLongerThan (int sec)
    {
        long now = System.currentTimeMillis();
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return ((_sessionData.getAccessed() + (sec*1000)) <= now);
        }
    }
    
  

    /* ------------------------------------------------------------ */
    /**
     * 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);

            if (_handler == null)
                throw new IllegalStateException ("No session manager for session "+ _sessionData.getId());
            
            _handler.doSessionAttributeListeners(this,name,oldValue,newValue);
        }
    }

    
    
    /* ------------------------------------------------------------- */
    /**
     * Unbind value if value implements {@link HttpSessionBindingListener} (calls {@link HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)}) 
     * @param name the name with which the object is bound or unbound  
     * @param value the bound value
     */
    public void unbindValue(java.lang.String name, Object value)
    {
        if (value!=null&&value instanceof HttpSessionBindingListener)
            ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
    }
    

    /* ------------------------------------------------------------- */
    /** 
     * Bind value if value implements {@link HttpSessionBindingListener} (calls {@link HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)}) 
     * @param name the name with which the object is bound or unbound  
     * @param value the bound value
     */
    public void bindValue(java.lang.String name, Object value)
    {
        if (value!=null&&value instanceof HttpSessionBindingListener)
            ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
    }


    /* ------------------------------------------------------------- */
    /**
     * Call the activation listeners. This must be called holding the
     * lock.
     */
    public void didActivate()
    {
        HttpSessionEvent event = new HttpSessionEvent(this);
        for (Iterator<String> iter = _sessionData.getKeys().iterator(); iter.hasNext();)
        {
            Object value = _sessionData.getAttribute(iter.next());
            if (value instanceof HttpSessionActivationListener)
            {
                HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                listener.sessionDidActivate(event);
            }
        }
    }


    /* ------------------------------------------------------------- */
    /**
     * Call the passivation listeners. This must be called holding the
     * lock
     */
    public void willPassivate()
    {
        HttpSessionEvent event = new HttpSessionEvent(this);
        for (Iterator<String> iter = _sessionData.getKeys().iterator(); iter.hasNext();)
        {
            Object value = _sessionData.getAttribute(iter.next());
            if (value instanceof HttpSessionActivationListener)
            {
                HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                listener.sessionWillPassivate(event);
            }
        }
    }

    /* ------------------------------------------------------------ */
    public boolean isValid()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _state==State.VALID;
        }
    }


    /* ------------------------------------------------------------- */
    public long getCookieSetTime()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _sessionData.getCookieSet();
        }
    }


    /* ------------------------------------------------------------- */
    @Override
    public long getCreationTime() throws IllegalStateException
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForRead();
            return _sessionData.getCreated();
        }
    }



    /** 
     * @see javax.servlet.http.HttpSession#getId()
     */
    @Override
    public String getId()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _sessionData.getId();
        }
    }

    
    public String getExtendedId()
    {
        return _extendedId;
    }
    
    public String getContextPath()
    {
        return _sessionData.getContextPath();
    }

    
    public String getVHost ()
    {
        return _sessionData.getVhost();
    }
    

    /** 
     * @see javax.servlet.http.HttpSession#getLastAccessedTime()
     */
    @Override
    public long getLastAccessedTime()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _sessionData.getLastAccessed();
        }
    }

    /** 
     * @see javax.servlet.http.HttpSession#getServletContext()
     */
    @Override
    public ServletContext getServletContext()
    {
        if (_handler == null)
            throw new IllegalStateException ("No session manager for session "+ _sessionData.getId());
       return _handler._context;
    }

    /** 
     * @see javax.servlet.http.HttpSession#setMaxInactiveInterval(int)
     */
    @Override
    public void setMaxInactiveInterval(int secs)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            _sessionData.setMaxInactiveMs((long)secs*1000L);  
            _sessionData.calcAndSetExpiry();
            _sessionData.setDirty(true);
            updateInactivityTimer();
            if (LOG.isDebugEnabled())
            {
                if (secs <= 0)
                    LOG.debug("Session {} is now immortal (maxInactiveInterval={})", _sessionData.getId(), secs);
                else
                    LOG.debug("Session {} maxInactiveInterval={}", _sessionData.getId(), secs);
            }
        }
    }
    
    
    /**
     * Set the inactivity timer to the smaller of the session maxInactivity
     * (ie session-timeout from web.xml), or the inactive eviction time.
     */
    public void updateInactivityTimer ()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            if (LOG.isDebugEnabled())LOG.debug("updateInactivityTimer");

            long maxInactive =  _sessionData.getMaxInactiveMs();        
            int evictionPolicy = getSessionHandler().getSessionCache().getEvictionPolicy();

            if (maxInactive <= 0)
            {
                //sessions are immortal, they never expire
                if (evictionPolicy < SessionCache.EVICT_ON_INACTIVITY)
                {
                    //we do not want to evict inactive sessions
                    setInactivityTimer(-1L);
                    if (LOG.isDebugEnabled()) LOG.debug("Session is immortal && no inactivity eviction: timer cancelled");
                }
                else
                {
                    //sessions are immortal but we want to evict after inactivity
                    setInactivityTimer(TimeUnit.SECONDS.toMillis(evictionPolicy));
                    if (LOG.isDebugEnabled()) LOG.debug("Session is immortal; evict after {} sec inactivity", evictionPolicy);
                }                    
            }
            else
            {
                //sessions are not immortal
                if (evictionPolicy < SessionCache.EVICT_ON_INACTIVITY)
                {
                    //don't want to evict inactive sessions, set the timer for the session's maxInactive setting
                    setInactivityTimer(_sessionData.getMaxInactiveMs());
                    if (LOG.isDebugEnabled()) LOG.debug("No inactive session eviction");
                }
                else
                {
                    //set the time to the lesser of the session's maxInactive and eviction timeout
                    setInactivityTimer(Math.min(maxInactive, TimeUnit.SECONDS.toMillis(evictionPolicy)));
                    if (LOG.isDebugEnabled()) LOG.debug("Inactivity timer set to lesser of maxInactive={} and inactivityEvict={}", maxInactive, evictionPolicy);
                }
            }
        }
    }
    
    /**
     * Set the inactivity timer
     * 
     * @param ms value in millisec, -1 disables it
     */
    private void setInactivityTimer (long ms)
    {
        if (_sessionInactivityTimer == null)
            _sessionInactivityTimer = new SessionInactivityTimeout();
        _sessionInactivityTimer.setIdleTimeout(ms);
    }


    /**
     * 
     */
    public void stopInactivityTimer ()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            if (_sessionInactivityTimer != null)
            {
                _sessionInactivityTimer.setIdleTimeout(-1);
                _sessionInactivityTimer = null;
                if (LOG.isDebugEnabled()) LOG.debug("Session timer stopped");
            }
        }
    }

    /** 
     * @see javax.servlet.http.HttpSession#getMaxInactiveInterval()
     */
    @Override
    public int getMaxInactiveInterval()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return (int)(_sessionData.getMaxInactiveMs()/1000);
        }
    }

    /** 
     * @see javax.servlet.http.HttpSession#getSessionContext()
     */
    @Override
    @Deprecated
    public HttpSessionContext getSessionContext()
    {
        checkValidForRead();
        return SessionHandler.__nullSessionContext;
    }

    
    public SessionHandler getSessionHandler()
    {
        return _handler;
    }
    
    
    /* ------------------------------------------------------------- */
    /**
     * Check that the session can be modified.
     * 
     * @throws IllegalStateException if the session is invalid
     */
    protected void checkValidForWrite() throws IllegalStateException
    {    
        checkLocked();

        if (_state == State.INVALID)
            throw new IllegalStateException("Not valid for write: id="+_sessionData.getId()+" created="+_sessionData.getCreated()+" accessed="+_sessionData.getAccessed()+" lastaccessed="+_sessionData.getLastAccessed()+" maxInactiveMs="+_sessionData.getMaxInactiveMs()+" expiry="+_sessionData.getExpiry());
        
        if (_state == State.INVALIDATING)
            return;  //in the process of being invalidated, listeners may try to remove attributes
        
        if (!isResident())
            throw new IllegalStateException("Not valid for write: id="+_sessionData.getId()+" not resident");
    }
    
    
    /* ------------------------------------------------------------- */
    /**
     * Chech that the session data can be read.
     * 
     * @throws IllegalStateException if the session is invalid
     */
    protected void checkValidForRead () throws IllegalStateException
    {
        checkLocked();
        
        if (_state == State.INVALID)
            throw new IllegalStateException("Invalid for read: id="+_sessionData.getId()+" created="+_sessionData.getCreated()+" accessed="+_sessionData.getAccessed()+" lastaccessed="+_sessionData.getLastAccessed()+" maxInactiveMs="+_sessionData.getMaxInactiveMs()+" expiry="+_sessionData.getExpiry());
        
        if (_state == State.INVALIDATING)
            return;
        
        if (!isResident())
            throw new IllegalStateException("Invalid for read: id="+_sessionData.getId()+" not resident");
    }
    
    
    /* ------------------------------------------------------------- */
    protected void checkLocked ()
    throws IllegalStateException
    {
        if (!_lock.isLocked())
            throw new IllegalStateException("Session not locked");
    }

    /** 
     * @see javax.servlet.http.HttpSession#getAttribute(java.lang.String)
     */
    @Override
    public Object getAttribute(String name)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForRead();
            return _sessionData.getAttribute(name);
        }
    }

    /** 
     * @see javax.servlet.http.HttpSession#getValue(java.lang.String)
     */
    @Override
    @Deprecated
    public Object getValue(String name)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        { 
            return _sessionData.getAttribute(name);
        }
    }

    /** 
     * @see javax.servlet.http.HttpSession#getAttributeNames()
     */
    @Override
    public Enumeration<String> getAttributeNames()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForRead();
            final Iterator<String> itor = _sessionData.getKeys().iterator();
            return new Enumeration<String> ()
            {

                @Override
                public boolean hasMoreElements()
                {
                    return itor.hasNext();
                }

                @Override
                public String nextElement()
                {
                    return itor.next();
                }

            };
        }
    }




    /* ------------------------------------------------------------ */
    public int getAttributes()
    {
        return _sessionData.getKeys().size();
    }




    /* ------------------------------------------------------------ */
    public Set<String> getNames()
    {
        return Collections.unmodifiableSet(_sessionData.getKeys());
    }


    /* ------------------------------------------------------------- */
    /**
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #getAttributeNames}
     */
    @Deprecated
    @Override
    public String[] getValueNames() throws IllegalStateException
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForRead();
            Iterator<String> itor = _sessionData.getKeys().iterator();
            if (!itor.hasNext())
                return new String[0];
            ArrayList<String> names = new ArrayList<String>();
            while (itor.hasNext())
                names.add(itor.next());
            return names.toArray(new String[names.size()]);
        }
    }

    /* ------------------------------------------------------------- */
    /** 
     * @see javax.servlet.http.HttpSession#setAttribute(java.lang.String, java.lang.Object)
     */
    @Override
    public void setAttribute(String name, Object value)
    {
        Object old=null;
        try (Lock lock = _lock.lockIfNotHeld())
        {
            //if session is not valid, don't accept the set
            checkValidForWrite();
            old=_sessionData.setAttribute(name,value);
        }
        if (value == null && old == null)
            return; //if same as remove attribute but attribute was already removed, no change
        callSessionAttributeListeners(name, value, old);
    }
    
    
    
    /* ------------------------------------------------------------- */
    /** 
     * @see javax.servlet.http.HttpSession#putValue(java.lang.String, java.lang.Object)
     */
    @Override
    @Deprecated
    public void putValue(String name, Object value)
    {
        setAttribute(name,value);
    }
    
    
    
    /* ------------------------------------------------------------- */
    /** 
     * @see javax.servlet.http.HttpSession#removeAttribute(java.lang.String)
     */
    @Override
    public void removeAttribute(String name)
    {
       setAttribute(name, null);
    }
    
    
    
    /* ------------------------------------------------------------- */
    /** 
     * @see javax.servlet.http.HttpSession#removeValue(java.lang.String)
     */
    @Override
    @Deprecated
    public void removeValue(String name)
    {
       setAttribute(name, null);
    }

    /* ------------------------------------------------------------ */
    /** Force a change to the id of a session.
     * 
     * @param request the Request associated with the call to change id.
     */
    public void renewId(HttpServletRequest request)
    {
        if (_handler == null)
            throw new IllegalStateException ("No session manager for session "+ _sessionData.getId());
        
        String id = null;
        String extendedId = null;
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForWrite(); //don't renew id on a session that is not valid
            id = _sessionData.getId(); //grab the values as they are now
            extendedId = getExtendedId();
        }
        
        String newId = _handler._sessionIdManager.renewSessionId(id, extendedId, request); 
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForWrite(); 
            _sessionData.setId(newId);
            setExtendedId(_handler._sessionIdManager.getExtendedId(newId, request));
        }
        setIdChanged(true);
    }
       
   
    /* ------------------------------------------------------------- */
    /** Called by users to invalidate a session, or called by the
     * access method as a request enters the session if the session
     * has expired, or called by manager as a result of scavenger
     * expiring session
     * 
     * @see javax.servlet.http.HttpSession#invalidate()
     */
    @Override
    public void invalidate()
    {
        if (_handler == null)
            throw new IllegalStateException ("No session manager for session "+ _sessionData.getId());

        boolean result = beginInvalidate();

        try
        {
            //if the session was not already invalid, or in process of being invalidated, do invalidate
            if (result)
            {
                //tell id mgr to remove session from all contexts
                _handler.getSessionIdManager().invalidateAll(_sessionData.getId());
            }
        }
        catch (Exception e)
        {
            LOG.warn(e);
        }
    }

    /* ------------------------------------------------------------- */
    /** Grab the lock on the session
     * @return the lock
     */
    public Lock lock ()
    {
        return _lock.lock();
    }
    
    
    /* ------------------------------------------------------------- */
    /** Grab the lock on the session if it isn't locked already
     * @return the lock
     */
    public Lock lockIfNotHeld ()
    {
        return _lock.lockIfNotHeld();
    }
    
    /* ------------------------------------------------------------- */
    /**
     * @return true if the session is not already invalid or being invalidated.
     */
    protected boolean beginInvalidate()
    {
        boolean result = false;
        
        try (Lock lock = _lock.lockIfNotHeld())
        {
            switch (_state)
            {
                case INVALID:
                {
                    throw new IllegalStateException(); //spec does not allow invalidate of already invalid session
                }
                case VALID:
                {
                    //only first change from valid to invalidating should be actionable
                    result = true;
                    _state = State.INVALIDATING;
                    break;
                }
                default:
                {
                    LOG.info("Session {} already being invalidated", _sessionData.getId());
                }
            }
        }
        
        return result;
    }

    /* ------------------------------------------------------------- */
    /** Call HttpSessionAttributeListeners as part of invalidating
     * a Session.
     * 
     * @throws IllegalStateException
     */
    @Deprecated 
    protected void doInvalidate() throws IllegalStateException
    {
       finishInvalidate();
    }
    
    
    /* ------------------------------------------------------------- */
    /** Call HttpSessionAttributeListeners as part of invalidating
     * a Session.
     * 
     * @throws IllegalStateException
     */
    protected void finishInvalidate() throws IllegalStateException
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            try
            {
                if (LOG.isDebugEnabled())
                    LOG.debug("invalidate {}",_sessionData.getId());
                if (_state == State.VALID || _state == State.INVALIDATING)
                {
                    Set<String> keys = null;
                    do
                    {
                        keys = _sessionData.getKeys();
                        for (String key:keys)
                        {
                            Object  old=_sessionData.setAttribute(key,null);
                            if (old == null)
                                return; //if same as remove attribute but attribute was already removed, no change
                            callSessionAttributeListeners(key, null, old);
                        }

                    }
                    while (!keys.isEmpty());
                }
            }
            finally
            {
                // mark as invalid
                _state = State.INVALID;
            }
        }
    }

    /* ------------------------------------------------------------- */
    @Override
    public boolean isNew() throws IllegalStateException
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            checkValidForRead();
            return _newSession;
        }
    }
    
    

    /* ------------------------------------------------------------- */
    public void setIdChanged(boolean changed)
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            _idChanged=changed;
        }
    }
    
    
    /* ------------------------------------------------------------- */
    public boolean isIdChanged ()
    {
        try (Lock lock = _lock.lockIfNotHeld())
        {
            return _idChanged;
        }
    }
    
    
    /* ------------------------------------------------------------- */
    @Override
    public Session getSession()
    {
        // TODO why is this used
        return this;
    }
  
    /* ------------------------------------------------------------- */
    protected SessionData getSessionData()
    {
        return _sessionData;
    }

    /* ------------------------------------------------------------- */
    public void setResident (boolean resident)
    {
        _resident = resident;
    }
    
    /* ------------------------------------------------------------- */
    public boolean isResident ()
    {
        return _resident;
    }
    
}
