KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > servlet > CocoonServlet


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
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  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.servlet;
17
18 import org.apache.avalon.excalibur.logger.Log4JLoggerManager;
19 import org.apache.avalon.excalibur.logger.LogKitLoggerManager;
20 import org.apache.avalon.excalibur.logger.LoggerManager;
21 import org.apache.avalon.framework.activity.Disposable;
22 import org.apache.avalon.framework.activity.Initializable;
23 import org.apache.avalon.framework.component.ComponentManager;
24 import org.apache.avalon.framework.configuration.Configurable;
25 import org.apache.avalon.framework.configuration.Configuration;
26 import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
27 import org.apache.avalon.framework.container.ContainerUtil;
28 import org.apache.avalon.framework.context.Contextualizable;
29 import org.apache.avalon.framework.context.DefaultContext;
30 import org.apache.avalon.framework.logger.LogEnabled;
31 import org.apache.avalon.framework.logger.LogKitLogger;
32 import org.apache.avalon.framework.logger.Logger;
33
34 import org.apache.cocoon.Cocoon;
35 import org.apache.cocoon.ConnectionResetException;
36 import org.apache.cocoon.Constants;
37 import org.apache.cocoon.ResourceNotFoundException;
38 import org.apache.cocoon.components.notification.DefaultNotifyingBuilder;
39 import org.apache.cocoon.components.notification.Notifier;
40 import org.apache.cocoon.components.notification.Notifying;
41 import org.apache.cocoon.environment.Environment;
42 import org.apache.cocoon.environment.http.HttpContext;
43 import org.apache.cocoon.environment.http.HttpEnvironment;
44 import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest;
45 import org.apache.cocoon.servlet.multipart.RequestFactory;
46 import org.apache.cocoon.util.ClassUtils;
47 import org.apache.cocoon.util.Deprecation;
48 import org.apache.cocoon.util.IOUtils;
49 import org.apache.cocoon.util.StringUtils;
50 import org.apache.cocoon.util.log.CocoonLogFormatter;
51 import org.apache.cocoon.util.log.Log4JConfigurator;
52
53 import org.apache.commons.lang.BooleanUtils;
54 import org.apache.commons.lang.SystemUtils;
55 import org.apache.commons.lang.time.StopWatch;
56 import org.apache.excalibur.instrument.InstrumentManager;
57 import org.apache.excalibur.instrument.manager.impl.DefaultInstrumentManagerImpl;
58 import org.apache.log.ContextMap;
59 import org.apache.log.ErrorHandler;
60 import org.apache.log.Hierarchy;
61 import org.apache.log.Priority;
62 import org.apache.log.output.ServletOutputLogTarget;
63 import org.apache.log.util.DefaultErrorHandler;
64 import org.apache.log4j.LogManager;
65
66 import javax.servlet.ServletConfig JavaDoc;
67 import javax.servlet.ServletContext JavaDoc;
68 import javax.servlet.ServletException JavaDoc;
69 import javax.servlet.ServletOutputStream JavaDoc;
70 import javax.servlet.http.HttpServlet JavaDoc;
71 import javax.servlet.http.HttpServletRequest JavaDoc;
72 import javax.servlet.http.HttpServletResponse JavaDoc;
73 import java.io.File JavaDoc;
74 import java.io.FileInputStream JavaDoc;
75 import java.io.FileOutputStream JavaDoc;
76 import java.io.IOException JavaDoc;
77 import java.io.InputStream JavaDoc;
78 import java.io.OutputStream JavaDoc;
79 import java.lang.reflect.Constructor JavaDoc;
80 import java.net.MalformedURLException JavaDoc;
81 import java.net.SocketException JavaDoc;
82 import java.net.URL JavaDoc;
83 import java.util.ArrayList JavaDoc;
84 import java.util.Arrays JavaDoc;
85 import java.util.HashMap JavaDoc;
86 import java.util.Iterator JavaDoc;
87 import java.util.List JavaDoc;
88 import java.util.StringTokenizer JavaDoc;
89 import java.util.jar.Attributes JavaDoc;
90 import java.util.jar.Manifest JavaDoc;
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 325950 2005-10-17 18:26:07Z vgritsenko $
103  */

104 public class CocoonServlet extends HttpServlet JavaDoc {
105
106     /**
107      * Application <code>Context</code> Key for the servlet configuration
108      * @since 2.1.3
109      */

110     public static final String JavaDoc CONTEXT_SERVLET_CONFIG = "servlet-config";
111
112     // Processing time message
113
protected static final String JavaDoc 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 JavaDoc 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 JavaDoc uploadDir;
187     private File JavaDoc workDir;
188     private File JavaDoc cacheDir;
189     private String JavaDoc containerEncoding;
190     private String JavaDoc defaultFormEncoding;
191
192     protected ServletContext JavaDoc servletContext;
193
194     /** The classloader that will be set as the context classloader if init-classloader is true */
195     protected ClassLoader JavaDoc classLoader = this.getClass().getClassLoader();
196     protected boolean initClassLoader = false;
197
198     private String JavaDoc parentComponentManagerClass;
199     private String JavaDoc 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 JavaDoc forceLoadParameter;
205     protected String JavaDoc 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 JavaDoc servletContextPath;
229
230     /**
231      * This is the url to the servlet context directory
232      */

233     protected String JavaDoc 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 JavaDoc conf)
254     throws ServletException JavaDoc {
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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc(workDirParam);
300             } else {
301                 // Context path exists : is work-directory absolute ?
302
File JavaDoc workDirParamFile = new File JavaDoc(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 JavaDoc(servletContextPath, workDirParam);
309                 }
310             }
311         } else {
312             this.workDir = (File JavaDoc) this.servletContext.getAttribute("javax.servlet.context.tempdir");
313             this.workDir = new File JavaDoc(workDir, "cocoon-files");
314         }
315         this.workDir.mkdirs();
316         this.appContext.put(Constants.CONTEXT_WORK_DIR, workDir);
317
318         String JavaDoc 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 JavaDoc 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 JavaDoc me) {
327                 throw new ServletException JavaDoc("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 JavaDoc(path).toURL().toExternalForm();
338             }
339         } catch (MalformedURLException JavaDoc 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 JavaDoc(path).toURL().toExternalForm();
345             } catch (MalformedURLException JavaDoc ignored) {
346                 throw new ServletException JavaDoc("Unable to determine servlet context URL.", me);
347             }
348         }
349         try {
350             this.appContext.put("context-root", new URL JavaDoc(this.servletContextURL));
351         } catch (MalformedURLException JavaDoc 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 JavaDoc uploadDirParam = conf.getInitParameter("upload-directory");
380         if (uploadDirParam != null) {
381             if (this.servletContextPath == null) {
382                 this.uploadDir = new File JavaDoc(uploadDirParam);
383             } else {
384                 // Context path exists : is upload-directory absolute ?
385
File JavaDoc uploadDirParamFile = new File JavaDoc(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 JavaDoc(servletContextPath, uploadDirParam);
392                 }
393             }
394             if (getLogger().isDebugEnabled()) {
395                 getLogger().debug("Using upload-directory " + this.uploadDir);
396             }
397         } else {
398             this.uploadDir = new File JavaDoc(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 JavaDoc 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 JavaDoc cacheDirParam = conf.getInitParameter("cache-directory");
427         if (cacheDirParam != null) {
428             if (this.servletContextPath == null) {
429                 this.cacheDir = new File JavaDoc(cacheDirParam);
430             } else {
431                 // Context path exists : is cache-directory absolute ?
432
File JavaDoc cacheDirParamFile = new File JavaDoc(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 JavaDoc(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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc getClassPath() throws ServletException JavaDoc {
568         StringBuffer JavaDoc buildClassPath = new StringBuffer JavaDoc();
569
570         File JavaDoc root = null;
571         if (servletContextPath != null) {
572             // Old method. There *MUST* be a better method than this...
573

574             String JavaDoc classDir = this.servletContext.getRealPath("/WEB-INF/classes");
575             String JavaDoc libDir = this.servletContext.getRealPath("/WEB-INF/lib");
576
577             if (libDir != null) {
578                 root = new File JavaDoc(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 JavaDoc classDirURL = null;
589             URL JavaDoc libDirURL = null;
590
591             try {
592                 classDirURL = this.servletContext.getResource("/WEB-INF/classes");
593             } catch (MalformedURLException JavaDoc 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 JavaDoc 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 JavaDoc(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 JavaDoc[] libraries = root.listFiles();
625             Arrays.sort(libraries);
626             for (int i = 0; i < libraries.length; i++) {
627                 String JavaDoc 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 JavaDoc extractLibraries() {
643         try {
644             URL JavaDoc 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 JavaDoc mf = new Manifest JavaDoc(manifestURL.openStream());
651             Attributes JavaDoc attr = mf.getMainAttributes();
652             String JavaDoc 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 JavaDoc libList = new ArrayList JavaDoc();
659             for (StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(libValue, " "); st.hasMoreTokens();) {
660                 libList.add(st.nextToken());
661             }
662
663             File JavaDoc root = new File JavaDoc(this.workDir, "lib");
664             root.mkdirs();
665
666             File JavaDoc[] oldLibs = root.listFiles();
667             for (int i = 0; i < oldLibs.length; i++) {
668                 String JavaDoc 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 JavaDoc i = libList.iterator(); i.hasNext();) {
678                 String JavaDoc libName = (String JavaDoc) i.next();
679
680                 long lastModified = -1;
681                 try {
682                     lastModified = Long.parseLong(attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
683                 } catch (Exception JavaDoc e) {
684                     this.getLogger().debug("Failed to parse lastModified: " + attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
685                 }
686
687                 File JavaDoc lib = new File JavaDoc(root, libName);
688                 if (lib.exists() && lib.lastModified() != lastModified) {
689                     this.getLogger().debug("Removing modified library " + lib);
690                     lib.delete();
691                 }
692
693                 InputStream JavaDoc is = this.servletContext.getResourceAsStream("/WEB-INF/lib/" + libName);
694                 if (is == null) {
695                     this.getLogger().warn("Skipping " + libName);
696                 } else {
697                     this.getLogger().debug("Extracting " + libName);
698                     OutputStream JavaDoc os = null;
699                     try {
700                         os = new FileOutputStream JavaDoc(lib);
701                         int count;
702                         while ((count = is.read(buffer)) > 0) {
703                             os.write(buffer, 0, count);
704                         }
705                     } finally {
706                         if (is != null) is.close();
707                         if (os != null) os.close();
708                     }
709                 }
710
711                 if (lastModified != -1) {
712                     lib.setLastModified(lastModified);
713                 }
714             }
715
716             return root;
717         } catch (IOException JavaDoc e) {
718             this.getLogger().fatalError(