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