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.cocoon.servlet;
18
19 import org.apache.avalon.excalibur.logger.Log4JLoggerManager;
20 import org.apache.avalon.excalibur.logger.LogKitLoggerManager;
21 import org.apache.avalon.excalibur.logger.LoggerManager;
22 import org.apache.avalon.framework.activity.Disposable;
23 import org.apache.avalon.framework.activity.Initializable;
24 import org.apache.avalon.framework.component.ComponentManager;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
28 import org.apache.avalon.framework.container.ContainerUtil;
29 import org.apache.avalon.framework.context.Contextualizable;
30 import org.apache.avalon.framework.context.DefaultContext;
31 import org.apache.avalon.framework.logger.LogEnabled;
32 import org.apache.avalon.framework.logger.LogKitLogger;
33 import org.apache.avalon.framework.logger.Logger;
34
35 import org.apache.cocoon.Cocoon;
36 import org.apache.cocoon.ConnectionResetException;
37 import org.apache.cocoon.Constants;
38 import org.apache.cocoon.ResourceNotFoundException;
39 import org.apache.cocoon.components.notification.DefaultNotifyingBuilder;
40 import org.apache.cocoon.components.notification.Notifier;
41 import org.apache.cocoon.components.notification.Notifying;
42 import org.apache.cocoon.environment.Environment;
43 import org.apache.cocoon.environment.http.HttpContext;
44 import org.apache.cocoon.environment.http.HttpEnvironment;
45 import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest;
46 import org.apache.cocoon.servlet.multipart.RequestFactory;
47 import org.apache.cocoon.util.ClassUtils;
48 import org.apache.cocoon.util.Deprecation;
49 import org.apache.cocoon.util.IOUtils;
50 import org.apache.cocoon.util.StringUtils;
51 import org.apache.cocoon.util.log.CocoonLogFormatter;
52 import org.apache.cocoon.util.log.Log4JConfigurator;
53
54 import org.apache.commons.lang.BooleanUtils;
55 import org.apache.commons.lang.SystemUtils;
56 import org.apache.commons.lang.time.StopWatch;
57 import org.apache.excalibur.instrument.InstrumentManager;
58 import org.apache.excalibur.instrument.manager.impl.DefaultInstrumentManagerImpl;
59 import org.apache.log.ContextMap;
60 import org.apache.log.ErrorHandler;
61 import org.apache.log.Hierarchy;
62 import org.apache.log.Priority;
63 import org.apache.log.output.ServletOutputLogTarget;
64 import org.apache.log.util.DefaultErrorHandler;
65 import org.apache.log4j.LogManager;
66
67 import javax.servlet.ServletConfig;
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.http.HttpServlet;
72 import javax.servlet.http.HttpServletRequest;
73 import javax.servlet.http.HttpServletResponse;
74 import java.io.File;
75 import java.io.FileInputStream;
76 import java.io.FileOutputStream;
77 import java.io.IOException;
78 import java.io.InputStream;
79 import java.io.OutputStream;
80 import java.lang.reflect.Constructor;
81 import java.net.MalformedURLException;
82 import java.net.URL;
83 import java.util.ArrayList;
84 import java.util.Arrays;
85 import java.util.HashMap;
86 import java.util.Iterator;
87 import java.util.List;
88 import java.util.StringTokenizer;
89 import java.util.jar.Attributes;
90 import java.util.jar.Manifest;
91
92 /**
93 * This is the entry point for Cocoon execution as an HTTP Servlet.
94 *
95 * @author <a href="mailto:pier@apache.org">Pierpaolo Fumagalli</a>
96 * (Apache Software Foundation)
97 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
98 * @author <a href="mailto:nicolaken@apache.org">Nicola Ken Barozzi</a>
99 * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
100 * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
101 * @author <a href="mailto:leo.sutic@inspireinfrastructure.com">Leo Sutic</a>
102 * @version $Id: CocoonServlet.java 606745 2007-12-24 22:26:08Z solprovider $
103 */
104 public class CocoonServlet extends HttpServlet {
105
106 /**
107 * Application <code>Context</code> Key for the servlet configuration
108 * @since 2.1.3
109 */
110 public static final String CONTEXT_SERVLET_CONFIG = "servlet-config";
111
112 // Processing time message
113 protected static final String PROCESSED_BY = "Processed by "
114 + Constants.COMPLETE_NAME + " in ";
115
116 // Used by "show-time"
117 static final float SECOND = 1000;
118 static final float MINUTE = 60 * SECOND;
119 static final float HOUR = 60 * MINUTE;
120
121 private Logger log;
122 private LoggerManager loggerManager;
123
124 /**
125 * The time the cocoon instance was created
126 */
127 protected long creationTime;
128
129 /**
130 * The <code>Cocoon</code> instance
131 */
132 protected Cocoon cocoon;
133
134 /**
135 * Holds exception happened during initialization (if any)
136 */
137 protected Exception exception;
138
139 /**
140 * Avalon application context
141 */
142 protected DefaultContext appContext = new DefaultContext();
143
144
145 /**
146 * Default value for {@link #allowReload} parameter (false)
147 */
148 protected static final boolean ALLOW_RELOAD = false;
149
150 /**
151 * Allow reloading of cocoon by specifying the
152 * <code>cocoon-reload=true</code> parameter with a request
153 */
154 protected boolean allowReload;
155
156
157 /**
158 * Allow adding processing time to the response
159 */
160 protected boolean showTime;
161
162 /**
163 * If true, processing time will be added as an HTML comment
164 */
165 protected boolean hiddenShowTime;
166
167 /** Flag to enable/disable X-Cocoon-Version header */
168 private boolean showCocoonVersion;
169
170 /**
171 * Default value for {@link #enableUploads} parameter (false)
172 */
173 private static final boolean ENABLE_UPLOADS = false;
174 private static final boolean SAVE_UPLOADS_TO_DISK = true;
175 private static final int MAX_UPLOAD_SIZE = 10000000; // 10Mb
176
177 /**
178 * Allow processing of upload requests (mime/multipart)
179 */
180 private boolean enableUploads;
181 private boolean autoSaveUploads;
182 private boolean allowOverwrite;
183 private boolean silentlyRename;
184 private int maxUploadSize;
185
186 private File uploadDir;
187 private File workDir;
188 private File cacheDir;
189 private String containerEncoding;
190 private String defaultFormEncoding;
191
192 protected ServletContext servletContext;
193
194 /** The classloader that will be set as the context classloader if init-classloader is true */
195 protected ClassLoader classLoader = this.getClass().getClassLoader();
196 protected boolean initClassLoader = false;
197
198 private String parentComponentManagerClass;
199 private String parentComponentManagerInitParam;
200
201 /** The parent ComponentManager, if any. Stored here in order to be able to dispose it in destroy(). */
202 private ComponentManager parentComponentManager;
203
204 protected String forceLoadParameter;
205 protected String forceSystemProperty;
206
207 /**
208 * If true or not set, this class will try to catch and handle all Cocoon exceptions.
209 * If false, it will rethrow them to the servlet container.
210 */
211 private boolean manageExceptions;
212
213 /**
214 * Flag to enable avalon excalibur instrumentation of Cocoon.
215 */
216 private boolean enableInstrumentation;
217
218 /**
219 * The <code>InstrumentManager</code> instance
220 */
221 private InstrumentManager instrumentManager;
222
223 /**
224 * This is the path to the servlet context (or the result
225 * of calling getRealPath('/') on the ServletContext.
226 * Note, that this can be null.
227 */
228 protected String servletContextPath;
229
230 /**
231 * This is the url to the servlet context directory
232 */
233 protected String servletContextURL;
234
235 /**
236 * The RequestFactory is responsible for wrapping multipart-encoded
237 * forms and for handing the file payload of incoming requests
238 */
239 protected RequestFactory requestFactory;
240
241 /**
242 * Initialize this <code>CocoonServlet</code> instance. You will
243 * notice that I have broken the init into sub methods to make it
244 * easier to maintain (BL). The context is passed to a couple of
245 * the subroutines. This is also because it is better to explicitly
246 * pass variables than implicitely. It is both more maintainable,
247 * and more elegant.
248 *
249 * @param conf The ServletConfig object from the servlet engine.
250 *
251 * @throws ServletException
252 */
253 public void init(ServletConfig conf)
254 throws ServletException {
255
256 super.init(conf);
257
258 // Check the init-classloader parameter only if it's not already true.
259 // This is useful for subclasses of this servlet that override the value
260 // initially set by this class (i.e. false).
261 if (!this.initClassLoader) {
262 this.initClassLoader = getInitParameterAsBoolean("init-classloader", false);
263 }
264
265 if (this.initClassLoader) {
266 // Force context classloader so that JAXP can work correctly
267 // (see javax.xml.parsers.FactoryFinder.findClassLoader())
268 try {
269 Thread.currentThread().setContextClassLoader(this.classLoader);
270 } catch (Exception e) {
271 // ignore this
272 }
273 }
274
275 try {
276 // FIXME (VG): We shouldn't have to specify these. Need to override
277 // jaxp implementation of weblogic before initializing logger.
278 // This piece of code is also required in the Cocoon class.
279 String value = System.getProperty("javax.xml.parsers.SAXParserFactory");
280 if (value != null && value.startsWith("weblogic")) {
281 System.setProperty("javax.xml.parsers.SAXParserFactory", "org.apache.xerces.jaxp.SAXParserFactoryImpl");
282 System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
283 }
284 } catch (Exception e) {
285 // Ignore security exception
286 System.out.println("CocoonServlet: Could not check system properties, got: " + e);
287 }
288
289 this.servletContext = conf.getServletContext();
290 this.appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, new HttpContext(this.servletContext));
291 this.servletContextPath = this.servletContext.getRealPath("/");
292
293 // first init the work-directory for the logger.
294 // this is required if we are running inside a war file!
295 final String workDirParam = getInitParameter("work-directory");
296 if (workDirParam != null) {
297 if (this.servletContextPath == null) {
298 // No context path : consider work-directory as absolute
299 this.workDir = new File(workDirParam);
300 } else {
301 // Context path exists : is work-directory absolute ?
302 File workDirParamFile = new File(workDirParam);
303 if (workDirParamFile.isAbsolute()) {
304 // Yes : keep it as is
305 this.workDir = workDirParamFile;
306 } else {
307 // No : consider it relative to context path
308 this.workDir = new File(servletContextPath, workDirParam);
309 }
310 }
311 } else {
312 this.workDir = (File) this.servletContext.getAttribute("javax.servlet.context.tempdir");
313 this.workDir = new File(workDir, "cocoon-files");
314 }
315 this.workDir.mkdirs();
316 this.appContext.put(Constants.CONTEXT_WORK_DIR, workDir);
317
318 String path = this.servletContextPath;
319 // these two variables are just for debugging. We can't log at this point
320 // as the logger isn't initialized yet.
321 String debugPathOne = null, debugPathTwo = null;
322 if (path == null) {
323 // Try to figure out the path of the root from that of WEB-INF
324 try {
325 path = this.servletContext.getResource("/WEB-INF").toString();
326 } catch (MalformedURLException me) {
327 throw new ServletException("Unable to get resource 'WEB-INF'.", me);
328 }
329 debugPathOne = path;
330 path = path.substring(0, path.length() - "WEB-INF".length());
331 debugPathTwo = path;
332 }
333 try {
334 if (path.indexOf(':') > 1) {
335 this.servletContextURL = path;
336 } else {
337 this.servletContextURL = new File(path).toURL().toExternalForm();
338 }
339 } catch (MalformedURLException me) {
340 // VG: Novell has absolute file names starting with the
341 // volume name which is easily more then one letter.
342 // Examples: sys:/apache/cocoon or sys:\apache\cocoon
343 try {
344 this.servletContextURL = new File(path).toURL().toExternalForm();
345 } catch (MalformedURLException ignored) {
346 throw new ServletException("Unable to determine servlet context URL.", me);
347 }
348 }
349 try {
350 this.appContext.put("context-root", new URL(this.servletContextURL));
351 } catch (MalformedURLException ignore) {
352 // we simply ignore this
353 }
354
355 // Init logger
356 initLogger();
357
358 if (getLogger().isDebugEnabled()) {
359 getLogger().debug("getRealPath for /: " + this.servletContextPath);
360 if (this.servletContextPath == null) {
361 getLogger().debug("getResource for /WEB-INF: " + debugPathOne);
362 getLogger().debug("Path for Root: " + debugPathTwo);
363 }
364 }
365
366 this.forceLoadParameter = getInitParameter("load-class", null);
367 this.forceSystemProperty = getInitParameter("force-property", null);
368
369 // Output some debug info
370 if (getLogger().isDebugEnabled()) {
371 getLogger().debug("Servlet Context URL: " + this.servletContextURL);
372 if (workDirParam != null) {
373 getLogger().debug("Using work-directory " + this.workDir);
374 } else {
375 getLogger().debug("Using default work-directory " + this.workDir);
376 }
377 }
378
379 final String uploadDirParam = conf.getInitParameter("upload-directory");
380 if (uploadDirParam != null) {
381 if (this.servletContextPath == null) {
382 this.uploadDir = new File(uploadDirParam);
383 } else {
384 // Context path exists : is upload-directory absolute ?
385 File uploadDirParamFile = new File(uploadDirParam);
386 if (uploadDirParamFile.isAbsolute()) {
387 // Yes : keep it as is
388 this.uploadDir = uploadDirParamFile;
389 } else {
390 // No : consider it relative to context path
391 this.uploadDir = new File(servletContextPath, uploadDirParam);
392 }
393 }
394 if (getLogger().isDebugEnabled()) {
395 getLogger().debug("Using upload-directory " + this.uploadDir);
396 }
397 } else {
398 this.uploadDir = new File(workDir, "upload-dir" + File.separator);
399 if (getLogger().isDebugEnabled()) {
400 getLogger().debug("Using default upload-directory " + this.uploadDir);
401 }
402 }
403 this.uploadDir.mkdirs();
404 this.appContext.put(Constants.CONTEXT_UPLOAD_DIR, this.uploadDir);
405
406 this.enableUploads = getInitParameterAsBoolean("enable-uploads", ENABLE_UPLOADS);
407
408 this.autoSaveUploads = getInitParameterAsBoolean("autosave-uploads", SAVE_UPLOADS_TO_DISK);
409
410 String overwriteParam = getInitParameter("overwrite-uploads", "rename");
411 // accepted values are deny|allow|rename - rename is default.
412 if ("deny".equalsIgnoreCase(overwriteParam)) {
413 this.allowOverwrite = false;
414 this.silentlyRename = false;
415 } else if ("allow".equalsIgnoreCase(overwriteParam)) {
416 this.allowOverwrite = true;
417 this.silentlyRename = false; // ignored in this case
418 } else {
419 // either rename is specified or unsupported value - default to rename.
420 this.allowOverwrite = false;
421 this.silentlyRename = true;
422 }
423
424 this.maxUploadSize = getInitParameterAsInteger("upload-max-size", MAX_UPLOAD_SIZE);
425
426 String cacheDirParam = conf.getInitParameter("cache-directory");
427 if (cacheDirParam != null) {
428 if (this.servletContextPath == null) {
429 this.cacheDir = new File(cacheDirParam);
430 } else {
431 // Context path exists : is cache-directory absolute ?
432 File cacheDirParamFile = new File(cacheDirParam);
433 if (cacheDirParamFile.isAbsolute()) {
434 // Yes : keep it as is
435 this.cacheDir = cacheDirParamFile;
436 } else {
437 // No : consider it relative to context path
438 this.cacheDir = new File(servletContextPath, cacheDirParam);
439 }
440 }
441 if (getLogger().isDebugEnabled()) {
442 getLogger().debug("Using cache-directory " + this.cacheDir);
443 }
444 } else {
445 this.cacheDir = IOUtils.createFile(workDir, "cache-dir" + File.separator);
446 if (getLogger().isDebugEnabled()) {
447 getLogger().debug("cache-directory was not set - defaulting to " + this.cacheDir);
448 }
449 }
450 this.cacheDir.mkdirs();
451 this.appContext.put(Constants.CONTEXT_CACHE_DIR, this.cacheDir);
452
453 this.appContext.put(Constants.CONTEXT_CONFIG_URL,
454 getConfigFile(conf.getInitParameter("configurations")));
455 if (conf.getInitParameter("configurations") == null) {
456 if (getLogger().isDebugEnabled()) {
457 getLogger().debug("configurations was not set - defaulting to... ?");
458 }
459 }
460
461 // get allow reload parameter, default is true
462 this.allowReload = getInitParameterAsBoolean("allow-reload", ALLOW_RELOAD);
463
464 String value = conf.getInitParameter("show-time");
465 this.showTime = BooleanUtils.toBoolean(value) || (this.hiddenShowTime = "hide".equals(value));
466 if (value == null) {
467 if (getLogger().isDebugEnabled()) {
468 getLogger().debug("show-time was not set - defaulting to false");
469 }
470 }
471
472 this.showCocoonVersion = getInitParameterAsBoolean("show-cocoon-version", true);
473
474 parentComponentManagerClass = getInitParameter("parent-component-manager", null);
475 if (parentComponentManagerClass != null) {
476 int dividerPos = parentComponentManagerClass.indexOf('/');
477 if (dividerPos != -1) {
478 parentComponentManagerInitParam = parentComponentManagerClass.substring(dividerPos + 1);
479 parentComponentManagerClass = parentComponentManagerClass.substring(0, dividerPos);
480 }
481 }
482
483 this.containerEncoding = getInitParameter("container-encoding", "ISO-8859-1");
484 this.defaultFormEncoding = getInitParameter("form-encoding", "ISO-8859-1");
485 this.appContext.put(Constants.CONTEXT_DEFAULT_ENCODING, this.defaultFormEncoding);
486
487 this.manageExceptions = getInitParameterAsBoolean("manage-exceptions", true);
488
489 this.enableInstrumentation = getInitParameterAsBoolean("enable-instrumentation", false);
490
491 this.requestFactory = new RequestFactory(this.autoSaveUploads,
492 this.uploadDir,
493 this.allowOverwrite,
494 this.silentlyRename,
495 this.maxUploadSize,
496 this.containerEncoding);
497 // Add the servlet configuration
498 this.appContext.put(CONTEXT_SERVLET_CONFIG, conf);
499 this.createCocoon();
500 }
501
502 /**
503 * Dispose Cocoon when servlet is destroyed
504 */
505 public void destroy() {
506 if (this.initClassLoader) {
507 try {
508 Thread.currentThread().setContextClassLoader(this.classLoader);
509 } catch (Exception e) {
510 // Ignored
511 }
512 }
513
514 if (this.cocoon != null) {
515 if (getLogger().isDebugEnabled()) {
516 getLogger().debug("Servlet destroyed - disposing Cocoon");
517 }
518 disposeCocoon();
519 }
520
521 if (this.instrumentManager instanceof Disposable) {
522 ((Disposable) this.instrumentManager).dispose();
523 }
524
525 if (this.parentComponentManager != null && this.parentComponentManager instanceof Disposable) {
526 ((Disposable) this.parentComponentManager).dispose();
527 }
528
529 this.appContext = null;
530 this.classLoader = null;
531 this.log = null;
532 this.loggerManager = null;
533 }
534
535 /**
536 * Adds an URL to the classloader. Does nothing here, but is
537 * overriden in {@link ParanoidCocoonServlet}.
538 */
539 protected void addClassLoaderURL(URL URL) {
540 // Nothing
541 }
542
543 /**
544 * Adds a directory to the classloader. Does nothing here, but is
545 * overriden in {@link ParanoidCocoonServlet}.
546 */
547 protected void addClassLoaderDirectory(String dir) {
548 // Nothing
549 }
550
551 /**
552 * This builds the important ClassPath used by this Servlet. It
553 * does so in a Servlet Engine neutral way. It uses the
554 * <code>ServletContext</code>'s <code>getRealPath</code> method
555 * to get the Servlet 2.2 identified classes and lib directories.
556 * It iterates in alphabetical order through every file in the
557 * lib directory and adds it to the classpath.
558 *
559 * Also, we add the files to the ClassLoader for the Cocoon system.
560 * In order to protect ourselves from skitzofrantic classloaders,
561 * we need to work with a known one.
562 *
563 * We need to get this to work properly when Cocoon is in a war.
564 *
565 * @throws ServletException
566 */
567 protected String getClassPath() throws ServletException {
568 StringBuffer buildClassPath = new StringBuffer();
569
570 File root = null;
571 if (servletContextPath != null) {
572 // Old method. There *MUST* be a better method than this...
573
574 String classDir = this.servletContext.getRealPath("/WEB-INF/classes");
575 String libDir = this.servletContext.getRealPath("/WEB-INF/lib");
576
577 if (libDir != null) {
578 root = new File(libDir);
579 }
580
581 if (classDir != null) {
582 buildClassPath.append(classDir);
583
584 addClassLoaderDirectory(classDir);
585 }
586 } else {
587 // New(ish) method for war'd deployments
588 URL classDirURL = null;
589 URL libDirURL = null;
590
591 try {
592 classDirURL = this.servletContext.getResource("/WEB-INF/classes");
593 } catch (MalformedURLException me) {
594 if (getLogger().isWarnEnabled()) {
595 this.getLogger().warn("Unable to add WEB-INF/classes to the classpath", me);
596 }
597 }
598
599 try {
600 libDirURL = this.servletContext.getResource("/WEB-INF/lib");
601 } catch (MalformedURLException me) {
602 if (getLogger().isWarnEnabled()) {
603 this.getLogger().warn("Unable to add WEB-INF/lib to the classpath", me);
604 }
605 }
606
607 if (libDirURL != null && libDirURL.toExternalForm().startsWith("file:")) {
608 root = new File(libDirURL.toExternalForm().substring("file:".length()));
609 }
610
611 if (classDirURL != null) {
612 buildClassPath.append(classDirURL.toExternalForm());
613
614 addClassLoaderURL(classDirURL);
615 }
616 }
617
618 // Unable to find lib directory. Going the hard way.
619 if (root == null) {
620 root = extractLibraries();
621 }
622
623 if (root != null && root.isDirectory()) {
624 File[] libraries = root.listFiles();
625 Arrays.sort(libraries);
626 for (int i = 0; i < libraries.length; i++) {
627 String fullName = IOUtils.getFullFilename(libraries[i]);
628 buildClassPath.append(File.pathSeparatorChar).append(fullName);
629
630 addClassLoaderDirectory(fullName);
631 }
632 }
633
634 buildClassPath.append(File.pathSeparatorChar)
635 .append(SystemUtils.JAVA_CLASS_PATH);
636
637 buildClassPath.append(File.pathSeparatorChar)
638 .append(getExtraClassPath());
639 return buildClassPath.toString();
640 }
641
642 private File extractLibraries() {
643 try {
644 URL manifestURL = this.servletContext.getResource("/META-INF/MANIFEST.MF");
645 if (manifestURL == null) {
646 this.getLogger().fatalError("Unable to get Manifest");
647 return null;
648 }
649
650 Manifest mf = new Manifest(manifestURL.openStream());
651 Attributes attr = mf.getMainAttributes();
652 String libValue = attr.getValue("Cocoon-Libs");
653 if (libValue == null) {
654 this.getLogger().fatalError("Unable to get 'Cocoon-Libs' attribute from the Manifest");
655 return null;
656 }
657
658 List libList = new ArrayList();
659 for (StringTokenizer st = new StringTokenizer(libValue, " "); st.hasMoreTokens();) {
660 libList.add(st.nextToken());
661 }
662
663 File root = new File(this.workDir, "lib");
664 root.mkdirs();
665
666 File[] oldLibs = root.listFiles();
667 for (int i = 0; i < oldLibs.length; i++) {
668 String oldLib = oldLibs[i].getName();
669 if (!libList.contains(oldLib)) {
670 this.getLogger().debug("Removing old library " + oldLibs[i]);
671 oldLibs[i].delete();
672 }
673 }
674
675 this.getLogger().warn("Extracting libraries into " + root);
676 byte[] buffer = new byte[65536];
677 for (Iterator i = libList.iterator(); i.hasNext();) {
678 String libName = (String) i.next();
679
680 long lastModified = -1;
681 try {
682 lastModified = Long.parseLong(attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
683 } catch (Exception e) {
684 this.getLogger().debug("Failed to parse lastModified: " + attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
685 }
686
687 File lib = new File(root, libName);
688 if (lib.exists() && lib.lastModified() != lastModified) {
689 this.getLogger().debug("Removing modified library " + lib);
690 lib.delete();
691 }
692
693 InputStream is = null;
694 OutputStream os = null;
695 try {
696 is = this.servletContext.getResourceAsStream("/WEB-INF/lib/" + libName);
697 if (is != null) {
698 this.getLogger().debug("Extracting " + libName);
699 os = new FileOutputStream(lib);
700 int count;
701 while ((count = is.read(buffer)) > 0) {
702 os.write(buffer, 0, count);
703 }
704 } else {
705 this.getLogger().warn("Skipping " + libName);
706 }
707 } finally {
708 if (os != null) os.close();
709 if (is != null) is.close();
710 }
711
712 if (lastModified != -1) {
713 lib.setLastModified(lastModified);
714 }
715 }
716
717 return root;
718 } catch (IOException e) {
719 this.getLogger().fatalError("Exception while processing Manifest file", e);
720 return null;
721 }
722 }
723
724 /**
725 * Retreives the "extra-classpath" attribute, that needs to be
726 * added to the class path.
727 *
728 * @throws ServletException
729 */
730 protected String getExtraClassPath() throws ServletException {
731 String extraClassPath = this.getInitParameter("extra-classpath");
732 if (extraClassPath != null) {
733 StringBuffer sb = new StringBuffer();
734 StringTokenizer st = new StringTokenizer(extraClassPath, SystemUtils.PATH_SEPARATOR, false);
735 int i = 0;
736 while (st.hasMoreTokens()) {
737 String s = st.nextToken();
738 if (i++ > 0) {
739 sb.append(File.pathSeparatorChar);
740 }
741 if ((s.charAt(0) == File.separatorChar) ||
742 (s.charAt(1) == ':')) {
743 if (getLogger().isDebugEnabled()) {
744 getLogger().debug("extraClassPath is absolute: " + s);
745 }
746 sb.append(s);
747
748 addClassLoaderDirectory(s);
749 } else {
750 if (s.indexOf("${") != -1) {
751 String path = StringUtils.replaceToken(s);
752 sb.append(path);
753 if (getLogger().isDebugEnabled()) {
754 getLogger().debug("extraClassPath is not absolute replacing using token: [" + s + "] : " + path);
755 }
756 addClassLoaderDirectory(path);
757 } else {
758 String path = null;
759 if (this.servletContextPath != null) {
760 path = this.servletContextPath + s;
761 if (getLogger().isDebugEnabled()) {
762 getLogger().debug("extraClassPath is not absolute pre-pending context path: " + path);
763 }
764 } else {
765 path = this.workDir.toString() + s;
766 if (getLogger().isDebugEnabled()) {
767 getLogger().debug("extraClassPath is not absolute pre-pending work-directory: " + path);
768 }
769 }
770 sb.append(path);
771 addClassLoaderDirectory(path);
772 }
773 }
774 }
775 return sb.toString();
776 }
777 return "";
778 }
779
780 /**
781 * Set up the log level and path. The default log level is
782 * Priority.ERROR, although it can be overwritten by the parameter
783 * "log-level". The log system goes to both a file and the Servlet
784 * container's log system. Only messages that are Priority.ERROR
785 * and above go to the servlet context. The log messages can
786 * be as restrictive (Priority.FATAL_ERROR and above) or as liberal
787 * (Priority.DEBUG and above) as you want that get routed to the
788 * file.
789 */
790 protected void initLogger() {
791 final String logLevel = getInitParameter("log-level", "INFO");
792
793 final String accesslogger = getInitParameter("servlet-logger", "cocoon");
794
795 final Priority logPriority = Priority.getPriorityForName(logLevel);
796
797 final CocoonLogFormatter formatter = new CocoonLogFormatter();
798 formatter.setFormat("%7.7{priority} %{time} [%8.8{category}] " +
799 "(%{uri}) %{thread}/%{class:short}: %{message}\\n%{throwable}");
800 final ServletOutputLogTarget servTarget = new ServletOutputLogTarget(this.servletContext, formatter);
801
802 final Hierarchy defaultHierarchy = Hierarchy.getDefaultHierarchy();
803 final ErrorHandler errorHandler = new DefaultErrorHandler();
804 defaultHierarchy.setErrorHandler(errorHandler);
805 defaultHierarchy.setDefaultLogTarget(servTarget);
806 defaultHierarchy.setDefaultPriority(logPriority);
807 final Logger logger = new LogKitLogger(Hierarchy.getDefaultHierarchy().getLoggerFor(""));
808 final String loggerManagerClass =
809 this.getInitParameter("logger-class", LogKitLoggerManager.class.getName());
810
811 // the log4j support requires currently that the log4j system is already configured elsewhere
812
813 final LoggerManager loggerManager =
814 newLoggerManager(loggerManagerClass, defaultHierarchy);
815 ContainerUtil.enableLogging(loggerManager, logger);
816
817 final DefaultContext subcontext = new DefaultContext(this.appContext);
818 subcontext.put("servlet-context", this.servletContext);
819 subcontext.put("context-work", this.workDir);
820 if (this.servletContextPath == null) {
821 File logSCDir = new File(this.workDir, "log");
822 logSCDir.mkdirs();
823 if (logger.isWarnEnabled()) {
824 logger.warn("Setting context-root for LogKit to " + logSCDir);
825 }
826 subcontext.put("context-root", logSCDir.toString());
827 } else {
828 subcontext.put("context-root", this.servletContextPath);
829 }
830
831 try {
832 ContainerUtil.contextualize(loggerManager, subcontext);
833 this.loggerManager = loggerManager;
834
835 if (loggerManager instanceof Configurable) {
836 //Configure the logkit management
837 String logkitConfig = getInitParameter("logkit-config", "/WEB-INF/logkit.xconf");
838
839 // test if this is a qualified url
840 InputStream is = null;
841 if (logkitConfig.indexOf(':') == -1) {
842 is = this.servletContext.getResourceAsStream(logkitConfig);
843 if (is == null) is = new FileInputStream(logkitConfig);
844 } else {
845 URL logkitURL = new URL(logkitConfig);
846 is = logkitURL.openStream();
847 }
848 final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
849 final Configuration conf = builder.build(is);
850 ContainerUtil.configure(loggerManager, conf);
851 }
852
853 // let's configure log4j
854 final String log4jConfig = getInitParameter("log4j-config", null);
855 if ( log4jConfig != null ) {
856 final Log4JConfigurator configurator = new Log4JConfigurator(subcontext);
857
858 // test if this is a qualified url
859 InputStream is = null;
860 if ( log4jConfig.indexOf(':') == -1) {
861 is = this.servletContext.getResourceAsStream(log4jConfig);
862 if (is == null) is = new FileInputStream(log4jConfig);
863 } else {
864 final URL log4jURL = new URL(log4jConfig);
865 is = log4jURL.openStream();
866 }
867 configurator.doConfigure(is, LogManager.getLoggerRepository());
868 }
869
870 ContainerUtil.initialize(loggerManager);
871 } catch (Exception e) {
872 errorHandler.error("Could not set up Cocoon Logger, will use screen instead", e, null);
873 }
874
875 this.log = this.loggerManager.getLoggerForCategory(accesslogger);
876
877 final String deprecationLevel = getInitParameter("forbidden-deprecation-level", "ERROR");
878 Deprecation.setForbiddenLevel(Deprecation.LogLevel.getLevel(deprecationLevel));
879 }
880
881 private LoggerManager newLoggerManager(String loggerManagerClass, Hierarchy hierarchy) {
882 if (loggerManagerClass.equals(LogKitLoggerManager.class.getName())) {
883 return new LogKitLoggerManager(hierarchy);
884 } else if (loggerManagerClass.equals(Log4JLoggerManager.class.getName()) ||
885 loggerManagerClass.equalsIgnoreCase("LOG4J")) {
886 return new Log4JLoggerManager();
887 } else {
888 try {
889 Class clazz = Class.forName(loggerManagerClass);
890 return (LoggerManager)clazz.newInstance();
891 } catch (Exception e) {
892 return new LogKitLoggerManager(hierarchy);
893 }
894 }
895 }
896
897 /**
898 * Set the ConfigFile for the Cocoon object.
899 *
900 * @param configFileName The file location for the cocoon.xconf
901 *
902 * @throws ServletException
903 */
904 private URL getConfigFile(final String configFileName)
905 throws ServletException {
906 final String usedFileName;
907
908 if (configFileName == null) {
909 if (getLogger().isWarnEnabled()) {
910 getLogger().warn("Servlet initialization argument 'configurations' not specified, attempting to use '/WEB-INF/cocoon.xconf'");
911 }
912 usedFileName = "/WEB-INF/cocoon.xconf";
913 } else {
914 usedFileName = configFileName;
915 }
916
917 if (getLogger().isDebugEnabled()) {
918 getLogger().debug("Using configuration file: " + usedFileName);
919 }
920
921 URL result;
922 try {
923 // test if this is a qualified url
924 if (usedFileName.indexOf(':') == -1) {
925 result = this.servletContext.getResource(usedFileName);
926 } else {
927 result = new URL(usedFileName);
928 }
929 } catch (Exception mue) {
930 String msg = "Init parameter 'configurations' is invalid : " + usedFileName;
931 getLogger().error(msg, mue);
932 throw new ServletException(msg, mue);
933 }
934
935 if (result == null) {
936 File resultFile = new File(usedFileName);
937 if (resultFile.isFile()) {
938 try {
939 result = resultFile.getCanonicalFile().toURL();
940 } catch (Exception e) {
941 String msg = "Init parameter 'configurations' is invalid : " + usedFileName;
942 getLogger().error(msg, e);
943 throw new ServletException(msg, e);
944 }
945 }
946 }
947
948 if (result == null) {
949 String msg = "Init parameter 'configurations' doesn't name an existing resource : " + usedFileName;
950 getLogger().error(msg);
951 throw new ServletException(msg);
952 }
953 return result;
954 }
955
956 /**
957 * Handle the <code>load-class</code> parameter. This overcomes
958 * limits in many classpath issues. One of the more notorious
959 * ones is a bug in WebSphere that does not load the URL handler
960 * for the <code>classloader://</code> protocol. In order to
961 * overcome that bug, set <code>load-class</code> parameter to
962 * the <code>com.ibm.servlet.classloader.Handler</code> value.
963 *
964 * <p>If you need to load more than one class, then separate each
965 * entry with whitespace, a comma, or a semi-colon. Cocoon will
966 * strip any whitespace from the entry.</p>
967 */
968 private void forceLoad() {
969 if (this.forceLoadParameter != null) {
970 StringTokenizer fqcnTokenizer = new StringTokenizer(forceLoadParameter, " \t\r\n\f;,", false);
971
972 while (fqcnTokenizer.hasMoreTokens()) {
973 final String fqcn = fqcnTokenizer.nextToken().trim();
974
975 try {
976 if (getLogger().isDebugEnabled()) {
977 getLogger().debug("Loading: " + fqcn);
978 }
979 ClassUtils.loadClass(fqcn).newInstance();
980 } catch (Exception e) {
981 if (getLogger().isWarnEnabled()) {
982 getLogger().warn("Could not load class: " + fqcn, e);
983 }
984 // Do not throw an exception, because it is not a fatal error.
985 }
986 }
987 }
988 }
989
990 /**
991 * Handle the "force-property" parameter.
992 *
993 * If you need to force more than one property to load, then
994 * separate each entry with whitespace, a comma, or a semi-colon.
995 * Cocoon will strip any whitespace from the entry.
996 */
997 private void forceProperty() {
998 if (this.forceSystemProperty != null) {
999 StringTokenizer tokenizer = new StringTokenizer(forceSystemProperty, " \t\r\n\f;,", false);
1000
1001 while (tokenizer.hasMoreTokens()) {
1002 final String property = tokenizer.nextToken().trim();
1003 if (property.indexOf('=') == -1) {
1004 continue;
1005 }
1006 try {
1007 String key = property.substring(0, property.indexOf('='));
1008 String value = property.substring(property.indexOf('=') + 1);
1009 if (value.indexOf("${") != -1) {
1010 value = StringUtils.replaceToken(value);
1011 }
1012 if (getLogger().isDebugEnabled()) {
1013 getLogger().debug("Setting " + key + "=" + value);
1014 }
1015 System.setProperty(key, value);
1016 } catch (Exception e) {
1017 if (getLogger().isWarnEnabled()) {
1018 getLogger().warn("Could not set property: " + property, e);
1019 }
1020 // Do not throw an exception, because it is not a fatal error.
1021 }
1022 }
1023 }
1024 }
1025
1026 /**
1027 * Process the specified <code>HttpServletRequest</code> producing output
1028 * on the specified <code>HttpServletResponse</code>.
1029 */
1030 public void service(HttpServletRequest req, HttpServletResponse res)
1031 throws ServletException, IOException {
1032
1033 /* HACK for reducing class loader problems. */
1034 /* example: xalan extensions fail if someone adds xalan jars in tomcat3.2.1/lib */
1035 if (this.initClassLoader) {
1036 try {
1037 Thread.currentThread().setContextClassLoader(this.classLoader);
1038 } catch (Exception e) {
1039 }
1040 }
1041
1042 // used for timing the processing
1043 StopWatch stopWatch = new StopWatch();
1044 stopWatch.start();
1045
1046 // add the cocoon header timestamp
1047 if (this.showCocoonVersion) {
1048 res.addHeader("X-Cocoon-Version", Constants.VERSION);
1049 }
1050
1051 // get the request (wrapped if contains multipart-form data)
1052 HttpServletRequest request;
1053 try{
1054 if (this.enableUploads) {
1055 request = requestFactory.getServletRequest(req);
1056 } else {
1057 request = req;
1058 }
1059 } catch (Exception e) {
1060 if (getLogger().isErrorEnabled()) {
1061 getLogger().error("Problem with Cocoon servlet", e);
1062 }
1063
1064 manageException(req, res, null, null,
1065 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1066 "Problem in creating the Request", null, null, e);
1067 return;
1068 }
1069
1070 // Get the cocoon engine instance
1071
1072 if (reloadCocoon(request.getPathInfo(), request.getParameter(Constants.RELOAD_PARAM))) {
1073 disposeCocoon();
1074 initLogger();
1075 createCocoon();
1076 }
1077
1078 // Check if cocoon was initialized
1079 if (this.cocoon == null) {
1080 manageException(request, res, null, null,
1081 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1082 "Initialization Problem",
1083 null /* "Cocoon was not initialized" */,
1084 null /* "Cocoon was not initialized, cannot process request" */,
1085 this.exception);
1086 return;
1087 }
1088
1089 // We got it... Process the request
1090 String uri = request.getServletPath();
1091 if (uri == null) {
1092 uri = "";
1093 }
1094 String pathInfo = request.getPathInfo();
1095 if (pathInfo != null) {
1096 // VG: WebLogic fix: Both uri and pathInfo starts with '/'
1097 // This problem exists only in WL6.1sp2, not in WL6.0sp2 or WL7.0b.
1098 if (uri.length() > 0 && uri.charAt(0) == '/') {
1099 uri = uri.substring(1);
1100 }
1101 uri += pathInfo;
1102 }
1103
1104 if (uri.length() == 0) {
1105 /* empty relative URI
1106 -> HTTP-redirect from /cocoon to /cocoon/ to avoid
1107 StringIndexOutOfBoundsException when calling
1108 "".charAt(0)
1109 else process URI normally
1110 */
1111 String prefix = request.getRequestURI();
1112 if (prefix == null) {
1113 prefix = "";
1114 }
1115
1116 res.sendRedirect(res.encodeRedirectURL(prefix + "/"));
1117 return;
1118 }
1119
1120 String contentType = null;
1121 ContextMap ctxMap = null;
1122
1123 Environment env;
1124 try{
1125 if (uri.charAt(0) == '/') {
1126 uri = uri.substring(1);
1127 }
1128 // Pass uri into environment without URLDecoding, as it is already decoded.
1129 env = getEnvironment(uri, request, res);
1130 } catch (Exception e) {
1131 if (getLogger().isErrorEnabled()) {
1132 getLogger().error("Problem with Cocoon servlet", e);
1133 }
1134
1135 manageException(request, res, null, uri,
1136 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1137 "Problem in creating the Environment", null, null, e);
1138 return;
1139 }
1140
1141 try {
1142 try {
1143 // Initialize a fresh log context containing the object model: it
1144 // will be used by the CocoonLogFormatter
1145 ctxMap = ContextMap.getCurrentContext();
1146 // Add thread name (default content for empty context)
1147 String threadName = Thread.currentThread().getName();
1148 ctxMap.set("threadName", threadName);
1149 // Add the object model
1150 ctxMap.set("objectModel", env.getObjectModel());
1151 // Add a unique request id (threadName + currentTime
1152 ctxMap.set("request-id", threadName + System.currentTimeMillis());
1153
1154 if (this.cocoon.process(env)) {
1155 contentType = env.getContentType();
1156 } else {
1157 // We reach this when there is nothing in the processing change that matches
1158 // the request. For example, no matcher matches.
1159 getLogger().fatalError("The Cocoon engine failed to process the request.");
1160 manageException(request, res, env, uri,
1161 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1162 "Request Processing Failed",
1163 "Cocoon engine failed in process the request",
1164 "The processing engine failed to process the request. This could be due to lack of matching or bugs in the pipeline engine.",
1165 null);
1166 return;
1167 }
1168 } catch (ResourceNotFoundException e) {
1169 if (getLogger().isDebugEnabled()) {
1170 getLogger().warn(e.getMessage(), e);
1171 } else if (getLogger().isWarnEnabled()) {
1172 getLogger().warn(e.getMessage());
1173 }
1174
1175 manageException(request, res, env, uri,
1176 HttpServletResponse.SC_NOT_FOUND,
1177 "Resource Not Found",
1178 "Resource Not Found",
1179 "The requested resource \"" + request.getRequestURI() + "\" could not be found",
1180 e);
1181 return;
1182
1183 } catch (ConnectionResetException e) {
1184 if (getLogger().isDebugEnabled()) {
1185 getLogger().debug(e.toString(), e);
1186 } else if (getLogger().isWarnEnabled()) {
1187 getLogger().warn(e.toString());
1188 }
1189
1190 } catch (IOException e) {
1191 // Tomcat5 wraps SocketException into ClientAbortException which extends IOException.
1192 if (getLogger().isDebugEnabled()) {
1193 getLogger().debug(e.toString(), e);
1194 } else if (getLogger().isWarnEnabled()) {
1195 getLogger().warn(e.toString());
1196 }
1197
1198 } catch (Exception e) {
1199 if (getLogger().isErrorEnabled()) {
1200 getLogger().error("Internal Cocoon Problem", e);
1201 }
1202
1203 manageException(request, res, env, uri,
1204 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1205 "Internal Server Error", null, null, e);
1206 return;
1207 }
1208
1209 stopWatch.stop();
1210 String timeString = null;
1211 if (getLogger().isInfoEnabled()) {
1212 timeString = processTime(stopWatch.getTime());
1213 getLogger().info("'" + uri + "' " + timeString);
1214 }
1215
1216 if (contentType != null && contentType.equals("text/html")) {
1217 String showTime = request.getParameter(Constants.SHOWTIME_PARAM);
1218 boolean show = this.showTime;
1219 if (showTime != null) {
1220 show = !showTime.equalsIgnoreCase("no");
1221 }
1222 if (show) {
1223 if (timeString == null) {
1224 timeString = processTime(stopWatch.getTime());
1225 }
1226 boolean hide = this.hiddenShowTime;
1227 if (showTime != null) {
1228 hide = showTime.equalsIgnoreCase("hide");
1229 }
1230 ServletOutputStream out = res.getOutputStream();
1231 out.print((hide) ? "<!-- " : "<p>");
1232 out.print(timeString);
1233 out.println((hide) ? " -->" : "</p>");
1234 }
1235 }
1236 } finally {
1237 if (ctxMap != null) {
1238 ctxMap.clear();
1239 }
1240
1241 try {
1242 if (request instanceof MultipartHttpServletRequest) {
1243 if (getLogger().isDebugEnabled()) {
1244 getLogger().debug("Deleting uploaded file(s).");
1245 }
1246 ((MultipartHttpServletRequest) request).cleanup();
1247 }
1248 } catch (IOException e) {
1249 getLogger().error("Cocoon got an Exception while trying to cleanup the uploaded files.", e);
1250 }
1251
1252 /*
1253 * Servlet Specification 2.2, 6.5 Closure of Response Object:
1254 *
1255 * A number of events can indicate that the servlet has provided all of the
1256 * content to satisfy the request and that the response object can be
1257 * considered to be closed. The events are:
1258 * o The termination of the service method of the servlet.
1259 * o When the amount of content specified in the setContentLength method
1260 * of the response has been written to the response.
1261 * o The sendError method is called.
1262 * o The sendRedirect method is called.
1263 * When a response is closed, all content in the response buffer, if any remains,
1264 * must be immediately flushed to the client.
1265 *
1266 * Due to the above, out.flush() and out.close() are not necessary, and sometimes
1267 * (if sendError or sendRedirect were used) request may be already closed.
1268 */
1269 }
1270 }
1271
1272 protected void manageException(HttpServletRequest req, HttpServletResponse res, Environment env,
1273 String uri, int errorStatus,
1274 String title, String message, String description,
1275 Exception e)
1276 throws IOException {
1277 if (this.manageExceptions) {
1278 if (env != null) {
1279 env.tryResetResponse();
1280 } else {
1281 res.reset();
1282 }
1283
1284 String type = Notifying.FATAL_NOTIFICATION;
1285 HashMap extraDescriptions = null;
1286
1287 if (errorStatus == HttpServletResponse.SC_NOT_FOUND) {
1288 type = "resource-not-found";
1289 // Do not show the exception stacktrace for such common errors.
1290 e = null;
1291 } else {
1292 extraDescriptions = new HashMap(2);
1293 extraDescriptions.put(Notifying.EXTRA_REQUESTURI, req.getRequestURI());
1294 if (uri != null) {
1295 extraDescriptions.put("Request URI", uri);
1296 }
1297
1298 // Do not show exception stack trace when log level is WARN or above. Show only message.
1299 if (!getLogger().isInfoEnabled()) {
1300 Throwable t = DefaultNotifyingBuilder.getRootCause(e);
1301 if (t != null) extraDescriptions.put(Notifying.EXTRA_CAUSE, t.getMessage());
1302 e = null;
1303 }
1304 }
1305
1306 Notifying n = new DefaultNotifyingBuilder().build(this,
1307 e,
1308 type,
1309 title,
1310 "Cocoon Servlet",
1311 message,
1312 description,
1313 extraDescriptions);
1314
1315 res.setContentType("text/html");
1316 res.setStatus(errorStatus);
1317 Notifier.notify(n, res.getOutputStream(), "text/html");
1318 } else {
1319 res.sendError(errorStatus, title);
1320 res.flushBuffer();
1321 }
1322 }
1323
1324 /**
1325 * Create the environment for the request
1326 */
1327 protected Environment getEnvironment(String uri,
1328 HttpServletRequest req,
1329 HttpServletResponse res)
1330 throws Exception {
1331 HttpEnvironment env;
1332
1333 String formEncoding = req.getParameter("cocoon-form-encoding");
1334 if (formEncoding == null) {
1335 formEncoding = this.defaultFormEncoding;
1336 }
1337 env = new HttpEnvironment(uri,
1338 this.servletContextURL,
1339 req,
1340 res,
1341 this.servletContext,
1342 (HttpContext) this.appContext.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT),
1343 this.containerEncoding,
1344 formEncoding);
1345 env.enableLogging(getLogger());
1346 return env;
1347 }
1348
1349 /**
1350 * Instatiates the parent component manager, as specified in the
1351 * parent-component-manager init parameter.
1352 *
1353 * If none is specified, the method returns <code>null</code>.
1354 *
1355 * @return the parent component manager, or <code>null</code>.
1356 */
1357 protected synchronized ComponentManager getParentComponentManager() {
1358 if (parentComponentManager != null && parentComponentManager instanceof Disposable) {
1359 ((Disposable) parentComponentManager).dispose();
1360 }
1361
1362 parentComponentManager = null;
1363 if (parentComponentManagerClass != null) {
1364 try {
1365 Class pcm = ClassUtils.loadClass(parentComponentManagerClass);
1366 Constructor pcmc = pcm.getConstructor(new Class[]{String.class});
1367 parentComponentManager = (ComponentManager) pcmc.newInstance(new Object[]{parentComponentManagerInitParam});
1368
1369 if (parentComponentManager instanceof LogEnabled) {
1370 ((LogEnabled) parentComponentManager).enableLogging(getLogger());
1371 }
1372 if (parentComponentManager instanceof Contextualizable) {
1373 ((Contextualizable) parentComponentManager).contextualize(this.appContext);
1374 }
1375 if (parentComponentManager instanceof Initializable) {
1376 ((Initializable) parentComponentManager).initialize();
1377 }
1378 } catch (Exception e) {
1379 if (getLogger().isErrorEnabled()) {
1380 getLogger().error("Could not initialize parent component manager.", e);
1381 }
1382 }
1383 }
1384 return parentComponentManager;
1385 }
1386
1387 /**
1388 * Creates the Cocoon object and handles exception handling.
1389 */
1390 protected synchronized void createCocoon()
1391 throws ServletException {
1392
1393 // Recheck that we need to create the cocoon object. It can have been created by
1394 // a concurrent invocation to this method.
1395 if (this.cocoon != null) {
1396 return;
1397 }
1398
1399 /* HACK for reducing class loader problems. */
1400 /* example: xalan extensions fail if someone adds xalan jars in tomcat3.2.1/lib */
1401 if (this.initClassLoader) {
1402 try {
1403 Thread.currentThread().setContextClassLoader(this.classLoader);
1404 } catch (Exception e) {
1405 }
1406 }
1407
1408 updateEnvironment();
1409 forceLoad();
1410 forceProperty();
1411
1412 try {
1413 this.exception = null;
1414 URL configFile = (URL) this.appContext.get(Constants.CONTEXT_CONFIG_URL);
1415 if (getLogger().isInfoEnabled()) {
1416 getLogger().info("Reloading from: " + configFile.toExternalForm());
1417 }
1418 Cocoon c = (Cocoon) ClassUtils.newInstance("org.apache.cocoon.Cocoon");
1419 ContainerUtil.enableLogging(c, getCocoonLogger());
1420 c.setLoggerManager(getLoggerManager());
1421 ContainerUtil.contextualize(c, this.appContext);
1422 final ComponentManager parent = this.getParentComponentManager();
1423 if (parent != null) {
1424 ContainerUtil.compose(c, parent);
1425 }
1426 if (this.enableInstrumentation) {
1427 c.setInstrumentManager(getInstrumentManager());
1428 }
1429 ContainerUtil.initialize(c);
1430 this.creationTime = System.currentTimeMillis();
1431
1432 this.cocoon = c;
1433 } catch (Exception e) {
1434 if (getLogger().isErrorEnabled()) {
1435 getLogger().error("Exception reloading", e);
1436 }
1437 this.exception = e;
1438 disposeCocoon();
1439 }
1440 }
1441
1442 private Logger getCocoonLogger() {
1443 final String rootlogger = getInitParameter("cocoon-logger");
1444 if (rootlogger != null) {
1445 return this.getLoggerManager().getLoggerForCategory(rootlogger);
1446 } else {
1447 return getLogger();
1448 }
1449 }
1450
1451 /**
1452 * Method to update the environment before Cocoon instances are created.
1453 *
1454 * This is also useful if you wish to customize any of the 'protected'
1455 * variables from this class before a Cocoon instance is built in a derivative
1456 * of this class (eg. Cocoon Context).
1457 */
1458 protected void updateEnvironment() throws ServletException {
1459 this.appContext.put(Constants.CONTEXT_CLASS_LOADER, classLoader);
1460 this.appContext.put(Constants.CONTEXT_CLASSPATH, getClassPath());
1461 }
1462
1463 /**
1464 * Helper method to obtain an <code>InstrumentManager</code> instance
1465 *
1466 * @return an <code>InstrumentManager</code> instance
1467 */
1468 private InstrumentManager getInstrumentManager()
1469 throws Exception {
1470 String imConfig = getInitParameter("instrumentation-config");
1471 if (imConfig == null) {
1472 throw new ServletException("Please define the init-param 'instrumentation-config' in your web.xml");
1473 }
1474
1475 final InputStream is = this.servletContext.getResourceAsStream(imConfig);
1476 final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
1477 final Configuration conf = builder.build(is);
1478
1479 // Get the logger for the instrument manager
1480 final String imLoggerCategory = conf.getAttribute("logger", "core.instrument");
1481 Logger imLogger = this.loggerManager.getLoggerForCategory(imLoggerCategory);
1482
1483 // Set up the Instrument Manager
1484 DefaultInstrumentManagerImpl instrumentManager = new DefaultInstrumentManagerImpl();
1485 instrumentManager.enableLogging(imLogger);
1486 instrumentManager.configure(conf);
1487 instrumentManager.initialize();
1488
1489 if (getLogger().isDebugEnabled()) {
1490 getLogger().debug("Instrument manager created " + instrumentManager);
1491 }
1492
1493 this.instrumentManager = instrumentManager;
1494 return instrumentManager;
1495 }
1496
1497 private String processTime(long time) {
1498 StringBuffer out = new StringBuffer(PROCESSED_BY);
1499 if (time <= SECOND) {
1500 out.append(time);
1501 out.append(" milliseconds.");
1502 } else if (time <= MINUTE) {
1503 out.append(time / SECOND);
1504 out.append(" seconds.");
1505 } else if (time <= HOUR) {
1506 out.append(time / MINUTE);
1507 out.append(" minutes.");
1508 } else {
1509 out.append(time / HOUR);
1510 out.append(" hours.");
1511 }
1512 return out.toString();
1513 }
1514
1515 /**
1516 * Gets the current cocoon object. Reload cocoon if configuration
1517 * changed or we are reloading.
1518 */
1519 private boolean reloadCocoon(final String pathInfo, final String reloadParam)
1520 throws ServletException {
1521 if (this.allowReload) {
1522 boolean reload = false;
1523
1524 if (this.cocoon != null) {
1525 if (this.cocoon.modifiedSince(this.creationTime)) {
1526 if (getLogger().isInfoEnabled()) {
1527 getLogger().info("Configuration changed reload attempt");
1528 }
1529 reload = true;
1530 } else if (pathInfo == null && reloadParam != null) {
1531 if (getLogger().isInfoEnabled()) {
1532 getLogger().info("Forced reload attempt");
1533 }
1534 reload = true;
1535 }
1536 } else if (pathInfo == null && reloadParam != null) {
1537 if (getLogger().isInfoEnabled()) {
1538 getLogger().info("Invalid configurations reload");
1539 }
1540 reload = true;
1541 }
1542
1543 return reload;
1544 } else {
1545 return false;
1546 }
1547 }
1548
1549 /**
1550 * Destroy Cocoon
1551 */
1552 protected final void disposeCocoon() {
1553 if (this.cocoon != null) {
1554 ContainerUtil.dispose(this.cocoon);
1555 this.cocoon = null;
1556 }
1557 }
1558
1559 /**
1560 * Get an initialisation parameter. The value is trimmed, and null is returned if the trimmed value
1561 * is empty.
1562 */
1563 public String getInitParameter(String name) {
1564 String result = super.getInitParameter(name);
1565 if (result != null) {
1566 result = result.trim();
1567 if (result.length() == 0) {
1568 result = null;
1569 }
1570 }
1571
1572 return result;
1573 }
1574
1575 /** Convenience method to access servlet parameters */
1576 protected String getInitParameter(String name, String defaultValue) {
1577 String result = getInitParameter(name);
1578 if (result == null) {
1579 if (getLogger() != null && getLogger().isDebugEnabled()) {
1580 getLogger().debug(name + " was not set - defaulting to '" + defaultValue + "'");
1581 }
1582 return defaultValue;
1583 } else {
1584 return result;
1585 }
1586 }
1587
1588 /** Convenience method to access boolean servlet parameters */
1589 protected boolean getInitParameterAsBoolean(String name, boolean defaultValue) {
1590 String value = getInitParameter(name);
1591 if (value == null) {
1592 if (getLogger() != null && getLogger().isDebugEnabled()) {
1593 getLogger().debug(name + " was not set - defaulting to '" + defaultValue + "'");
1594 }
1595 return defaultValue;
1596 }
1597
1598 return BooleanUtils.toBoolean(value);
1599 }
1600
1601 protected int getInitParameterAsInteger(String name, int defaultValue) {
1602 String value = getInitParameter(name);
1603 if (value == null) {
1604 if (getLogger() != null && getLogger().isDebugEnabled()) {
1605 getLogger().debug(name + " was not set - defaulting to '" + defaultValue + "'");
1606 }
1607 return defaultValue;
1608 } else {
1609 return Integer.parseInt(value);
1610 }
1611 }
1612
1613 protected Logger getLogger() {
1614 return this.log;
1615 }
1616
1617 protected LoggerManager getLoggerManager() {
1618 return this.loggerManager;
1619 }
1620 }