1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.session;
18
19 import java.io.BufferedInputStream;
20 import java.io.BufferedOutputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.security.AccessController;
29 import java.security.PrivilegedActionException;
30 import java.security.PrivilegedExceptionAction;
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import javax.servlet.ServletContext;
35
36 import org.apache.catalina.Context;
37 import org.apache.catalina.LifecycleException;
38 import org.apache.catalina.LifecycleState;
39 import org.apache.catalina.Loader;
40 import org.apache.catalina.Session;
41 import org.apache.catalina.security.SecurityUtil;
42 import org.apache.catalina.util.CustomObjectInputStream;
43 import org.apache.juli.logging.Log;
44 import org.apache.juli.logging.LogFactory;
45 import org.apache.tomcat.util.ExceptionUtils;
46
47 /**
48  * Standard implementation of the <b>Manager</b> interface that provides
49  * simple session persistence across restarts of this component (such as
50  * when the entire server is shut down and restarted, or when a particular
51  * web application is reloaded.
52  * <p>
53  * <b>IMPLEMENTATION NOTE</b>:  Correct behavior of session storing and
54  * reloading depends upon external calls to the <code>start()</code> and
55  * <code>stop()</code> methods of this class at the correct times.
56  *
57  * @author Craig R. McClanahan
58  */

59 public class StandardManager extends ManagerBase {
60
61     private final Log log = LogFactory.getLog(StandardManager.class); // must not be static
62
63     // ---------------------------------------------------- Security Classes
64
65     private class PrivilegedDoLoad
66         implements PrivilegedExceptionAction<Void> {
67
68         PrivilegedDoLoad() {
69             // NOOP
70         }
71
72         @Override
73         public Void run() throws Exception{
74            doLoad();
75            return null;
76         }
77     }
78
79     private class PrivilegedDoUnload
80         implements PrivilegedExceptionAction<Void> {
81
82         PrivilegedDoUnload() {
83             // NOOP
84         }
85
86         @Override
87         public Void run() throws Exception{
88             doUnload();
89             return null;
90         }
91
92     }
93
94
95     // ----------------------------------------------------- Instance Variables
96
97     /**
98      * The descriptive name of this Manager implementation (for logging).
99      */

100     protected static final String name = "StandardManager";
101
102
103     /**
104      * Path name of the disk file in which active sessions are saved
105      * when we stop, and from which these sessions are loaded when we start.
106      * A <code>null</code> value indicates that no persistence is desired.
107      * If this pathname is relative, it will be resolved against the
108      * temporary working directory provided by our context, available via
109      * the <code>javax.servlet.context.tempdir</code> context attribute.
110      */

111     protected String pathname = "SESSIONS.ser";
112
113
114     // ------------------------------------------------------------- Properties
115
116     @Override
117     public String getName() {
118         return name;
119     }
120
121
122     /**
123      * @return The session persistence pathname, if any.
124      */

125     public String getPathname() {
126         return pathname;
127     }
128
129
130     /**
131      * Set the session persistence pathname to the specified value.  If no
132      * persistence support is desired, set the pathname to <code>null</code>.
133      *
134      * @param pathname New session persistence pathname
135      */

136     public void setPathname(String pathname) {
137         String oldPathname = this.pathname;
138         this.pathname = pathname;
139         support.firePropertyChange("pathname", oldPathname, this.pathname);
140     }
141
142
143     // --------------------------------------------------------- Public Methods
144
145     @Override
146     public void load() throws ClassNotFoundException, IOException {
147         if (SecurityUtil.isPackageProtectionEnabled()){
148             try{
149                 AccessController.doPrivileged( new PrivilegedDoLoad() );
150             } catch (PrivilegedActionException ex){
151                 Exception exception = ex.getException();
152                 if (exception instanceof ClassNotFoundException) {
153                     throw (ClassNotFoundException)exception;
154                 } else if (exception instanceof IOException) {
155                     throw (IOException)exception;
156                 }
157                 if (log.isDebugEnabled()) {
158                     log.debug("Unreported exception in load() ", exception);
159                 }
160             }
161         } else {
162             doLoad();
163         }
164     }
165
166
167     /**
168      * Load any currently active sessions that were previously unloaded
169      * to the appropriate persistence mechanism, if any.  If persistence is not
170      * supported, this method returns without doing anything.
171      *
172      * @exception ClassNotFoundException if a serialized class cannot be
173      *  found during the reload
174      * @exception IOException if an input/output error occurs
175      */

176     protected void doLoad() throws ClassNotFoundException, IOException {
177         if (log.isDebugEnabled()) {
178             log.debug("Start: Loading persisted sessions");
179         }
180
181         // Initialize our internal data structures
182         sessions.clear();
183
184         // Open an input stream to the specified pathname, if any
185         File file = file();
186         if (file == null) {
187             return;
188         }
189         if (log.isDebugEnabled()) {
190             log.debug(sm.getString("standardManager.loading", pathname));
191         }
192         Loader loader = null;
193         ClassLoader classLoader = null;
194         Log logger = null;
195         try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
196                 BufferedInputStream bis = new BufferedInputStream(fis)) {
197             Context c = getContext();
198             loader = c.getLoader();
199             logger = c.getLogger();
200             if (loader != null) {
201                 classLoader = loader.getClassLoader();
202             }
203             if (classLoader == null) {
204                 classLoader = getClass().getClassLoader();
205             }
206
207             // Load the previously unloaded active sessions
208             synchronized (sessions) {
209                 try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
210                         getSessionAttributeValueClassNamePattern(),
211                         getWarnOnSessionAttributeFilterFailure())) {
212                     Integer count = (Integer) ois.readObject();
213                     int n = count.intValue();
214                     if (log.isDebugEnabled())
215                         log.debug("Loading " + n + " persisted sessions");
216                     for (int i = 0; i < n; i++) {
217                         StandardSession session = getNewSession();
218                         session.readObjectData(ois);
219                         session.setManager(this);
220                         sessions.put(session.getIdInternal(), session);
221                         session.activate();
222                         if (!session.isValidInternal()) {
223                             // If session is already invalid,
224                             // expire session to prevent memory leak.
225                             session.setValid(true);
226                             session.expire();
227                         }
228                         sessionCounter++;
229                     }
230                 } finally {
231                     // Delete the persistent storage file
232                     if (file.exists()) {
233                         if (!file.delete()) {
234                             log.warn(sm.getString("standardManager.deletePersistedFileFail", file));
235                         }
236                     }
237                 }
238             }
239         } catch (FileNotFoundException e) {
240             if (log.isDebugEnabled()) {
241                 log.debug("No persisted data file found");
242             }
243             return;
244         }
245
246         if (log.isDebugEnabled()) {
247             log.debug("Finish: Loading persisted sessions");
248         }
249     }
250
251
252     @Override
253     public void unload() throws IOException {
254         if (SecurityUtil.isPackageProtectionEnabled()) {
255             try {
256                 AccessController.doPrivileged(new PrivilegedDoUnload());
257             } catch (PrivilegedActionException ex){
258                 Exception exception = ex.getException();
259                 if (exception instanceof IOException) {
260                     throw (IOException)exception;
261                 }
262                 if (log.isDebugEnabled()) {
263                     log.debug("Unreported exception in unLoad()", exception);
264                 }
265             }
266         } else {
267             doUnload();
268         }
269     }
270
271
272     /**
273      * Save any currently active sessions in the appropriate persistence
274      * mechanism, if any.  If persistence is not supported, this method
275      * returns without doing anything.
276      *
277      * @exception IOException if an input/output error occurs
278      */

279     protected void doUnload() throws IOException {
280
281         if (log.isDebugEnabled())
282             log.debug(sm.getString("standardManager.unloading.debug"));
283
284         if (sessions.isEmpty()) {
285             log.debug(sm.getString("standardManager.unloading.nosessions"));
286             return// nothing to do
287         }
288
289         // Open an output stream to the specified pathname, if any
290         File file = file();
291         if (file == null) {
292             return;
293         }
294         if (log.isDebugEnabled()) {
295             log.debug(sm.getString("standardManager.unloading", pathname));
296         }
297
298         // Keep a note of sessions that are expired
299         List<StandardSession> list = new ArrayList<>();
300
301         try (FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
302                 BufferedOutputStream bos = new BufferedOutputStream(fos);
303                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
304
305             synchronized (sessions) {
306                 if (log.isDebugEnabled()) {
307                     log.debug("Unloading " + sessions.size() + " sessions");
308                 }
309                 // Write the number of active sessions, followed by the details
310                 oos.writeObject(Integer.valueOf(sessions.size()));
311                 for (Session s : sessions.values()) {
312                     StandardSession session = (StandardSession) s;
313                     list.add(session);
314                     session.passivate();
315                     session.writeObjectData(oos);
316                 }
317             }
318         }
319
320         // Expire all the sessions we just wrote
321         if (log.isDebugEnabled()) {
322             log.debug("Expiring " + list.size() + " persisted sessions");
323         }
324         for (StandardSession session : list) {
325             try {
326                 session.expire(false);
327             } catch (Throwable t) {
328                 ExceptionUtils.handleThrowable(t);
329             } finally {
330                 session.recycle();
331             }
332         }
333
334         if (log.isDebugEnabled()) {
335             log.debug("Unloading complete");
336         }
337     }
338
339
340     /**
341      * Start this component and implement the requirements
342      * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
343      *
344      * @exception LifecycleException if this component detects a fatal error
345      *  that prevents this component from being used
346      */

347     @Override
348     protected synchronized void startInternal() throws LifecycleException {
349
350         super.startInternal();
351
352         // Load unloaded sessions, if any
353         try {
354             load();
355         } catch (Throwable t) {
356             ExceptionUtils.handleThrowable(t);
357             log.error(sm.getString("standardManager.managerLoad"), t);
358         }
359
360         setState(LifecycleState.STARTING);
361     }
362
363
364     /**
365      * Stop this component and implement the requirements
366      * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
367      *
368      * @exception LifecycleException if this component detects a fatal error
369      *  that prevents this component from being used
370      */

371     @Override
372     protected synchronized void stopInternal() throws LifecycleException {
373
374         if (log.isDebugEnabled()) {
375             log.debug("Stopping");
376         }
377
378         setState(LifecycleState.STOPPING);
379
380         // Write out sessions
381         try {
382             unload();
383         } catch (Throwable t) {
384             ExceptionUtils.handleThrowable(t);
385             log.error(sm.getString("standardManager.managerUnload"), t);
386         }
387
388         // Expire all active sessions
389         Session sessions[] = findSessions();
390         for (int i = 0; i < sessions.length; i++) {
391             Session session = sessions[i];
392             try {
393                 if (session.isValid()) {
394                     session.expire();
395                 }
396             } catch (Throwable t) {
397                 ExceptionUtils.handleThrowable(t);
398             } finally {
399                 // Measure against memory leaking if references to the session
400                 // object are kept in a shared field somewhere
401                 session.recycle();
402             }
403         }
404
405         // Require a new random number generator if we are restarted
406         super.stopInternal();
407     }
408
409
410     // ------------------------------------------------------ Protected Methods
411
412     /**
413      * Return a File object representing the pathname to our
414      * persistence file, if any.
415      * @return the file
416      */

417     protected File file() {
418         if (pathname == null || pathname.length() == 0) {
419             return null;
420         }
421         File file = new File(pathname);
422         if (!file.isAbsolute()) {
423             Context context = getContext();
424             ServletContext servletContext = context.getServletContext();
425             File tempdir = (File) servletContext.getAttribute(ServletContext.TEMPDIR);
426             if (tempdir != null) {
427                 file = new File(tempdir, pathname);
428             }
429         }
430         return file;
431     }
432 }
433