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