KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > portlet > CocoonPortlet


1 /*
2  * Copyright 1999-2004 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.portlet;
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.portlet.PortletContext;
43 import org.apache.cocoon.environment.portlet.PortletEnvironment;
44 import org.apache.cocoon.portlet.multipart.MultipartActionRequest;
45 import org.apache.cocoon.portlet.multipart.RequestFactory;
46 import org.apache.cocoon.util.ClassUtils;
47 import org.apache.cocoon.util.IOUtils;
48 import org.apache.cocoon.util.StringUtils;
49 import org.apache.cocoon.util.log.CocoonLogFormatter;
50 import org.apache.cocoon.util.log.Log4JConfigurator;
51
52 import org.apache.commons.lang.BooleanUtils;
53 import org.apache.commons.lang.SystemUtils;
54 import org.apache.excalibur.instrument.InstrumentManager;
55 import org.apache.excalibur.instrument.manager.impl.DefaultInstrumentManagerImpl;
56 import org.apache.log.ContextMap;
57 import org.apache.log.ErrorHandler;
58 import org.apache.log.Hierarchy;
59 import org.apache.log.Priority;
60 import org.apache.log.util.DefaultErrorHandler;
61 import org.apache.log4j.LogManager;
62
63 import javax.portlet.ActionRequest;
64 import javax.portlet.ActionResponse;
65 import javax.portlet.GenericPortlet;
66 import javax.portlet.PortletConfig;
67 import javax.portlet.PortletException;
68 import javax.portlet.PortletSession;
69 import javax.portlet.RenderRequest;
70 import javax.portlet.RenderResponse;
71 import javax.portlet.PortletRequest;
72 import java.io.File JavaDoc;
73 import java.io.FileInputStream JavaDoc;
74 import java.io.FileOutputStream JavaDoc;
75 import java.io.IOException JavaDoc;
76 import java.io.InputStream JavaDoc;
77 import java.io.OutputStream JavaDoc;
78 import java.io.PrintStream 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 JSR-168 Portlet.
94  *
95  * @version CVS $Id: CocoonPortlet.java 325950 2005-10-17 18:26:07Z vgritsenko $
96  */

97 public class CocoonPortlet extends GenericPortlet {
98
99     /**
100      * Application <code>Context</code> Key for the portlet configuration
101      * @since 2.1.3
102      */

103     public static final String JavaDoc CONTEXT_PORTLET_CONFIG = "portlet-config";
104
105     // Processing time message
106
protected static final String JavaDoc PROCESSED_BY = "Processed by "
107             + Constants.COMPLETE_NAME + " in ";
108
109     // Used by "show-time"
110
static final float SECOND = 1000;
111     static final float MINUTE = 60 * SECOND;
112     static final float HOUR = 60 * MINUTE;
113
114     private Logger log;
115     private LoggerManager loggerManager;
116
117     /**
118      * The time the cocoon instance was created
119      */

120     protected long creationTime = 0;
121
122     /**
123      * The <code>Cocoon</code> instance
124      */

125     protected Cocoon cocoon;
126
127     /**
128      * Holds exception happened during initialization (if any)
129      */

130     protected Exception JavaDoc exception;
131
132     /**
133      * Avalon application context
134      */

135     protected DefaultContext appContext = new DefaultContext();
136
137
138     /**
139      * Default value for {@link #allowReload} parameter (false)
140      */

141     protected static final boolean ALLOW_RELOAD = false;
142
143     /**
144      * Allow reloading of cocoon by specifying the <code>cocoon-reload=true</code> parameter with a request
145      */

146     protected boolean allowReload;
147
148
149     /**
150      * Allow adding processing time to the response
151      */

152     protected boolean showTime;
153
154     /**
155      * If true, processing time will be added as an HTML comment
156      */

157     protected boolean hiddenShowTime;
158
159
160     /**
161      * Default value for {@link #enableUploads} parameter (false)
162      */

163     private static final boolean ENABLE_UPLOADS = false;
164     private static final boolean SAVE_UPLOADS_TO_DISK = true;
165     private static final int MAX_UPLOAD_SIZE = 10000000; // 10Mb
166

167     /**
168      * Allow processing of upload requests (mime/multipart)
169      */

170     private boolean enableUploads;
171     private boolean autoSaveUploads;
172     private boolean allowOverwrite;
173     private boolean silentlyRename;
174     private int maxUploadSize;
175
176     private File JavaDoc uploadDir;
177     private File JavaDoc workDir;
178     private File JavaDoc cacheDir;
179     private String JavaDoc containerEncoding;
180     private String JavaDoc defaultFormEncoding;
181
182     protected javax.portlet.PortletContext portletContext;
183
184     /** The classloader that will be set as the context classloader if init-classloader is true */
185     protected ClassLoader JavaDoc classLoader = this.getClass().getClassLoader();
186     protected boolean initClassLoader = false;
187
188     private String JavaDoc parentComponentManagerClass;
189     private String JavaDoc parentComponentManagerInitParam;
190
191     /** The parent ComponentManager, if any. Stored here in order to be able to dispose it in destroy(). */
192     private ComponentManager parentComponentManager;
193
194     protected String JavaDoc forceLoadParameter;
195     protected String JavaDoc forceSystemProperty;
196
197     /**
198      * If true or not set, this class will try to catch and handle all Cocoon exceptions.
199      * If false, it will rethrow them to the portlet container.
200      */

201     private boolean manageExceptions;
202
203     /**
204      * Flag to enable avalon excalibur instrumentation of Cocoon.
205      */

206     private boolean enableInstrumentation;
207
208     /**
209      * The <code>InstrumentManager</code> instance
210      */

211     private InstrumentManager instrumentManager;
212
213     /**
214      * This is the path to the portlet context (or the result
215      * of calling getRealPath('/') on the PortletContext.
216      * Note, that this can be null.
217      */

218     protected String JavaDoc portletContextPath;
219
220     /**
221      * This is the url to the portlet context directory
222      */

223     protected String JavaDoc portletContextURL;
224
225     /**
226      * The RequestFactory is responsible for wrapping multipart-encoded
227      * forms and for handing the file payload of incoming requests
228      */

229     protected RequestFactory requestFactory;
230
231     /**
232      * Value to be used as servletPath in the request.
233      * Provided via configuration parameter, if missing, defaults to the
234      * '/portlets/' + portletName.
235      */

236     protected String JavaDoc servletPath;
237
238     /**
239      * Default scope for the session attributes, either
240      * {@link javax.portlet.PortletSession#PORTLET_SCOPE} or
241      * {@link javax.portlet.PortletSession#APPLICATION_SCOPE}.
242      * This corresponds to <code>default-session-scope</code>
243      * parameter, with default value <code>portlet</code>.
244      *
245      * @see org.apache.cocoon.environment.portlet.PortletSession
246      */

247     protected int defaultSessionScope;
248
249     /**
250      * Store pathInfo in session
251      */

252     protected boolean storeSessionPath;
253
254     /**
255      * Initialize this <code>CocoonPortlet</code> instance.
256      *
257      * <p>Uses the following parameters:
258      * portlet-logger
259      * enable-uploads
260      * autosave-uploads
261      * overwrite-uploads
262      * upload-max-size
263      * show-time
264      * container-encoding
265      * form-encoding
266      * manage-exceptions
267      * servlet-path
268      *
269      * @param conf The PortletConfig object from the portlet container.
270      * @throws PortletException
271      */

272     public void init(PortletConfig conf) throws PortletException {
273
274         super.init(conf);
275
276         // Check the init-classloader parameter only if it's not already true.
277
// This is useful for subclasses of this portlet that override the value
278
// initially set by this class (i.e. false).
279
if (!this.initClassLoader) {
280             this.initClassLoader = getInitParameterAsBoolean("init-classloader", false);
281         }
282
283         if (this.initClassLoader) {
284             // Force context classloader so that JAXP can work correctly
285
// (see javax.xml.parsers.FactoryFinder.findClassLoader())
286
try {
287                 Thread.currentThread().setContextClassLoader(this.classLoader);
288             } catch (Exception JavaDoc e) {
289             }
290         }
291
292         try {
293             // FIXME (VG): We shouldn't have to specify these. Need to override
294
// jaxp implementation of weblogic before initializing logger.
295
// This piece of code is also required in the Cocoon class.
296
String JavaDoc value = System.getProperty("javax.xml.parsers.SAXParserFactory");
297             if (value != null && value.startsWith("weblogic")) {
298                 System.setProperty("javax.xml.parsers.SAXParserFactory", "org.apache.xerces.jaxp.SAXParserFactoryImpl");
299                 System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
300             }
301         } catch (SecurityException JavaDoc e) {
302             // Ignore security exception
303
System.out.println("CocoonPortlet: Could not check system properties, got: " + e);
304         }
305
306         this.portletContext = conf.getPortletContext();
307         this.appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, new PortletContext(this.portletContext));
308         this.portletContextPath = this.portletContext.getRealPath("/");
309
310         // first init the work-directory for the logger.
311
// this is required if we are running inside a war file!
312
final String JavaDoc workDirParam = getInitParameter("work-directory");
313         if (workDirParam != null) {
314             if (this.portletContextPath == null) {
315                 // No context path : consider work-directory as absolute
316
this.workDir = new File JavaDoc(workDirParam);
317             } else {
318                 // Context path exists : is work-directory absolute ?
319
File JavaDoc workDirParamFile = new File JavaDoc(workDirParam);
320                 if (workDirParamFile.isAbsolute()) {
321                     // Yes : keep it as is
322
this.workDir = workDirParamFile;
323                 } else {
324                     // No : consider it relative to context path
325
this.workDir = new File JavaDoc(portletContextPath, workDirParam);
326                 }
327             }
328         } else {
329             // TODO: Check portlet specification
330
this.workDir = (File JavaDoc) this.portletContext.getAttribute("javax.servlet.context.tempdir");
331             if (this.workDir == null) {
332                 this.workDir = new File JavaDoc(this.portletContext.getRealPath("/WEB-INF/work"));
333             }
334             this.workDir = new File JavaDoc(workDir, "cocoon-files");
335         }
336         this.workDir.mkdirs();
337         this.appContext.put(Constants.CONTEXT_WORK_DIR, workDir);
338
339         String JavaDoc path = this.portletContextPath;
340         // these two variables are just for debugging. We can't log at this point
341
// as the logger isn't initialized yet.
342
String JavaDoc debugPathOne = null, debugPathTwo = null;
343         if (path == null) {
344             // Try to figure out the path of the root from that of WEB-INF
345
try {
346                 path = this.portletContext.getResource("/WEB-INF").toString();
347             } catch (MalformedURLException JavaDoc me) {
348                 throw new PortletException("Unable to get resource 'WEB-INF'.", me);
349             }
350             debugPathOne = path;
351             path = path.substring(0, path.length() - "WEB-INF".length());
352             debugPathTwo = path;
353         }
354
355         try {
356             if (path.indexOf(':') > 1) {
357                 this.portletContextURL = path;
358             } else {
359                 this.portletContextURL = new File JavaDoc(path).toURL().toExternalForm();
360             }
361         } catch (MalformedURLException JavaDoc me) {
362             // VG: Novell has absolute file names starting with the
363
// volume name which is easily more then one letter.
364
// Examples: sys:/apache/cocoon or sys:\apache\cocoon
365
try {
366                 this.portletContextURL = new File JavaDoc(path).toURL().toExternalForm();
367             } catch (MalformedURLException JavaDoc ignored) {
368                 throw new PortletException("Unable to determine portlet context URL.", me);
369             }
370         }
371         try {
372             this.appContext.put("context-root", new URL JavaDoc(this.portletContextURL));
373         } catch (MalformedURLException JavaDoc ignore) {
374             // we simply ignore this
375
}
376
377         // Init logger
378
initLogger();
379
380         if (getLogger().isDebugEnabled()) {
381             getLogger().debug("getRealPath for /: " + this.portletContextPath);
382             if (this.portletContextPath == null) {
383                 getLogger().debug("getResource for /WEB-INF: " + debugPathOne);
384                 getLogger().debug("Path for Root: " + debugPathTwo);
385             }
386         }
387
388         this.forceLoadParameter = getInitParameter("load-class", null);
389         this.forceSystemProperty = getInitParameter("force-property", null);
390
391         // Output some debug info
392
if (getLogger().isDebugEnabled()) {
393             getLogger().debug("Portlet Context URL: " + this.portletContextURL);
394             if (workDirParam != null) {
395                 getLogger().debug("Using work-directory " + this.workDir);
396             } else {
397                 getLogger().debug("Using default work-directory " + this.workDir);
398             }
399         }
400
401         final String JavaDoc uploadDirParam = conf.getInitParameter("upload-directory");
402         if (uploadDirParam != null) {
403             if (this.portletContextPath == null) {
404                 this.uploadDir = new File JavaDoc(uploadDirParam);
405             } else {
406                 // Context path exists : is upload-directory absolute ?
407
File JavaDoc uploadDirParamFile = new File JavaDoc(uploadDirParam);
408                 if (uploadDirParamFile.isAbsolute()) {
409                     // Yes : keep it as is
410
this.uploadDir = uploadDirParamFile;
411                 } else {
412                     // No : consider it relative to context path
413
this.uploadDir = new File JavaDoc(portletContextPath, uploadDirParam);
414                 }
415             }
416             if (getLogger().isDebugEnabled()) {
417                 getLogger().debug("Using upload-directory " + this.uploadDir);
418             }
419         } else {
420             this.uploadDir = new File JavaDoc(workDir, "upload-dir" + File.separator);
421             if (getLogger().isDebugEnabled()) {
422                 getLogger().debug("Using default upload-directory " + this.uploadDir);
423             }
424         }
425         this.uploadDir.mkdirs();
426         this.appContext.put(Constants.CONTEXT_UPLOAD_DIR, this.uploadDir);
427
428         this.enableUploads = getInitParameterAsBoolean("enable-uploads", ENABLE_UPLOADS);
429
430         this.autoSaveUploads = getInitParameterAsBoolean("autosave-uploads", SAVE_UPLOADS_TO_DISK);
431
432         String JavaDoc overwriteParam = getInitParameter("overwrite-uploads", "rename");
433         // accepted values are deny|allow|rename - rename is default.
434
if ("deny".equalsIgnoreCase(overwriteParam)) {
435             this.allowOverwrite = false;
436             this.silentlyRename = false;
437         } else if ("allow".equalsIgnoreCase(overwriteParam)) {
438             this.allowOverwrite = true;
439             this.silentlyRename = false; // ignored in this case
440
} else {
441             // either rename is specified or unsupported value - default to rename.
442
this.allowOverwrite = false;
443             this.silentlyRename = true;
444         }
445
446         this.maxUploadSize = getInitParameterAsInteger("upload-max-size", MAX_UPLOAD_SIZE);
447
448         String JavaDoc cacheDirParam = conf.getInitParameter("cache-directory");
449         if (cacheDirParam != null) {
450             if (this.portletContextPath == null) {
451                 this.cacheDir = new File JavaDoc(cacheDirParam);
452             } else {
453                 // Context path exists : is cache-directory absolute ?
454
File JavaDoc cacheDirParamFile = new File JavaDoc(cacheDirParam);
455                 if (cacheDirParamFile.isAbsolute()) {
456                     // Yes : keep it as is
457
this.cacheDir = cacheDirParamFile;
458                 } else {
459                     // No : consider it relative to context path
460
this.cacheDir = new File JavaDoc(portletContextPath, cacheDirParam);
461                 }
462             }
463             if (getLogger().isDebugEnabled()) {
464                 getLogger().debug("Using cache-directory " + this.cacheDir);
465             }
466         } else {
467             this.cacheDir = IOUtils.createFile(workDir, "cache-dir" + File.separator);
468             if (getLogger().isDebugEnabled()) {
469                 getLogger().debug("cache-directory was not set - defaulting to " + this.cacheDir);
470             }
471         }
472         this.cacheDir.mkdirs();
473         this.appContext.put(Constants.CONTEXT_CACHE_DIR, this.cacheDir);
474
475         this.appContext.put(Constants.CONTEXT_CONFIG_URL,
476                             getConfigFile(conf.getInitParameter("configurations")));
477         if (conf.getInitParameter("configurations") == null) {
478             if (getLogger().isDebugEnabled()) {
479                 getLogger().debug("configurations was not set - defaulting to... ?");
480             }
481         }
482
483         // get allow reload parameter, default is true
484
this.allowReload = getInitParameterAsBoolean("allow-reload", ALLOW_RELOAD);
485
486         String JavaDoc value = conf.getInitParameter("show-time");
487         this.showTime = BooleanUtils.toBoolean(value) || (this.hiddenShowTime = "hide".equals(value));
488         if (value == null) {
489             if (getLogger().isDebugEnabled()) {
490                 getLogger().debug("show-time was not set - defaulting to false");
491             }
492         }
493
494         parentComponentManagerClass = getInitParameter("parent-component-manager", null);
495         if (parentComponentManagerClass != null) {
496             int dividerPos = parentComponentManagerClass.indexOf('/');
497             if (dividerPos != -1) {
498                 parentComponentManagerInitParam = parentComponentManagerClass.substring(dividerPos + 1);
499                 parentComponentManagerClass = parentComponentManagerClass.substring(0, dividerPos);
500             }
501         }
502
503         this.containerEncoding = getInitParameter("container-encoding", "ISO-8859-1");
504         this.defaultFormEncoding = getInitParameter("form-encoding", "ISO-8859-1");
505
506         this.appContext.put(Constants.CONTEXT_DEFAULT_ENCODING, this.defaultFormEncoding);
507         this.manageExceptions = getInitParameterAsBoolean("manage-exceptions", true);
508
509         this.enableInstrumentation = getInitParameterAsBoolean("enable-instrumentation", false);
510
511         this.requestFactory = new RequestFactory(this.autoSaveUploads,
512                                                  this.uploadDir,
513                                                  this.allowOverwrite,
514                                                  this.silentlyRename,
515                                                  this.maxUploadSize,
516                                                  this.defaultFormEncoding);
517
518         this.servletPath = getInitParameter("servlet-path", null);
519         if (this.servletPath != null) {
520             if (this.servletPath.startsWith("/")) {
521                 this.servletPath = this.servletPath.substring(1);
522             }
523             if (this.servletPath.endsWith("/")) {
524                 this.servletPath = servletPath.substring(0, servletPath.length() - 1);
525             }
526         }
527
528         final String JavaDoc sessionScopeParam = getInitParameter("default-session-scope", "portlet");
529         if ("application".equalsIgnoreCase(sessionScopeParam)) {
530             this.defaultSessionScope = javax.portlet.PortletSession.APPLICATION_SCOPE;
531         } else {
532             this.defaultSessionScope = javax.portlet.PortletSession.PORTLET_SCOPE;
533         }
534
535         // Add the portlet configuration
536
this.appContext.put(CONTEXT_PORTLET_CONFIG, conf);
537         this.createCocoon();
538     }
539
540     /**
541      * Dispose Cocoon when portlet is destroyed
542      */

543     public void destroy() {
544         if (this.initClassLoader) {
545             try {
546                 Thread.currentThread().setContextClassLoader(this.classLoader);
547             } catch (Exception JavaDoc e) {
548             }
549         }
550
551         if (this.cocoon != null) {
552             if (getLogger().isDebugEnabled()) {
553                 getLogger().debug("Portlet destroyed - disposing Cocoon");
554             }
555             this.disposeCocoon();
556         }
557
558         if (this.instrumentManager instanceof Disposable) {
559             ((Disposable) this.instrumentManager).dispose();
560         }
561
562         if (this.parentComponentManager != null && this.parentComponentManager instanceof Disposable) {
563             ((Disposable) this.parentComponentManager).dispose();
564         }
565     }
566
567     /**
568      * Adds an URL to the classloader. Does nothing here, but can
569      * be overriden.
570      */

571     protected void addClassLoaderURL(URL JavaDoc URL) {
572         // Nothing
573
}
574
575     /**
576      * Adds a directory to the classloader. Does nothing here, but can
577      * be overriden.
578      */

579     protected void addClassLoaderDirectory(String JavaDoc dir) {
580         // Nothing
581
}
582
583     /**
584      * This builds the important ClassPath used by this Portlet. It
585      * does so in a Portlet Engine neutral way. It uses the
586      * <code>PortletContext</code>'s <code>getRealPath</code> method
587      * to get the Portlet identified classes and lib directories.
588      * It iterates in alphabetical order through every file in the
589      * lib directory and adds it to the classpath.
590      *
591      * Also, we add the files to the ClassLoader for the Cocoon system.
592      * In order to protect ourselves from skitzofrantic classloaders,
593      * we need to work with a known one.
594      *
595      * We need to get this to work properly when Cocoon is in a war.
596      *
597      * @throws PortletException
598      */

599     protected String JavaDoc getClassPath() throws PortletException {
600         StringBuffer JavaDoc buildClassPath = new StringBuffer JavaDoc();
601
602         File JavaDoc root = null;
603         if (portletContextPath != null) {
604             // Old method. There *MUST* be a better method than this...
605

606             String JavaDoc classDir = this.portletContext.getRealPath("/WEB-INF/classes");
607             String JavaDoc libDir = this.portletContext.getRealPath("/WEB-INF/lib");
608
609             if (libDir != null) {
610                 root = new File JavaDoc(libDir);
611             }
612
613             if (classDir != null) {
614                 buildClassPath.append(classDir);
615
616                 addClassLoaderDirectory(classDir);
617             }
618         } else {
619             // New(ish) method for war'd deployments
620
URL JavaDoc classDirURL = null;
621             URL JavaDoc libDirURL = null;
622
623             try {
624                 classDirURL = this.portletContext.getResource("/WEB-INF/classes");
625             } catch (MalformedURLException JavaDoc me) {
626                 if (getLogger().isWarnEnabled()) {
627                     this.getLogger().warn("Unable to add WEB-INF/classes to the classpath", me);
628                 }
629             }
630
631             try {
632                 libDirURL = this.portletContext.getResource("/WEB-INF/lib");
633             } catch (MalformedURLException JavaDoc me) {
634                 if (getLogger().isWarnEnabled()) {
635                     this.getLogger().warn("Unable to add WEB-INF/lib to the classpath", me);
636                 }
637             }
638
639             if (libDirURL != null && libDirURL.toExternalForm().startsWith("file:")) {
640                 root = new File JavaDoc(libDirURL.toExternalForm().substring("file:".length()));
641             }
642
643             if (classDirURL != null) {
644                 buildClassPath.append(classDirURL.toExternalForm());
645
646                 addClassLoaderURL(classDirURL);
647             }
648         }
649
650         // Unable to find lib directory. Going the hard way.
651
if (root == null) {
652             root = extractLibraries();
653         }
654
655         if (root != null && root.isDirectory()) {
656             File JavaDoc[] libraries = root.listFiles();
657             Arrays.sort(libraries);
658             for (int i = 0; i < libraries.length; i++) {
659                 String JavaDoc fullName = IOUtils.getFullFilename(libraries[i]);
660                 buildClassPath.append(File.pathSeparatorChar).append(fullName);
661
662                 addClassLoaderDirectory(fullName);
663             }
664         }
665
666         buildClassPath.append(File.pathSeparatorChar)
667                       .append(SystemUtils.JAVA_CLASS_PATH);
668
669         buildClassPath.append(File.pathSeparatorChar)
670                       .append(getExtraClassPath());
671         return buildClassPath.toString();
672     }
673
674     private File JavaDoc extractLibraries() {
675         try {
676             URL JavaDoc manifestURL = this.portletContext.getResource("/META-INF/MANIFEST.MF");
677             if (manifestURL == null) {
678                 this.getLogger().fatalError("Unable to get Manifest");
679                 return null;
680             }
681
682             Manifest JavaDoc mf = new Manifest JavaDoc(manifestURL.openStream());
683             Attributes JavaDoc attr = mf.getMainAttributes();
684             String JavaDoc libValue = attr.getValue("Cocoon-Libs");
685             if (libValue == null) {
686                 this.getLogger().fatalError("Unable to get 'Cocoon-Libs' attribute from the Manifest");
687                 return null;
688             }
689
690             List JavaDoc libList = new ArrayList JavaDoc();
691             for (StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(libValue, " "); st.hasMoreTokens();) {
692                 libList.add(st.nextToken());
693             }
694
695             File JavaDoc root = new File JavaDoc(this.workDir, "lib");
696             root.mkdirs();
697
698             File JavaDoc[] oldLibs = root.listFiles();
699             for (int i = 0; i < oldLibs.length; i++) {
700                 String JavaDoc oldLib = oldLibs[i].getName();
701                 if (!libList.contains(oldLib)) {
702                     this.getLogger().debug("Removing old library " + oldLibs[i]);
703                     oldLibs[i].delete();
704                 }
705             }
706
707             this.getLogger().warn("Extracting libraries into " + root);
708             byte[] buffer = new byte[65536];
709             for (Iterator JavaDoc i = libList.iterator(); i.hasNext();) {
710                 String JavaDoc libName = (String JavaDoc) i.next();
711
712                 long lastModified = -1;
713                 try {
714                     lastModified = Long.parseLong(attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
715                 } catch (Exception JavaDoc e) {
716                     this.getLogger().debug("Failed to parse lastModified: " + attr.getValue("Cocoon-Lib-" + libName.replace('.', '_')));
717                 }
718
719                 File JavaDoc lib = new File JavaDoc(root, libName);
720                 if (lib.exists() && lib.lastModified() != lastModified) {
721                     this.getLogger().debug("Removing modified library " + lib);
722                     lib.delete();
723                 }
724
725                 InputStream JavaDoc is = this.portletContext.getResourceAsStream("/WEB-INF/lib/" + libName);
726                 if (is == null) {
727                     this.getLogger().warn("Skipping " + libName);
728                 } else {
729                     this.getLogger().debug("Extracting " + libName);
730                     OutputStream JavaDoc os = null;
731                     try {
732                         os = new FileOutputStream JavaDoc(lib);
733             &nbs