View Javadoc

    1   // ========================================================================
    2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.servlet;
    16
    17  import java.io.DataInputStream;
    18  import java.io.DataOutputStream;
    19  import java.io.File;
    20  import java.io.FileInputStream;
    21  import java.io.FileOutputStream;
    22  import java.io.IOException;
    23  import java.io.ObjectInputStream;
    24  import java.io.ObjectOutputStream;
    25  import java.util.ArrayList;
    26  import java.util.Collections;
    27  import java.util.HashMap;
    28  import java.util.Iterator;
    29  import java.util.Map;
    30  import java.util.Timer;
    31  import java.util.TimerTask;
    32
    33  import javax.servlet.http.HttpServletRequest;
    34
    35  import org.mortbay.log.Log;
    36  import org.mortbay.util.LazyList;
    37
    38
    39  /* ------------------------------------------------------------ */
    40  /** An in-memory implementation of SessionManager.
    41   *
    42   * @author Greg Wilkins (gregw)
    43   */
    44  public class HashSessionManager extends AbstractSessionManager
    45  {
    46      private Timer _timer;
    47      private TimerTask _task;
    48      private int _scavengePeriodMs=30000;
    49      private int _savePeriodMs=0; //don't do period saves by default
    50      private TimerTask _saveTask;
    51      protected Map _sessions;
    52      private File _storeDir;
    53      private boolean _lazyLoad=false;
    54      private boolean _sessionsLoaded=false;
    55
    56      /* ------------------------------------------------------------ */
    57      public HashSessionManager()
    58      {
    59          super();
    60      }
    61
    62      /* ------------------------------------------------------------ */
    63      /* (non-Javadoc)
    64       * @see org.mortbay.jetty.servlet.AbstractSessionManager#doStart()
    65       */
    66      public void doStart() throws Exception
    67      {
    68          _sessions=new HashMap();
    69          super.doStart();
    70
    71          _timer=new Timer(true);
    72
    73          setScavengePeriod(getScavengePeriod());
    74
    75          if (_storeDir!=null)
    76          {
    77              if (!_storeDir.exists())
    78                  _storeDir.mkdir();
    79
    80              if (!_lazyLoad)
    81                  restoreSessions();
    82          }
    83
    84          setSavePeriod(getSavePeriod());
    85      }
    86
    87      /* ------------------------------------------------------------ */
    88      /* (non-Javadoc)
    89       * @see org.mortbay.jetty.servlet.AbstractSessionManager#doStop()
    90       */
    91      public void doStop() throws Exception
    92      {
    93
    94          if (_storeDir != null)
    95              saveSessions();
    96
    97          super.doStop();
    98
    99          _sessions.clear();
    100         _sessions=null;
    101
    102         // stop the scavenger
    103         synchronized(this)
    104         {
    105             if (_saveTask!=null)
    106                 _saveTask.cancel();
    107             if (_task!=null)
    108                 _task.cancel();
    109             if (_timer!=null)
    110                 _timer.cancel();
    111             _timer=null;
    112         }
    113     }
    114
    115     /* ------------------------------------------------------------ */
    116     /**
    117      * @return seconds
    118      */
    119     public int getScavengePeriod()
    120     {
    121         return _scavengePeriodMs/1000;
    122     }
    123
    124
    125     /* ------------------------------------------------------------ */
    126     public Map getSessionMap()
    127     {
    128         return Collections.unmodifiableMap(_sessions);
    129     }
    130
    131
    132     /* ------------------------------------------------------------ */
    133     public int getSessions()
    134     {
    135         return _sessions.size();
    136     }
    137
    138
    139     /* ------------------------------------------------------------ */
    140     public void setMaxInactiveInterval(int seconds)
    141     {
    142         super.setMaxInactiveInterval(seconds);
    143         if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000)
    144             setScavengePeriod((_dftMaxIdleSecs+9)/10);
    145     }
    146
    147     /* ------------------------------------------------------------ */
    148     public void setSavePeriod (int seconds)
    149     {
    150         int oldSavePeriod = _savePeriodMs;
    151         int period = (seconds * 1000);
    152         if (period < 0)
    153             period=0;
    154         _savePeriodMs=period;
    155
    156         if (_timer!=null)
    157         {
    158             synchronized (this)
    159             {
    160                 if (_saveTask!=null)
    161                     _saveTask.cancel();
    162                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
    163                 {
    164                     _saveTask = new TimerTask()
    165                     {
    166                         public void run()
    167                         {
    168                             try
    169                             {
    170                                 saveSessions();
    171                             }
    172                             catch (Exception e)
    173                             {
    174                                 Log.warn(e);
    175                             }
    176                         }
    177                     };
    178                     _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
    179                 }
    180             }
    181         }
    182     }
    183
    184     /* ------------------------------------------------------------ */
    185     public int getSavePeriod ()
    186     {
    187         if (_savePeriodMs<=0)
    188             return 0;
    189
    190         return _savePeriodMs/1000;
    191     }
    192     /* ------------------------------------------------------------ */
    193     /**
    194      * @param seconds
    195      */
    196     public void setScavengePeriod(int seconds)
    197     {
    198         if (seconds==0)
    199             seconds=60;
    200
    201         int old_period=_scavengePeriodMs;
    202         int period=seconds*1000;
    203         if (period>60000)
    204             period=60000;
    205         if (period<1000)
    206             period=1000;
    207
    208         _scavengePeriodMs=period;
    209         if (_timer!=null && (period!=old_period || _task==null))
    210         {
    211             synchronized (this)
    212             {
    213                 if (_task!=null)
    214                     _task.cancel();
    215                 _task = new TimerTask()
    216                 {
    217                     public void run()
    218                     {
    219                         scavenge();
    220                     }
    221                 };
    222                 _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
    223             }
    224         }
    225     }
    226
    227     /* -------------------------------------------------------------- */
    228     /**
    229      * Find sessions that have timed out and invalidate them. This runs in the
    230      * SessionScavenger thread.
    231      */
    232     private void scavenge()
    233     {
    234         //don't attempt to scavenge if we are shutting down
    235         if (isStopping() || isStopped())
    236             return;
    237
    238         Thread thread=Thread.currentThread();
    239         ClassLoader old_loader=thread.getContextClassLoader();
    240         try
    241         {
    242             if (_loader!=null)
    243                 thread.setContextClassLoader(_loader);
    244
    245             long now=System.currentTimeMillis();
    246
    247             try
    248             {
    249                 if (!_sessionsLoaded && _lazyLoad)
    250                     restoreSessions();
    251             }
    252             catch(Exception e)
    253             {
    254                 Log.debug(e);
    255             }
    256
    257             // Since Hashtable enumeration is not safe over deletes,
    258             // we build a list of stale sessions, then go back and invalidate
    259             // them
    260             Object stale=null;
    261
    262             synchronized (HashSessionManager.this)
    263             {
    264                 // For each session
    265                 for (Iterator i=_sessions.values().iterator(); i.hasNext();)
    266                 {
    267                     Session session=(Session)i.next();
    268                     long idleTime=session._maxIdleMs;
    269                     if (idleTime>0&&session._accessed+idleTime<now)
    270                     {
    271                         // Found a stale session, add it to the list
    272                         stale=LazyList.add(stale,session);
    273                     }
    274                 }
    275             }
    276
    277             // Remove the stale sessions
    278             for (int i=LazyList.size(stale); i-->0;)
    279             {
    280                 // check it has not been accessed in the meantime
    281                 Session session=(Session)LazyList.get(stale,i);
    282                 long idleTime=session._maxIdleMs;
    283                 if (idleTime>0&&session._accessed+idleTime<System.currentTimeMillis())
    284                 {
    285                     ((Session)session).timeout();
    286                     int nbsess=this._sessions.size();
    287                     if (nbsess<this._minSessions)
    288                         this._minSessions=nbsess;
    289                 }
    290             }
    291         }
    292         catch (Throwable t)
    293         {
    294             if (t instanceof ThreadDeath)
    295                 throw ((ThreadDeath)t);
    296             else
    297                 Log.warn("Problem scavenging sessions", t);
    298         }
    299         finally
    300         {
    301             thread.setContextClassLoader(old_loader);
    302         }
    303     }
    304
    305     /* ------------------------------------------------------------ */
    306     protected void addSession(AbstractSessionManager.Session session)
    307     {
    308         _sessions.put(session.getClusterId(),session);
    309     }
    310
    311     /* ------------------------------------------------------------ */
    312     public AbstractSessionManager.Session getSession(String idInCluster)
    313     {
    314         try
    315         {
    316             if (!_sessionsLoaded && _lazyLoad)
    317                 restoreSessions();
    318         }
    319         catch(Exception e)
    320         {
    321             Log.warn(e);
    322         }
    323
    324         if (_sessions==null)
    325             return null;
    326
    327         return (Session)_sessions.get(idInCluster);
    328     }
    329
    330     /* ------------------------------------------------------------ */
    331     protected void invalidateSessions()
    332     {
    333         // Invalidate all sessions to cause unbind events
    334         ArrayList sessions=new ArrayList(_sessions.values());
    335         for (Iterator i=sessions.iterator(); i.hasNext();)
    336         {
    337             Session session=(Session)i.next();
    338             session.invalidate();
    339         }
    340         _sessions.clear();
    341
    342     }
    343
    344     /* ------------------------------------------------------------ */
    345     protected AbstractSessionManager.Session newSession(HttpServletRequest request)
    346     {
    347         return new Session(request);
    348     }
    349
    350     /* ------------------------------------------------------------ */
    351     protected void removeSession(String clusterId)
    352     {
    353         _sessions.remove(clusterId);
    354     }
    355
    356     /* ------------------------------------------------------------ */
    357     public void setStoreDirectory (File dir)
    358     {
    359         _storeDir=dir;
    360     }
    361
    362     /* ------------------------------------------------------------ */
    363     public File getStoreDirectory ()
    364     {
    365         return _storeDir;
    366     }
    367
    368     /* ------------------------------------------------------------ */
    369     public void setLazyLoad(boolean lazyLoad)
    370     {
    371         _lazyLoad = lazyLoad;
    372     }
    373
    374     /* ------------------------------------------------------------ */
    375     public boolean isLazyLoad()
    376     {
    377         return _lazyLoad;
    378     }
    379
    380     /* ------------------------------------------------------------ */
    381     public void restoreSessions () throws Exception
    382     {
    383         if (_storeDir==null || !_storeDir.exists())
    384         {
    385             return;
    386         }
    387
    388         if (!_storeDir.canRead())
    389         {
    390             Log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
    391             return;
    392         }
    393
    394         File[] files = _storeDir.listFiles();
    395         for (int i=0;files!=null&&i<files.length;i++)
    396         {
    397             try
    398             {
    399                 FileInputStream in = new FileInputStream(files[i]);
    400                 Session session = restoreSession(in);
    401                 in.close();
    402                 addSession(session, false);
    403                 files[i].delete();
    404             }
    405             catch (Exception e)
    406             {
    407                 Log.warn("Problem restoring session "+files[i].getName(), e);
    408             }
    409         }
    410
    411         _sessionsLoaded = true;
    412     }
    413
    414     /* ------------------------------------------------------------ */
    415     public void saveSessions () throws Exception
    416     {
    417         if (_storeDir==null || !_storeDir.exists())
    418         {
    419             return;
    420         }
    421
    422         if (!_storeDir.canWrite())
    423         {
    424             Log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
    425             return;
    426         }
    427
    428         synchronized (this)
    429         {
    430             Iterator itor = _sessions.entrySet().iterator();
    431             while (itor.hasNext())
    432             {
    433                 Map.Entry entry = (Map.Entry)itor.next();
    434                 String id = (String)entry.getKey();
    435                 Session session = (Session)entry.getValue();
    436                 try
    437                 {
    438                     File file = new File (_storeDir, id);
    439                     if (file.exists())
    440                         file.delete();
    441                     file.createNewFile();
    442                     FileOutputStream fos = new FileOutputStream (file);
    443                     session.save(fos);
    444                     fos.close();
    445                 }
    446                 catch (Exception e)
    447                 {
    448                     Log.warn("Problem persisting session "+id, e);
    449                 }
    450             }
    451         }
    452     }
    453
    454     /* ------------------------------------------------------------ */
    455     public Session restoreSession (FileInputStream fis)
    456     throws Exception
    457     {
    458
    459         /*
    460          * Take care of this class's fields first by calling
    461          * defaultReadObject
    462          */
    463
    464         DataInputStream in = new DataInputStream(fis);
    465         String clusterId = in.readUTF();
    466         String nodeId = in.readUTF();
    467         boolean idChanged = in.readBoolean();
    468         long created = in.readLong();
    469         long cookieSet = in.readLong();
    470         long accessed = in.readLong();
    471         long lastAccessed = in.readLong();
    472         //boolean invalid = in.readBoolean();
    473         //boolean invalidate = in.readBoolean();
    474         //long maxIdle = in.readLong();
    475         //boolean isNew = in.readBoolean();
    476         int requests = in.readInt();
    477
    478         Session session = new Session (created, clusterId);
    479         session._cookieSet = cookieSet;
    480         session._lastAccessed = lastAccessed;
    481
    482         int size = in.readInt();
    483         if (size > 0)
    484         {
    485             ArrayList keys = new ArrayList();
    486             for (int i=0; i<size; i++)
    487             {
    488                 String key = in.readUTF();
    489                 keys.add(key);
    490             }
    491             ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
    492             for (int i=0;i<size;i++)
    493             {
    494                 Object value = ois.readObject();
    495                 session.setAttribute((String)keys.get(i),value);
    496             }
    497             ois.close();
    498         }
    499         else
    500             session.initValues();
    501         in.close();
    502         return session;
    503     }
    504
    505
    506     /* ------------------------------------------------------------ */
    507     /* ------------------------------------------------------------ */
    508     /* ------------------------------------------------------------ */
    509     protected class Session extends AbstractSessionManager.Session
    510     {
    511         /* ------------------------------------------------------------ */
    512         private static final long serialVersionUID=-2134521374206116367L;
    513
    514
    515
    516         /* ------------------------------------------------------------- */
    517         protected Session(HttpServletRequest request)
    518         {
    519             super(request);
    520         }
    521
    522         protected Session(long created, String clusterId)
    523         {
    524             super(created, clusterId);
    525         }
    526         /* ------------------------------------------------------------- */
    527         public void setMaxInactiveInterval(int secs)
    528         {
    529             super.setMaxInactiveInterval(secs);
    530             if (_maxIdleMs>0&&(_maxIdleMs/10)<_scavengePeriodMs)
    531                 HashSessionManager.this.setScavengePeriod((secs+9)/10);
    532         }
    533
    534         /* ------------------------------------------------------------ */
    535         protected Map newAttributeMap()
    536         {
    537             return new HashMap(3);
    538         }
    539
    540
    541         public void invalidate ()
    542         throws IllegalStateException
    543         {
    544             super.invalidate();
    545             remove(getId());
    546         }
    547
    548         public void remove (String id)
    549         {
    550             if (id==null)
    551                 return;
    552
    553             //all sessions are invalidated when jetty is stopped, make sure we don't
    554             //remove all the sessions in this case
    555             if (isStopping() || isStopped())
    556                 return;
    557
    558             if (_storeDir==null || !_storeDir.exists())
    559             {
    560                 return;
    561             }
    562
    563             File f = new File(_storeDir, id);
    564             f.delete();
    565         }
    566
    567         public void save(FileOutputStream fos)  throws IOException
    568         {
    569             DataOutputStream out = new DataOutputStream(fos);
    570             out.writeUTF(_clusterId);
    571             out.writeUTF(_nodeId);
    572             out.writeBoolean(_idChanged);
    573             out.writeLong( _created);
    574             out.writeLong(_cookieSet);
    575             out.writeLong(_accessed);
    576             out.writeLong(_lastAccessed);
    577             /* Don't write these out, as they don't make sense to store because they
    578              * either they cannot be true or their value will be restored in the
    579              * Session constructor.
    580              */
    581             //out.writeBoolean(_invalid);
    582             //out.writeBoolean(_doInvalidate);
    583             //out.writeLong(_maxIdleMs);
    584             //out.writeBoolean( _newSession);
    585             out.writeInt(_requests);
    586             if (_values != null)
    587             {
    588                 out.writeInt(_values.size());
    589                 Iterator itor = _values.keySet().iterator();
    590                 while (itor.hasNext())
    591                 {
    592                     String key = (String)itor.next();
    593                     out.writeUTF(key);
    594                 }
    595                 itor = _values.values().iterator();
    596                 ObjectOutputStream oos = new ObjectOutputStream(out);
    597                 while (itor.hasNext())
    598                 {
    599                     oos.writeObject(itor.next());
    600                 }
    601                 oos.close();
    602             }
    603             else
    604                 out.writeInt(0);
    605             out.close();
    606         }
    607
    608     }
    609
    610     protected class ClassLoadingObjectInputStream extends ObjectInputStream
    611     {
    612         public ClassLoadingObjectInputStream(java.io.InputStream in)
    613         throws IOException
    614         {
    615             super(in);
    616         }
    617
    618         public ClassLoadingObjectInputStream ()
    619         throws IOException
    620         {
    621             super();
    622         }
    623
    624         public Class resolveClass (java.io.ObjectStreamClass cl)
    625         throws IOException, ClassNotFoundException
    626         {
    627             Class clazz;
    628             try
    629             {
    630                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
    631             }
    632             catch (ClassNotFoundException e)
    633             {
    634                 return super.resolveClass(cl);
    635             }
    636         }
    637     }
    638
    639
    640 }