0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.portlet;
0018:
0019: import org.apache.avalon.excalibur.logger.Log4JLoggerManager;
0020: import org.apache.avalon.excalibur.logger.LogKitLoggerManager;
0021: import org.apache.avalon.excalibur.logger.LoggerManager;
0022: import org.apache.avalon.framework.activity.Disposable;
0023: import org.apache.avalon.framework.activity.Initializable;
0024: import org.apache.avalon.framework.component.ComponentManager;
0025: import org.apache.avalon.framework.configuration.Configurable;
0026: import org.apache.avalon.framework.configuration.Configuration;
0027: import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
0028: import org.apache.avalon.framework.container.ContainerUtil;
0029: import org.apache.avalon.framework.context.Contextualizable;
0030: import org.apache.avalon.framework.context.DefaultContext;
0031: import org.apache.avalon.framework.logger.LogEnabled;
0032: import org.apache.avalon.framework.logger.LogKitLogger;
0033: import org.apache.avalon.framework.logger.Logger;
0034:
0035: import org.apache.cocoon.Cocoon;
0036: import org.apache.cocoon.ConnectionResetException;
0037: import org.apache.cocoon.Constants;
0038: import org.apache.cocoon.ResourceNotFoundException;
0039: import org.apache.cocoon.components.notification.DefaultNotifyingBuilder;
0040: import org.apache.cocoon.components.notification.Notifier;
0041: import org.apache.cocoon.components.notification.Notifying;
0042: import org.apache.cocoon.environment.Environment;
0043: import org.apache.cocoon.environment.portlet.PortletContext;
0044: import org.apache.cocoon.environment.portlet.PortletEnvironment;
0045: import org.apache.cocoon.portlet.multipart.MultipartActionRequest;
0046: import org.apache.cocoon.portlet.multipart.RequestFactory;
0047: import org.apache.cocoon.util.ClassUtils;
0048: import org.apache.cocoon.util.IOUtils;
0049: import org.apache.cocoon.util.StringUtils;
0050: import org.apache.cocoon.util.log.CocoonLogFormatter;
0051: import org.apache.cocoon.util.log.Log4JConfigurator;
0052:
0053: import org.apache.commons.lang.BooleanUtils;
0054: import org.apache.commons.lang.SystemUtils;
0055: import org.apache.excalibur.instrument.InstrumentManager;
0056: import org.apache.excalibur.instrument.manager.impl.DefaultInstrumentManagerImpl;
0057: import org.apache.log.ContextMap;
0058: import org.apache.log.ErrorHandler;
0059: import org.apache.log.Hierarchy;
0060: import org.apache.log.Priority;
0061: import org.apache.log.util.DefaultErrorHandler;
0062: import org.apache.log4j.LogManager;
0063:
0064: import javax.portlet.ActionRequest;
0065: import javax.portlet.ActionResponse;
0066: import javax.portlet.GenericPortlet;
0067: import javax.portlet.PortletConfig;
0068: import javax.portlet.PortletException;
0069: import javax.portlet.PortletSession;
0070: import javax.portlet.RenderRequest;
0071: import javax.portlet.RenderResponse;
0072: import javax.portlet.PortletRequest;
0073: import java.io.File;
0074: import java.io.FileInputStream;
0075: import java.io.FileOutputStream;
0076: import java.io.IOException;
0077: import java.io.InputStream;
0078: import java.io.OutputStream;
0079: import java.io.PrintStream;
0080: import java.lang.reflect.Constructor;
0081: import java.net.MalformedURLException;
0082: import java.net.URL;
0083: import java.util.ArrayList;
0084: import java.util.Arrays;
0085: import java.util.HashMap;
0086: import java.util.Iterator;
0087: import java.util.List;
0088: import java.util.StringTokenizer;
0089: import java.util.jar.Attributes;
0090: import java.util.jar.Manifest;
0091:
0092: /**
0093: * This is the entry point for Cocoon execution as an JSR-168 Portlet.
0094: *
0095: * @version CVS $Id: CocoonPortlet.java 433543 2006-08-22 06:22:54Z crossley $
0096: */
0097: public class CocoonPortlet extends GenericPortlet {
0098:
0099: /**
0100: * Application <code>Context</code> Key for the portlet configuration
0101: * @since 2.1.3
0102: */
0103: public static final String CONTEXT_PORTLET_CONFIG = "portlet-config";
0104:
0105: // Processing time message
0106: protected static final String PROCESSED_BY = "Processed by "
0107: + Constants.COMPLETE_NAME + " in ";
0108:
0109: // Used by "show-time"
0110: static final float SECOND = 1000;
0111: static final float MINUTE = 60 * SECOND;
0112: static final float HOUR = 60 * MINUTE;
0113:
0114: private Logger log;
0115: private LoggerManager loggerManager;
0116:
0117: /**
0118: * The time the cocoon instance was created
0119: */
0120: protected long creationTime = 0;
0121:
0122: /**
0123: * The <code>Cocoon</code> instance
0124: */
0125: protected Cocoon cocoon;
0126:
0127: /**
0128: * Holds exception happened during initialization (if any)
0129: */
0130: protected Exception exception;
0131:
0132: /**
0133: * Avalon application context
0134: */
0135: protected DefaultContext appContext = new DefaultContext();
0136:
0137: /**
0138: * Default value for {@link #allowReload} parameter (false)
0139: */
0140: protected static final boolean ALLOW_RELOAD = false;
0141:
0142: /**
0143: * Allow reloading of cocoon by specifying the <code>cocoon-reload=true</code> parameter with a request
0144: */
0145: protected boolean allowReload;
0146:
0147: /**
0148: * Allow adding processing time to the response
0149: */
0150: protected boolean showTime;
0151:
0152: /**
0153: * If true, processing time will be added as an HTML comment
0154: */
0155: protected boolean hiddenShowTime;
0156:
0157: /**
0158: * Default value for {@link #enableUploads} parameter (false)
0159: */
0160: private static final boolean ENABLE_UPLOADS = false;
0161: private static final boolean SAVE_UPLOADS_TO_DISK = true;
0162: private static final int MAX_UPLOAD_SIZE = 10000000; // 10Mb
0163:
0164: /**
0165: * Allow processing of upload requests (mime/multipart)
0166: */
0167: private boolean enableUploads;
0168: private boolean autoSaveUploads;
0169: private boolean allowOverwrite;
0170: private boolean silentlyRename;
0171: private int maxUploadSize;
0172:
0173: private File uploadDir;
0174: private File workDir;
0175: private File cacheDir;
0176: private String containerEncoding;
0177: private String defaultFormEncoding;
0178:
0179: protected javax.portlet.PortletContext portletContext;
0180:
0181: /** The classloader that will be set as the context classloader if init-classloader is true */
0182: protected ClassLoader classLoader = this .getClass()
0183: .getClassLoader();
0184: protected boolean initClassLoader = false;
0185:
0186: private String parentComponentManagerClass;
0187: private String parentComponentManagerInitParam;
0188:
0189: /** The parent ComponentManager, if any. Stored here in order to be able to dispose it in destroy(). */
0190: private ComponentManager parentComponentManager;
0191:
0192: protected String forceLoadParameter;
0193: protected String forceSystemProperty;
0194:
0195: /**
0196: * If true or not set, this class will try to catch and handle all Cocoon exceptions.
0197: * If false, it will rethrow them to the portlet container.
0198: */
0199: private boolean manageExceptions;
0200:
0201: /**
0202: * Flag to enable avalon excalibur instrumentation of Cocoon.
0203: */
0204: private boolean enableInstrumentation;
0205:
0206: /**
0207: * The <code>InstrumentManager</code> instance
0208: */
0209: private InstrumentManager instrumentManager;
0210:
0211: /**
0212: * This is the path to the portlet context (or the result
0213: * of calling getRealPath('/') on the PortletContext.
0214: * Note, that this can be null.
0215: */
0216: protected String portletContextPath;
0217:
0218: /**
0219: * This is the url to the portlet context directory
0220: */
0221: protected String portletContextURL;
0222:
0223: /**
0224: * The RequestFactory is responsible for wrapping multipart-encoded
0225: * forms and for handing the file payload of incoming requests
0226: */
0227: protected RequestFactory requestFactory;
0228:
0229: /**
0230: * Value to be used as servletPath in the request.
0231: * Provided via configuration parameter, if missing, defaults to the
0232: * '/portlets/' + portletName.
0233: */
0234: protected String servletPath;
0235:
0236: /**
0237: * Default scope for the session attributes, either
0238: * {@link javax.portlet.PortletSession#PORTLET_SCOPE} or
0239: * {@link javax.portlet.PortletSession#APPLICATION_SCOPE}.
0240: * This corresponds to <code>default-session-scope</code>
0241: * parameter, with default value <code>portlet</code>.
0242: *
0243: * @see org.apache.cocoon.environment.portlet.PortletSession
0244: */
0245: protected int defaultSessionScope;
0246:
0247: /**
0248: * Store pathInfo in session
0249: */
0250: protected boolean storeSessionPath;
0251:
0252: /**
0253: * Initialize this <code>CocoonPortlet</code> instance.
0254: *
0255: * <p>Uses the following parameters:
0256: * portlet-logger
0257: * enable-uploads
0258: * autosave-uploads
0259: * overwrite-uploads
0260: * upload-max-size
0261: * show-time
0262: * container-encoding
0263: * form-encoding
0264: * manage-exceptions
0265: * servlet-path
0266: *
0267: * @param conf The PortletConfig object from the portlet container.
0268: * @throws PortletException
0269: */
0270: public void init(PortletConfig conf) throws PortletException {
0271:
0272: super .init(conf);
0273:
0274: // Check the init-classloader parameter only if it's not already true.
0275: // This is useful for subclasses of this portlet that override the value
0276: // initially set by this class (i.e. false).
0277: if (!this .initClassLoader) {
0278: this .initClassLoader = getInitParameterAsBoolean(
0279: "init-classloader", false);
0280: }
0281:
0282: if (this .initClassLoader) {
0283: // Force context classloader so that JAXP can work correctly
0284: // (see javax.xml.parsers.FactoryFinder.findClassLoader())
0285: try {
0286: Thread.currentThread().setContextClassLoader(
0287: this .classLoader);
0288: } catch (Exception e) {
0289: }
0290: }
0291:
0292: try {
0293: // FIXME (VG): We shouldn't have to specify these. Need to override
0294: // jaxp implementation of weblogic before initializing logger.
0295: // This piece of code is also required in the Cocoon class.
0296: String value = System
0297: .getProperty("javax.xml.parsers.SAXParserFactory");
0298: if (value != null && value.startsWith("weblogic")) {
0299: System.setProperty(
0300: "javax.xml.parsers.SAXParserFactory",
0301: "org.apache.xerces.jaxp.SAXParserFactoryImpl");
0302: System
0303: .setProperty(
0304: "javax.xml.parsers.DocumentBuilderFactory",
0305: "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
0306: }
0307: } catch (SecurityException e) {
0308: // Ignore security exception
0309: System.out
0310: .println("CocoonPortlet: Could not check system properties, got: "
0311: + e);
0312: }
0313:
0314: this .portletContext = conf.getPortletContext();
0315: this .appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT,
0316: new PortletContext(this .portletContext));
0317: this .portletContextPath = this .portletContext.getRealPath("/");
0318:
0319: // first init the work-directory for the logger.
0320: // this is required if we are running inside a war file!
0321: final String workDirParam = getInitParameter("work-directory");
0322: if (workDirParam != null) {
0323: if (this .portletContextPath == null) {
0324: // No context path : consider work-directory as absolute
0325: this .workDir = new File(workDirParam);
0326: } else {
0327: // Context path exists : is work-directory absolute ?
0328: File workDirParamFile = new File(workDirParam);
0329: if (workDirParamFile.isAbsolute()) {
0330: // Yes : keep it as is
0331: this .workDir = workDirParamFile;
0332: } else {
0333: // No : consider it relative to context path
0334: this .workDir = new File(portletContextPath,
0335: workDirParam);
0336: }
0337: }
0338: } else {
0339: // TODO: Check portlet specification
0340: this .workDir = (File) this .portletContext
0341: .getAttribute("javax.servlet.context.tempdir");
0342: if (this .workDir == null) {
0343: this .workDir = new File(this .portletContext
0344: .getRealPath("/WEB-INF/work"));
0345: }
0346: this .workDir = new File(workDir, "cocoon-files");
0347: }
0348: this .workDir.mkdirs();
0349: this .appContext.put(Constants.CONTEXT_WORK_DIR, workDir);
0350:
0351: String path = this .portletContextPath;
0352: // these two variables are just for debugging. We can't log at this point
0353: // as the logger isn't initialized yet.
0354: String debugPathOne = null, debugPathTwo = null;
0355: if (path == null) {
0356: // Try to figure out the path of the root from that of WEB-INF
0357: try {
0358: path = this .portletContext.getResource("/WEB-INF")
0359: .toString();
0360: } catch (MalformedURLException me) {
0361: throw new PortletException(
0362: "Unable to get resource 'WEB-INF'.", me);
0363: }
0364: debugPathOne = path;
0365: path = path
0366: .substring(0, path.length() - "WEB-INF".length());
0367: debugPathTwo = path;
0368: }
0369:
0370: try {
0371: if (path.indexOf(':') > 1) {
0372: this .portletContextURL = path;
0373: } else {
0374: this .portletContextURL = new File(path).toURL()
0375: .toExternalForm();
0376: }
0377: } catch (MalformedURLException me) {
0378: // VG: Novell has absolute file names starting with the
0379: // volume name which is easily more then one letter.
0380: // Examples: sys:/apache/cocoon or sys:\apache\cocoon
0381: try {
0382: this .portletContextURL = new File(path).toURL()
0383: .toExternalForm();
0384: } catch (MalformedURLException ignored) {
0385: throw new PortletException(
0386: "Unable to determine portlet context URL.", me);
0387: }
0388: }
0389: try {
0390: this .appContext.put("context-root", new URL(
0391: this .portletContextURL));
0392: } catch (MalformedURLException ignore) {
0393: // we simply ignore this
0394: }
0395:
0396: // Init logger
0397: initLogger();
0398:
0399: if (getLogger().isDebugEnabled()) {
0400: getLogger().debug(
0401: "getRealPath for /: " + this .portletContextPath);
0402: if (this .portletContextPath == null) {
0403: getLogger().debug(
0404: "getResource for /WEB-INF: " + debugPathOne);
0405: getLogger().debug("Path for Root: " + debugPathTwo);
0406: }
0407: }
0408:
0409: this .forceLoadParameter = getInitParameter("load-class", null);
0410: this .forceSystemProperty = getInitParameter("force-property",
0411: null);
0412:
0413: // Output some debug info
0414: if (getLogger().isDebugEnabled()) {
0415: getLogger().debug(
0416: "Portlet Context URL: " + this .portletContextURL);
0417: if (workDirParam != null) {
0418: getLogger().debug(
0419: "Using work-directory " + this .workDir);
0420: } else {
0421: getLogger().debug(
0422: "Using default work-directory " + this .workDir);
0423: }
0424: }
0425:
0426: final String uploadDirParam = conf
0427: .getInitParameter("upload-directory");
0428: if (uploadDirParam != null) {
0429: if (this .portletContextPath == null) {
0430: this .uploadDir = new File(uploadDirParam);
0431: } else {
0432: // Context path exists : is upload-directory absolute ?
0433: File uploadDirParamFile = new File(uploadDirParam);
0434: if (uploadDirParamFile.isAbsolute()) {
0435: // Yes : keep it as is
0436: this .uploadDir = uploadDirParamFile;
0437: } else {
0438: // No : consider it relative to context path
0439: this .uploadDir = new File(portletContextPath,
0440: uploadDirParam);
0441: }
0442: }
0443: if (getLogger().isDebugEnabled()) {
0444: getLogger().debug(
0445: "Using upload-directory " + this .uploadDir);
0446: }
0447: } else {
0448: this .uploadDir = new File(workDir, "upload-dir"
0449: + File.separator);
0450: if (getLogger().isDebugEnabled()) {
0451: getLogger().debug(
0452: "Using default upload-directory "
0453: + this .uploadDir);
0454: }
0455: }
0456: this .uploadDir.mkdirs();
0457: this .appContext.put(Constants.CONTEXT_UPLOAD_DIR,
0458: this .uploadDir);
0459:
0460: this .enableUploads = getInitParameterAsBoolean(
0461: "enable-uploads", ENABLE_UPLOADS);
0462:
0463: this .autoSaveUploads = getInitParameterAsBoolean(
0464: "autosave-uploads", SAVE_UPLOADS_TO_DISK);
0465:
0466: String overwriteParam = getInitParameter("overwrite-uploads",
0467: "rename");
0468: // accepted values are deny|allow|rename - rename is default.
0469: if ("deny".equalsIgnoreCase(overwriteParam)) {
0470: this .allowOverwrite = false;
0471: this .silentlyRename = false;
0472: } else if ("allow".equalsIgnoreCase(overwriteParam)) {
0473: this .allowOverwrite = true;
0474: this .silentlyRename = false; // ignored in this case
0475: } else {
0476: // either rename is specified or unsupported value - default to rename.
0477: this .allowOverwrite = false;
0478: this .silentlyRename = true;
0479: }
0480:
0481: this .maxUploadSize = getInitParameterAsInteger(
0482: "upload-max-size", MAX_UPLOAD_SIZE);
0483:
0484: String cacheDirParam = conf.getInitParameter("cache-directory");
0485: if (cacheDirParam != null) {
0486: if (this .portletContextPath == null) {
0487: this .cacheDir = new File(cacheDirParam);
0488: } else {
0489: // Context path exists : is cache-directory absolute ?
0490: File cacheDirParamFile = new File(cacheDirParam);
0491: if (cacheDirParamFile.isAbsolute()) {
0492: // Yes : keep it as is
0493: this .cacheDir = cacheDirParamFile;
0494: } else {
0495: // No : consider it relative to context path
0496: this .cacheDir = new File(portletContextPath,
0497: cacheDirParam);
0498: }
0499: }
0500: if (getLogger().isDebugEnabled()) {
0501: getLogger().debug(
0502: "Using cache-directory " + this .cacheDir);
0503: }
0504: } else {
0505: this .cacheDir = IOUtils.createFile(workDir, "cache-dir"
0506: + File.separator);
0507: if (getLogger().isDebugEnabled()) {
0508: getLogger().debug(
0509: "cache-directory was not set - defaulting to "
0510: + this .cacheDir);
0511: }
0512: }
0513: this .cacheDir.mkdirs();
0514: this .appContext.put(Constants.CONTEXT_CACHE_DIR, this .cacheDir);
0515:
0516: this .appContext.put(Constants.CONTEXT_CONFIG_URL,
0517: getConfigFile(conf.getInitParameter("configurations")));
0518: if (conf.getInitParameter("configurations") == null) {
0519: if (getLogger().isDebugEnabled()) {
0520: getLogger()
0521: .debug(
0522: "configurations was not set - defaulting to... ?");
0523: }
0524: }
0525:
0526: // get allow reload parameter, default is true
0527: this .allowReload = getInitParameterAsBoolean("allow-reload",
0528: ALLOW_RELOAD);
0529:
0530: String value = conf.getInitParameter("show-time");
0531: this .showTime = BooleanUtils.toBoolean(value)
0532: || (this .hiddenShowTime = "hide".equals(value));
0533: if (value == null) {
0534: if (getLogger().isDebugEnabled()) {
0535: getLogger().debug(
0536: "show-time was not set - defaulting to false");
0537: }
0538: }
0539:
0540: parentComponentManagerClass = getInitParameter(
0541: "parent-component-manager", null);
0542: if (parentComponentManagerClass != null) {
0543: int dividerPos = parentComponentManagerClass.indexOf('/');
0544: if (dividerPos != -1) {
0545: parentComponentManagerInitParam = parentComponentManagerClass
0546: .substring(dividerPos + 1);
0547: parentComponentManagerClass = parentComponentManagerClass
0548: .substring(0, dividerPos);
0549: }
0550: }
0551:
0552: this .containerEncoding = getInitParameter("container-encoding",
0553: "ISO-8859-1");
0554: this .defaultFormEncoding = getInitParameter("form-encoding",
0555: "ISO-8859-1");
0556:
0557: this .appContext.put(Constants.CONTEXT_DEFAULT_ENCODING,
0558: this .defaultFormEncoding);
0559: this .manageExceptions = getInitParameterAsBoolean(
0560: "manage-exceptions", true);
0561:
0562: this .enableInstrumentation = getInitParameterAsBoolean(
0563: "enable-instrumentation", false);
0564:
0565: this .requestFactory = new RequestFactory(this .autoSaveUploads,
0566: this .uploadDir, this .allowOverwrite,
0567: this .silentlyRename, this .maxUploadSize,
0568: this .defaultFormEncoding);
0569:
0570: this .servletPath = getInitParameter("servlet-path", null);
0571: if (this .servletPath != null) {
0572: if (this .servletPath.startsWith("/")) {
0573: this .servletPath = this .servletPath.substring(1);
0574: }
0575: if (this .servletPath.endsWith("/")) {
0576: this .servletPath = servletPath.substring(0, servletPath
0577: .length() - 1);
0578: }
0579: }
0580:
0581: final String sessionScopeParam = getInitParameter(
0582: "default-session-scope", "portlet");
0583: if ("application".equalsIgnoreCase(sessionScopeParam)) {
0584: this .defaultSessionScope = javax.portlet.PortletSession.APPLICATION_SCOPE;
0585: } else {
0586: this .defaultSessionScope = javax.portlet.PortletSession.PORTLET_SCOPE;
0587: }
0588:
0589: // Add the portlet configuration
0590: this .appContext.put(CONTEXT_PORTLET_CONFIG, conf);
0591: this .createCocoon();
0592: }
0593:
0594: /**
0595: * Dispose Cocoon when portlet is destroyed
0596: */
0597: public void destroy() {
0598: if (this .initClassLoader) {
0599: try {
0600: Thread.currentThread().setContextClassLoader(
0601: this .classLoader);
0602: } catch (Exception e) {
0603: }
0604: }
0605:
0606: if (this .cocoon != null) {
0607: if (getLogger().isDebugEnabled()) {
0608: getLogger().debug(
0609: "Portlet destroyed - disposing Cocoon");
0610: }
0611: this .disposeCocoon();
0612: }
0613:
0614: if (this .instrumentManager instanceof Disposable) {
0615: ((Disposable) this .instrumentManager).dispose();
0616: }
0617:
0618: if (this .parentComponentManager != null
0619: && this .parentComponentManager instanceof Disposable) {
0620: ((Disposable) this .parentComponentManager).dispose();
0621: }
0622: }
0623:
0624: /**
0625: * Adds an URL to the classloader. Does nothing here, but can
0626: * be overriden.
0627: */
0628: protected void addClassLoaderURL(URL URL) {
0629: // Nothing
0630: }
0631:
0632: /**
0633: * Adds a directory to the classloader. Does nothing here, but can
0634: * be overriden.
0635: */
0636: protected void addClassLoaderDirectory(String dir) {
0637: // Nothing
0638: }
0639:
0640: /**
0641: * This builds the important ClassPath used by this Portlet. It
0642: * does so in a Portlet Engine neutral way. It uses the
0643: * <code>PortletContext</code>'s <code>getRealPath</code> method
0644: * to get the Portlet identified classes and lib directories.
0645: * It iterates in alphabetical order through every file in the
0646: * lib directory and adds it to the classpath.
0647: *
0648: * Also, we add the files to the ClassLoader for the Cocoon system.
0649: * In order to protect ourselves from skitzofrantic classloaders,
0650: * we need to work with a known one.
0651: *
0652: * We need to get this to work properly when Cocoon is in a war.
0653: *
0654: * @throws PortletException
0655: */
0656: protected String getClassPath() throws PortletException {
0657: StringBuffer buildClassPath = new StringBuffer();
0658:
0659: File root = null;
0660: if (portletContextPath != null) {
0661: // Old method. There *MUST* be a better method than this...
0662:
0663: String classDir = this .portletContext
0664: .getRealPath("/WEB-INF/classes");
0665: String libDir = this .portletContext
0666: .getRealPath("/WEB-INF/lib");
0667:
0668: if (libDir != null) {
0669: root = new File(libDir);
0670: }
0671:
0672: if (classDir != null) {
0673: buildClassPath.append(classDir);
0674:
0675: addClassLoaderDirectory(classDir);
0676: }
0677: } else {
0678: // New(ish) method for war'd deployments
0679: URL classDirURL = null;
0680: URL libDirURL = null;
0681:
0682: try {
0683: classDirURL = this .portletContext
0684: .getResource("/WEB-INF/classes");
0685: } catch (MalformedURLException me) {
0686: if (getLogger().isWarnEnabled()) {
0687: this
0688: .getLogger()
0689: .warn(
0690: "Unable to add WEB-INF/classes to the classpath",
0691: me);
0692: }
0693: }
0694:
0695: try {
0696: libDirURL = this .portletContext
0697: .getResource("/WEB-INF/lib");
0698: } catch (MalformedURLException me) {
0699: if (getLogger().isWarnEnabled()) {
0700: this
0701: .getLogger()
0702: .warn(
0703: "Unable to add WEB-INF/lib to the classpath",
0704: me);
0705: }
0706: }
0707:
0708: if (libDirURL != null
0709: && libDirURL.toExternalForm().startsWith("file:")) {
0710: root = new File(libDirURL.toExternalForm().substring(
0711: "file:".length()));
0712: }
0713:
0714: if (classDirURL != null) {
0715: buildClassPath.append(classDirURL.toExternalForm());
0716:
0717: addClassLoaderURL(classDirURL);
0718: }
0719: }
0720:
0721: // Unable to find lib directory. Going the hard way.
0722: if (root == null) {
0723: root = extractLibraries();
0724: }
0725:
0726: if (root != null && root.isDirectory()) {
0727: File[] libraries = root.listFiles();
0728: Arrays.sort(libraries);
0729: for (int i = 0; i < libraries.length; i++) {
0730: String fullName = IOUtils.getFullFilename(libraries[i]);
0731: buildClassPath.append(File.pathSeparatorChar).append(
0732: fullName);
0733:
0734: addClassLoaderDirectory(fullName);
0735: }
0736: }
0737:
0738: buildClassPath.append(File.pathSeparatorChar).append(
0739: SystemUtils.JAVA_CLASS_PATH);
0740:
0741: buildClassPath.append(File.pathSeparatorChar).append(
0742: getExtraClassPath());
0743: return buildClassPath.toString();
0744: }
0745:
0746: private File extractLibraries() {
0747: try {
0748: URL manifestURL = this .portletContext
0749: .getResource("/META-INF/MANIFEST.MF");
0750: if (manifestURL == null) {
0751: this .getLogger().fatalError("Unable to get Manifest");
0752: return null;
0753: }
0754:
0755: Manifest mf = new Manifest(manifestURL.openStream());
0756: Attributes attr = mf.getMainAttributes();
0757: String libValue = attr.getValue("Cocoon-Libs");
0758: if (libValue == null) {
0759: this
0760: .getLogger()
0761: .fatalError(
0762: "Unable to get 'Cocoon-Libs' attribute from the Manifest");
0763: return null;
0764: }
0765:
0766: List libList = new ArrayList();
0767: for (StringTokenizer st = new StringTokenizer(libValue, " "); st
0768: .hasMoreTokens();) {
0769: libList.add(st.nextToken());
0770: }
0771:
0772: File root = new File(this .workDir, "lib");
0773: root.mkdirs();
0774:
0775: File[] oldLibs = root.listFiles();
0776: for (int i = 0; i < oldLibs.length; i++) {
0777: String oldLib = oldLibs[i].getName();
0778: if (!libList.contains(oldLib)) {
0779: this .getLogger().debug(
0780: "Removing old library " + oldLibs[i]);
0781: oldLibs[i].delete();
0782: }
0783: }
0784:
0785: this .getLogger().warn("Extracting libraries into " + root);
0786: byte[] buffer = new byte[65536];
0787: for (Iterator i = libList.iterator(); i.hasNext();) {
0788: String libName = (String) i.next();
0789:
0790: long lastModified = -1;
0791: try {
0792: lastModified = Long.parseLong(attr
0793: .getValue("Cocoon-Lib-"
0794: + libName.replace('.', '_')));
0795: } catch (Exception e) {
0796: this .getLogger().debug(
0797: "Failed to parse lastModified: "
0798: + attr
0799: .getValue("Cocoon-Lib-"
0800: + libName.replace(
0801: '.', '_')));
0802: }
0803:
0804: File lib = new File(root, libName);
0805: if (lib.exists() && lib.lastModified() != lastModified) {
0806: this .getLogger().debug(
0807: "Removing modified library " + lib);
0808: lib.delete();
0809: }
0810:
0811: InputStream is = this .portletContext
0812: .getResourceAsStream("/WEB-INF/lib/" + libName);
0813: if (is == null) {
0814: this .getLogger().warn("Skipping " + libName);
0815: } else {
0816: this .getLogger().debug("Extracting " + libName);
0817: OutputStream os = null;
0818: try {
0819: os = new FileOutputStream(lib);
0820: int count;
0821: while ((count = is.read(buffer)) > 0) {
0822: os.write(buffer, 0, count);
0823: }
0824: } finally {
0825: if (is != null)
0826: is.close();
0827: if (os != null)
0828: os.close();
0829: }
0830: }
0831:
0832: if (lastModified != -1) {
0833: lib.setLastModified(lastModified);
0834: }
0835: }
0836:
0837: return root;
0838: } catch (IOException e) {
0839: this .getLogger().fatalError(
0840: "Exception while processing Manifest file", e);
0841: return null;
0842: }
0843: }
0844:
0845: /**
0846: * Retreives the "extra-classpath" attribute, that needs to be
0847: * added to the class path.
0848: *
0849: * @throws PortletException
0850: */
0851: protected String getExtraClassPath() throws PortletException {
0852: String extraClassPath = this
0853: .getInitParameter("extra-classpath");
0854: if (extraClassPath != null) {
0855: StringBuffer sb = new StringBuffer();
0856: StringTokenizer st = new StringTokenizer(extraClassPath,
0857: SystemUtils.PATH_SEPARATOR, false);
0858: int i = 0;
0859: while (st.hasMoreTokens()) {
0860: String s = st.nextToken();
0861: if (i++ > 0) {
0862: sb.append(File.pathSeparatorChar);
0863: }
0864: if ((s.charAt(0) == File.separatorChar)
0865: || (s.charAt(1) == ':')) {
0866: if (getLogger().isDebugEnabled()) {
0867: getLogger().debug(
0868: "extraClassPath is absolute: " + s);
0869: }
0870: sb.append(s);
0871:
0872: addClassLoaderDirectory(s);
0873: } else {
0874: if (s.indexOf("${") != -1) {
0875: String path = StringUtils.replaceToken(s);
0876: sb.append(path);
0877: if (getLogger().isDebugEnabled()) {
0878: getLogger().debug(
0879: "extraClassPath is not absolute replacing using token: ["
0880: + s + "] : " + path);
0881: }
0882: addClassLoaderDirectory(path);
0883: } else {
0884: String path = null;
0885: if (this .portletContextPath != null) {
0886: path = this .portletContextPath + s;
0887: if (getLogger().isDebugEnabled()) {
0888: getLogger().debug(
0889: "extraClassPath is not absolute pre-pending context path: "
0890: + path);
0891: }
0892: } else {
0893: path = this .workDir.toString() + s;
0894: if (getLogger().isDebugEnabled()) {
0895: getLogger().debug(
0896: "extraClassPath is not absolute pre-pending work-directory: "
0897: + path);
0898: }
0899: }
0900: sb.append(path);
0901: addClassLoaderDirectory(path);
0902: }
0903: }
0904: }
0905: return sb.toString();
0906: }
0907: return "";
0908: }
0909:
0910: /**
0911: * Set up the log level and path. The default log level is
0912: * Priority.ERROR, although it can be overwritten by the parameter
0913: * "log-level". The log system goes to both a file and the Portlet
0914: * container's log system. Only messages that are Priority.ERROR
0915: * and above go to the portlet context. The log messages can
0916: * be as restrictive (Priority.FATAL_ERROR and above) or as liberal
0917: * (Priority.DEBUG and above) as you want that get routed to the
0918: * file.
0919: */
0920: protected void initLogger() {
0921: final String logLevel = getInitParameter("log-level", "INFO");
0922:
0923: final String accesslogger = getInitParameter("portlet-logger",
0924: "cocoon");
0925:
0926: final Priority logPriority = Priority
0927: .getPriorityForName(logLevel);
0928:
0929: final CocoonLogFormatter formatter = new CocoonLogFormatter();
0930: formatter
0931: .setFormat("%7.7{priority} %{time} [%8.8{category}] "
0932: + "(%{uri}) %{thread}/%{class:short}: %{message}\\n%{throwable}");
0933: final PortletOutputLogTarget servTarget = new PortletOutputLogTarget(
0934: this .portletContext, formatter);
0935:
0936: final Hierarchy defaultHierarchy = Hierarchy
0937: .getDefaultHierarchy();
0938: final ErrorHandler errorHandler = new DefaultErrorHandler();
0939: defaultHierarchy.setErrorHandler(errorHandler);
0940: defaultHierarchy.setDefaultLogTarget(servTarget);
0941: defaultHierarchy.setDefaultPriority(logPriority);
0942: final Logger logger = new LogKitLogger(Hierarchy
0943: .getDefaultHierarchy().getLoggerFor(""));
0944: final String loggerManagerClass = this .getInitParameter(
0945: "logger-class", LogKitLoggerManager.class.getName());
0946:
0947: // the log4j support requires currently that the log4j system is already configured elsewhere
0948:
0949: final LoggerManager loggerManager = newLoggerManager(
0950: loggerManagerClass, defaultHierarchy);
0951: ContainerUtil.enableLogging(loggerManager, logger);
0952:
0953: final DefaultContext subcontext = new DefaultContext(
0954: this .appContext);
0955: subcontext.put("portlet-context", this .portletContext);
0956: if (this .portletContextPath == null) {
0957: File logSCDir = new File(this .workDir, "log");
0958: logSCDir.mkdirs();
0959: if (getLogger().isWarnEnabled()) {
0960: getLogger().warn(
0961: "Setting context-root for LogKit to "
0962: + logSCDir);
0963: }
0964: subcontext.put("context-root", logSCDir.toString());
0965: } else {
0966: subcontext.put("context-root", this .portletContextPath);
0967: }
0968:
0969: try {
0970: ContainerUtil.contextualize(loggerManager, subcontext);
0971: this .loggerManager = loggerManager;
0972:
0973: if (loggerManager instanceof Configurable) {
0974: //Configure the logkit management
0975: String logkitConfig = getInitParameter("logkit-config",
0976: "/WEB-INF/logkit.xconf");
0977:
0978: // test if this is a qualified url
0979: InputStream is = null;
0980: if (logkitConfig.indexOf(':') == -1) {
0981: is = this .portletContext
0982: .getResourceAsStream(logkitConfig);
0983: if (is == null)
0984: is = new FileInputStream(logkitConfig);
0985: } else {
0986: URL logkitURL = new URL(logkitConfig);
0987: is = logkitURL.openStream();
0988: }
0989: final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
0990: final Configuration conf = builder.build(is);
0991: ContainerUtil.configure(loggerManager, conf);
0992: }
0993:
0994: // let's configure log4j
0995: final String log4jConfig = getInitParameter("log4j-config",
0996: null);
0997: if (log4jConfig != null) {
0998: final Log4JConfigurator configurator = new Log4JConfigurator(
0999: subcontext);
1000:
1001: // test if this is a qualified url
1002: InputStream is = null;
1003: if (log4jConfig.indexOf(':') == -1) {
1004: is = this .portletContext
1005: .getResourceAsStream(log4jConfig);
1006: if (is == null)
1007: is = new FileInputStream(log4jConfig);
1008: } else {
1009: final URL log4jURL = new URL(log4jConfig);
1010: is = log4jURL.openStream();
1011: }
1012: configurator.doConfigure(is, LogManager
1013: .getLoggerRepository());
1014: }
1015:
1016: ContainerUtil.initialize(loggerManager);
1017: } catch (Exception e) {
1018: errorHandler
1019: .error(
1020: "Could not set up Cocoon Logger, will use screen instead",
1021: e, null);
1022: }
1023:
1024: this .log = this .loggerManager
1025: .getLoggerForCategory(accesslogger);
1026: }
1027:
1028: private LoggerManager newLoggerManager(String loggerManagerClass,
1029: Hierarchy hierarchy) {
1030: if (loggerManagerClass.equals(LogKitLoggerManager.class
1031: .getName())) {
1032: return new LogKitLoggerManager(hierarchy);
1033: } else if (loggerManagerClass.equals(Log4JLoggerManager.class
1034: .getName())
1035: || loggerManagerClass.equalsIgnoreCase("LOG4J")) {
1036: return new Log4JLoggerManager();
1037: } else {
1038: try {
1039: Class clazz = Class.forName(loggerManagerClass);
1040: return (LoggerManager) clazz.newInstance();
1041: } catch (Exception e) {
1042: return new LogKitLoggerManager(hierarchy);
1043: }
1044: }
1045: }
1046:
1047: /**
1048: * Set the ConfigFile for the Cocoon object.
1049: *
1050: * @param configFileName The file location for the cocoon.xconf
1051: *
1052: * @throws PortletException
1053: */
1054: private URL getConfigFile(final String configFileName)
1055: throws PortletException {
1056: final String usedFileName;
1057:
1058: if (configFileName == null) {
1059: if (getLogger().isWarnEnabled()) {
1060: getLogger()
1061: .warn(
1062: "Portlet initialization argument 'configurations' not specified, attempting to use '/WEB-INF/cocoon.xconf'");
1063: }
1064: usedFileName = "/WEB-INF/cocoon.xconf";
1065: } else {
1066: usedFileName = configFileName;
1067: }
1068:
1069: if (getLogger().isDebugEnabled()) {
1070: getLogger().debug(
1071: "Using configuration file: " + usedFileName);
1072: }
1073:
1074: URL result;
1075: try {
1076: // test if this is a qualified url
1077: if (usedFileName.indexOf(':') == -1) {
1078: result = this .portletContext.getResource(usedFileName);
1079: } else {
1080: result = new URL(usedFileName);
1081: }
1082: } catch (Exception mue) {
1083: String msg = "Init parameter 'configurations' is invalid : "
1084: + usedFileName;
1085: getLogger().error(msg, mue);
1086: throw new PortletException(msg, mue);
1087: }
1088:
1089: if (result == null) {
1090: File resultFile = new File(usedFileName);
1091: if (resultFile.isFile()) {
1092: try {
1093: result = resultFile.getCanonicalFile().toURL();
1094: } catch (Exception e) {
1095: String msg = "Init parameter 'configurations' is invalid : "
1096: + usedFileName;
1097: getLogger().error(msg, e);
1098: throw new PortletException(msg, e);
1099: }
1100: }
1101: }
1102:
1103: if (result == null) {
1104: String msg = "Init parameter 'configuration' doesn't name an existing resource : "
1105: + usedFileName;
1106: getLogger().error(msg);
1107: throw new PortletException(msg);
1108: }
1109: return result;
1110: }
1111:
1112: /**
1113: * Handle the <code>load-class</code> parameter. This overcomes
1114: * limits in many classpath issues. One of the more notorious
1115: * ones is a bug in WebSphere that does not load the URL handler
1116: * for the <code>classloader://</code> protocol. In order to
1117: * overcome that bug, set <code>load-class</code> parameter to
1118: * the <code>com.ibm.servlet.classloader.Handler</code> value.
1119: *
1120: * <p>If you need to load more than one class, then separate each
1121: * entry with whitespace, a comma, or a semi-colon. Cocoon will
1122: * strip any whitespace from the entry.</p>
1123: */
1124: private void forceLoad() {
1125: if (this .forceLoadParameter != null) {
1126: StringTokenizer fqcnTokenizer = new StringTokenizer(
1127: forceLoadParameter, " \t\r\n\f;,", false);
1128:
1129: while (fqcnTokenizer.hasMoreTokens()) {
1130: final String fqcn = fqcnTokenizer.nextToken().trim();
1131:
1132: try {
1133: if (getLogger().isDebugEnabled()) {
1134: getLogger().debug(
1135: "Trying to load class: " + fqcn);
1136: }
1137: ClassUtils.loadClass(fqcn).newInstance();
1138: } catch (Exception e) {
1139: if (getLogger().isWarnEnabled()) {
1140: getLogger().warn(
1141: "Could not force-load class: " + fqcn,
1142: e);
1143: }
1144: // Do not throw an exception, because it is not a fatal error.
1145: }
1146: }
1147: }
1148: }
1149:
1150: /**
1151: * Handle the "force-property" parameter.
1152: *
1153: * If you need to force more than one property to load, then
1154: * separate each entry with whitespace, a comma, or a semi-colon.
1155: * Cocoon will strip any whitespace from the entry.
1156: */
1157: private void forceProperty() {
1158: if (this .forceSystemProperty != null) {
1159: StringTokenizer tokenizer = new StringTokenizer(
1160: forceSystemProperty, " \t\r\n\f;,", false);
1161:
1162: while (tokenizer.hasMoreTokens()) {
1163: final String property = tokenizer.nextToken().trim();
1164: if (property.indexOf('=') == -1) {
1165: continue;
1166: }
1167: try {
1168: String key = property.substring(0, property
1169: .indexOf('='));
1170: String value = property.substring(property
1171: .indexOf('=') + 1);
1172: if (value.indexOf("${") != -1) {
1173: value = StringUtils.replaceToken(value);
1174: }
1175: if (getLogger().isDebugEnabled()) {
1176: getLogger().debug(
1177: "setting " + key + "=" + value);
1178: }
1179: System.setProperty(key, value);
1180: } catch (Exception e) {
1181: if (getLogger().isWarnEnabled()) {
1182: getLogger().warn(
1183: "Could not set property: " + property,
1184: e);
1185: }
1186: // Do not throw an exception, because it is not a fatal error.
1187: }
1188: }
1189: }
1190: }
1191:
1192: /**
1193: * Process the specified <code>ActionRequest</code> producing output
1194: * on the specified <code>ActionResponse</code>.
1195: */
1196: public void processAction(ActionRequest req, ActionResponse res)
1197: throws PortletException, IOException {
1198:
1199: /* HACK for reducing class loader problems. */
1200: /* example: xalan extensions fail if someone adds xalan jars in tomcat3.2.1/lib */
1201: if (this .initClassLoader) {
1202: try {
1203: Thread.currentThread().setContextClassLoader(
1204: this .classLoader);
1205: } catch (Exception e) {
1206: }
1207: }
1208:
1209: // remember when we started (used for timing the processing)
1210: long start = System.currentTimeMillis();
1211:
1212: // add the cocoon header timestamp
1213: res.setProperty("X-Cocoon-Version", Constants.VERSION);
1214:
1215: // get the request (wrapped if contains multipart-form data)
1216: ActionRequest request;
1217: try {
1218: if (this .enableUploads) {
1219: request = requestFactory.getServletRequest(req);
1220: } else {
1221: request = req;
1222: }
1223: } catch (Exception e) {
1224: if (getLogger().isErrorEnabled()) {
1225: getLogger().error("Problem with Cocoon portlet", e);
1226: }
1227:
1228: manageException(req, res, null, null,
1229: "Problem in creating the Request", null, null, e);
1230: return;
1231: }
1232:
1233: // Get the cocoon engine instance
1234: getCocoon(request.getParameter(Constants.RELOAD_PARAM));
1235:
1236: // Check if cocoon was initialized
1237: if (this .cocoon == null) {
1238: manageException(
1239: request,
1240: res,
1241: null,
1242: null,
1243: "Initialization Problem",
1244: null /* "Cocoon was not initialized" */,
1245: null /* "Cocoon was not initialized, cannot process request" */,
1246: this .exception);
1247: return;
1248: }
1249:
1250: // We got it... Process the request
1251: String servletPath = this .servletPath;
1252: if (servletPath == null) {
1253: servletPath = "portlets/"
1254: + getPortletConfig().getPortletName();
1255: }
1256: String pathInfo = getPathInfo(request);
1257:
1258: String uri = servletPath;
1259: if (pathInfo != null) {
1260: uri += pathInfo;
1261: }
1262:
1263: ContextMap ctxMap = null;
1264:
1265: Environment env;
1266: try {
1267: if (uri.charAt(0) == '/') {
1268: uri = uri.substring(1);
1269: }
1270: env = getEnvironment(servletPath, pathInfo, uri, request,
1271: res);
1272: } catch (Exception e) {
1273: if (getLogger().isErrorEnabled()) {
1274: getLogger().error("Problem with Cocoon portlet", e);
1275: }
1276:
1277: manageException(request, res, null, uri,
1278: "Problem in creating the Environment", null, null,
1279: e);
1280: return;
1281: }
1282:
1283: try {
1284: try {
1285: // Initialize a fresh log context containing the object model: it
1286: // will be used by the CocoonLogFormatter
1287: ctxMap = ContextMap.getCurrentContext();
1288: // Add thread name (default content for empty context)
1289: String threadName = Thread.currentThread().getName();
1290: ctxMap.set("threadName", threadName);
1291: // Add the object model
1292: ctxMap.set("objectModel", env.getObjectModel());
1293: // Add a unique request id (threadName + currentTime
1294: ctxMap.set("request-id", threadName
1295: + System.currentTimeMillis());
1296:
1297: if (this .cocoon.process(env)) {
1298: } else {
1299: // We reach this when there is nothing in the processing change that matches
1300: // the request. For example, no matcher matches.
1301: getLogger()
1302: .fatalError(
1303: "The Cocoon engine failed to process the request.");
1304: manageException(
1305: request,
1306: res,
1307: env,
1308: uri,
1309: "Request Processing Failed",
1310: "Cocoon engine failed in process the request",
1311: "The processing engine failed to process the request. This could be due to lack of matching or bugs in the pipeline engine.",
1312: null);
1313: return;
1314: }
1315: } catch (ResourceNotFoundException e) {
1316: if (getLogger().isDebugEnabled()) {
1317: getLogger().warn(e.getMessage(), e);
1318: } else if (getLogger().isWarnEnabled()) {
1319: getLogger().warn(e.getMessage());
1320: }
1321:
1322: manageException(request, res, env, uri,
1323: "Resource Not Found", "Resource Not Found",
1324: "The requested portlet could not be found", e);
1325: return;
1326:
1327: } catch (ConnectionResetException e) {
1328: if (getLogger().isDebugEnabled()) {
1329: getLogger().debug(e.getMessage(), e);
1330: } else if (getLogger().isWarnEnabled()) {
1331: getLogger().warn(e.getMessage());
1332: }
1333:
1334: } catch (IOException e) {
1335: // Tomcat5 wraps SocketException into ClientAbortException which extends IOException.
1336: if (getLogger().isDebugEnabled()) {
1337: getLogger().debug(e.getMessage(), e);
1338: } else if (getLogger().isWarnEnabled()) {
1339: getLogger().warn(e.getMessage());
1340: }
1341:
1342: } catch (Exception e) {
1343: if (getLogger().isErrorEnabled()) {
1344: getLogger().error("Internal Cocoon Problem", e);
1345: }
1346:
1347: manageException(request, res, env, uri,
1348: "Internal Server Error", null, null, e);
1349: return;
1350: }
1351:
1352: long end = System.currentTimeMillis();
1353: String timeString = processTime(end - start);
1354: if (getLogger().isInfoEnabled()) {
1355: getLogger().info("'" + uri + "' " + timeString);
1356: }
1357: res.setProperty("X-Cocoon-Time", timeString);
1358: } finally {
1359: if (ctxMap != null) {
1360: ctxMap.clear();
1361: }
1362:
1363: try {
1364: if (request instanceof MultipartActionRequest) {
1365: if (getLogger().isDebugEnabled()) {
1366: getLogger().debug("Deleting uploaded file(s).");
1367: }
1368: ((MultipartActionRequest) request).cleanup();
1369: }
1370: } catch (IOException e) {
1371: getLogger()
1372: .error(
1373: "Cocoon got an Exception while trying to cleanup the uploaded files.",
1374: e);
1375: }
1376: }
1377: }
1378:
1379: /**
1380: * Process the specified <code>RenderRequest</code> producing output
1381: * on the specified <code>RenderResponse</code>.
1382: */
1383: public void render(RenderRequest req, RenderResponse res)
1384: throws PortletException, IOException {
1385:
1386: /* HACK for reducing class loader problems. */
1387: /* example: xalan extensions fail if someone adds xalan jars in tomcat3.2.1/lib */
1388: if (this .initClassLoader) {
1389: try {
1390: Thread.currentThread().setContextClassLoader(
1391: this .classLoader);
1392: } catch (Exception e) {
1393: }
1394: }
1395:
1396: // remember when we started (used for timing the processing)
1397: long start = System.currentTimeMillis();
1398:
1399: // add the cocoon header timestamp
1400: res.setProperty("X-Cocoon-Version", Constants.VERSION);
1401:
1402: // get the request (wrapped if contains multipart-form data)
1403: RenderRequest request = req;
1404:
1405: // Get the cocoon engine instance
1406: getCocoon(request.getParameter(Constants.RELOAD_PARAM));
1407:
1408: // Check if cocoon was initialized
1409: if (this .cocoon == null) {
1410: manageException(
1411: request,
1412: res,
1413: null,
1414: null,
1415: "Initialization Problem",
1416: null /* "Cocoon was not initialized" */,
1417: null /* "Cocoon was not initialized, cannot process request" */,
1418: this .exception);
1419: return;
1420: }
1421:
1422: // We got it... Process the request
1423: String servletPath = this .servletPath;
1424: if (servletPath == null) {
1425: servletPath = "portlets/"
1426: + getPortletConfig().getPortletName();
1427: }
1428: String pathInfo = getPathInfo(request);
1429:
1430: String uri = servletPath;
1431: if (pathInfo != null) {
1432: uri += pathInfo;
1433: }
1434:
1435: String contentType = null;
1436: ContextMap ctxMap = null;
1437:
1438: Environment env;
1439: try {
1440: if (uri.charAt(0) == '/') {
1441: uri = uri.substring(1);
1442: }
1443: env = getEnvironment(servletPath, pathInfo, uri, request,
1444: res);
1445: } catch (Exception e) {
1446: if (getLogger().isErrorEnabled()) {
1447: getLogger().error("Problem with Cocoon portlet", e);
1448: }
1449:
1450: manageException(request, res, null, uri,
1451: "Problem in creating the Environment", null, null,
1452: e);
1453: return;
1454: }
1455:
1456: try {
1457: try {
1458: // Initialize a fresh log context containing the object model: it
1459: // will be used by the CocoonLogFormatter
1460: ctxMap = ContextMap.getCurrentContext();
1461: // Add thread name (default content for empty context)
1462: String threadName = Thread.currentThread().getName();
1463: ctxMap.set("threadName", threadName);
1464: // Add the object model
1465: ctxMap.set("objectModel", env.getObjectModel());
1466: // Add a unique request id (threadName + currentTime
1467: ctxMap.set("request-id", threadName
1468: + System.currentTimeMillis());
1469:
1470: if (this .cocoon.process(env)) {
1471: contentType = env.getContentType();
1472: } else {
1473: // We reach this when there is nothing in the processing change that matches
1474: // the request. For example, no matcher matches.
1475: getLogger()
1476: .fatalError(
1477: "The Cocoon engine failed to process the request.");
1478: manageException(
1479: request,
1480: res,
1481: env,
1482: uri,
1483: "Request Processing Failed",
1484: "Cocoon engine failed in process the request",
1485: "The processing engine failed to process the request. This could be due to lack of matching or bugs in the pipeline engine.",
1486: null);
1487: return;
1488: }
1489: } catch (ResourceNotFoundException rse) {
1490: if (getLogger().isWarnEnabled()) {
1491: getLogger().warn("The resource was not found", rse);
1492: }
1493:
1494: manageException(request, res, env, uri,
1495: "Resource Not Found", "Resource Not Found",
1496: "The requested portlet could not be found", rse);
1497: return;
1498:
1499: } catch (ConnectionResetException e) {
1500: if (getLogger().isDebugEnabled()) {
1501: getLogger().debug(e.getMessage(), e);
1502: } else if (getLogger().isWarnEnabled()) {
1503: getLogger().warn(e.getMessage());
1504: }
1505:
1506: } catch (IOException e) {
1507: // Tomcat5 wraps SocketException into ClientAbortException which extends IOException.
1508: if (getLogger().isDebugEnabled()) {
1509: getLogger().debug(e.getMessage(), e);
1510: } else if (getLogger().isWarnEnabled()) {
1511: getLogger().warn(e.getMessage());
1512: }
1513:
1514: } catch (Exception e) {
1515: if (getLogger().isErrorEnabled()) {
1516: getLogger().error("Internal Cocoon Problem", e);
1517: }
1518:
1519: manageException(request, res, env, uri,
1520: "Internal Server Error", null, null, e);
1521: return;
1522: }
1523:
1524: long end = System.currentTimeMillis();
1525: String timeString = processTime(end - start);
1526: if (getLogger().isInfoEnabled()) {
1527: getLogger().info("'" + uri + "' " + timeString);
1528: }
1529: res.setProperty("X-Cocoon-Time", timeString);
1530:
1531: if (contentType != null && contentType.equals("text/html")) {
1532: String showTime = request
1533: .getParameter(Constants.SHOWTIME_PARAM);
1534: boolean show = this .showTime;
1535: if (showTime != null) {
1536: show = !showTime.equalsIgnoreCase("no");
1537: }
1538: if (show) {
1539: boolean hide = this .hiddenShowTime;
1540: if (showTime != null) {
1541: hide = showTime.equalsIgnoreCase("hide");
1542: }
1543: PrintStream out = new PrintStream(res
1544: .getPortletOutputStream());
1545: out.print((hide) ? "<!-- " : "<p>");
1546: out.print(timeString);
1547: out.println((hide) ? " -->" : "</p>\n");
1548: }
1549: }
1550: } finally {
1551: if (ctxMap != null) {
1552: ctxMap.clear();
1553: }
1554:
1555: try {
1556: if (request instanceof MultipartActionRequest) {
1557: if (getLogger().isDebugEnabled()) {
1558: getLogger().debug("Deleting uploaded file(s).");
1559: }
1560: ((MultipartActionRequest) request).cleanup();
1561: }
1562: } catch (IOException e) {
1563: getLogger()
1564: .error(
1565: "Cocoon got an Exception while trying to cleanup the uploaded files.",
1566: e);
1567: }
1568:
1569: /*
1570: * Portlet Specification 1.0, PLT.12.3.2 Output Stream and Writer Objects:
1571: * The termination of the render method of the portlet indicates
1572: * that the portlet has satisfied the request and that the output
1573: * object is to be closed.
1574: *
1575: * Portlet container will close the stream, no need to close it here.
1576: */
1577: }
1578: }
1579:
1580: private String getPathInfo(PortletRequest request) {
1581: PortletSession session = null;
1582:
1583: String pathInfo = request
1584: .getParameter(PortletEnvironment.PARAMETER_PATH_INFO);
1585: if (storeSessionPath) {
1586: session = request.getPortletSession(true);
1587: if (pathInfo == null) {
1588: pathInfo = (String) session
1589: .getAttribute(PortletEnvironment.PARAMETER_PATH_INFO);
1590: }
1591: }
1592:
1593: // Make sure it starts with or equals to '/'
1594: if (pathInfo == null) {
1595: pathInfo = "/";
1596: } else if (!pathInfo.startsWith("/")) {
1597: pathInfo = '/' + pathInfo;
1598: }
1599:
1600: if (storeSessionPath) {
1601: session.setAttribute(
1602: PortletEnvironment.PARAMETER_PATH_INFO, pathInfo);
1603: }
1604: return pathInfo;
1605: }
1606:
1607: protected void manageException(ActionRequest req,
1608: ActionResponse res, Environment env, String uri,
1609: String title, String message, String description,
1610: Exception e) throws PortletException {
1611: throw new PortletException("Exception in CocoonPortlet", e);
1612: }
1613:
1614: protected void manageException(RenderRequest req,
1615: RenderResponse res, Environment env, String uri,
1616: String title, String message, String description,
1617: Exception e) throws IOException, PortletException {
1618: if (this .manageExceptions) {
1619: if (env != null) {
1620: env.tryResetResponse();
1621: } else {
1622: res.reset();
1623: }
1624:
1625: String type = Notifying.FATAL_NOTIFICATION;
1626: HashMap extraDescriptions = null;
1627:
1628: extraDescriptions = new HashMap(2);
1629: extraDescriptions.put(Notifying.EXTRA_REQUESTURI,
1630: getPortletConfig().getPortletName());
1631: if (uri != null) {
1632: extraDescriptions.put("Request URI", uri);
1633: }
1634:
1635: // Do not show exception stack trace when log level is WARN or above. Show only message.
1636: if (!getLogger().isInfoEnabled()) {
1637: Throwable t = DefaultNotifyingBuilder.getRootCause(e);
1638: if (t != null)
1639: extraDescriptions.put(Notifying.EXTRA_CAUSE, t
1640: .getMessage());
1641: e = null;
1642: }
1643:
1644: Notifying n = new DefaultNotifyingBuilder().build(this , e,
1645: type, title, "Cocoon Portlet", message,
1646: description, extraDescriptions);
1647:
1648: res.setContentType("text/html");
1649: Notifier.notify(n, res.getPortletOutputStream(),
1650: "text/html");
1651: } else {
1652: res.flushBuffer();
1653: throw new PortletException("Exception in CocoonPortlet", e);
1654: }
1655: }
1656:
1657: /**
1658: * Create the environment for the request
1659: */
1660: protected Environment getEnvironment(String servletPath,
1661: String pathInfo, String uri, ActionRequest req,
1662: ActionResponse res) throws Exception {
1663: PortletEnvironment env;
1664:
1665: String formEncoding = req.getParameter("cocoon-form-encoding");
1666: if (formEncoding == null) {
1667: formEncoding = this .defaultFormEncoding;
1668: }
1669: env = new PortletEnvironment(servletPath, pathInfo, uri,
1670: this .portletContextURL, req, res, this .portletContext,
1671: (PortletContext) this .appContext
1672: .get(Constants.CONTEXT_ENVIRONMENT_CONTEXT),
1673: this .containerEncoding, formEncoding,
1674: this .defaultSessionScope);
1675: env.enableLogging(getLogger());
1676: return env;
1677: }
1678:
1679: /**
1680: * Create the environment for the request
1681: */
1682: protected Environment getEnvironment(String servletPath,
1683: String pathInfo, String uri, RenderRequest req,
1684: RenderResponse res) throws Exception {
1685: PortletEnvironment env;
1686:
1687: String formEncoding = req.getParameter("cocoon-form-encoding");
1688: if (formEncoding == null) {
1689: formEncoding = this .defaultFormEncoding;
1690: }
1691: env = new PortletEnvironment(servletPath, pathInfo, uri,
1692: this .portletContextURL, req, res, this .portletContext,
1693: (PortletContext) this .appContext
1694: .get(Constants.CONTEXT_ENVIRONMENT_CONTEXT),
1695: this .containerEncoding, formEncoding,
1696: this .defaultSessionScope);
1697: env.enableLogging(getLogger());
1698: return env;
1699: }
1700:
1701: /**
1702: * Instatiates the parent component manager, as specified in the
1703: * parent-component-manager init parameter.
1704: *
1705: * If none is specified, the method returns <code>null</code>.
1706: *
1707: * @return the parent component manager, or <code>null</code>.
1708: */
1709: protected synchronized ComponentManager getParentComponentManager() {
1710: if (parentComponentManager != null
1711: && parentComponentManager instanceof Disposable) {
1712: ((Disposable) parentComponentManager).dispose();
1713: }
1714:
1715: parentComponentManager = null;
1716: if (parentComponentManagerClass != null) {
1717: try {
1718: Class pcm = ClassUtils
1719: .loadClass(parentComponentManagerClass);
1720: Constructor pcmc = pcm
1721: .getConstructor(new Class[] { String.class });
1722: parentComponentManager = (ComponentManager) pcmc
1723: .newInstance(new Object[] { parentComponentManagerInitParam });
1724:
1725: if (parentComponentManager instanceof LogEnabled) {
1726: ((LogEnabled) parentComponentManager)
1727: .enableLogging(getLogger());
1728: }
1729: if (parentComponentManager instanceof Contextualizable) {
1730: ((Contextualizable) parentComponentManager)
1731: .contextualize(this .appContext);
1732: }
1733: if (parentComponentManager instanceof Initializable) {
1734: ((Initializable) parentComponentManager)
1735: .initialize();
1736: }
1737: } catch (Exception e) {
1738: if (getLogger().isErrorEnabled()) {
1739: getLogger()
1740: .error(
1741: "Could not initialize parent component manager.",
1742: e);
1743: }
1744: }
1745: }
1746: return parentComponentManager;
1747: }
1748:
1749: /**
1750: * Creates the Cocoon object and handles exception handling.
1751: */
1752: private synchronized void createCocoon() throws PortletException {
1753:
1754: /* HACK for reducing class loader problems. */
1755: /* example: xalan extensions fail if someone adds xalan jars in tomcat3.2.1/lib */
1756: if (this .initClassLoader) {
1757: try {
1758: Thread.currentThread().setContextClassLoader(
1759: this .classLoader);
1760: } catch (Exception e) {
1761: }
1762: }
1763:
1764: updateEnvironment();
1765: forceLoad();
1766: forceProperty();
1767:
1768: try {
1769: URL configFile = (URL) this .appContext
1770: .get(Constants.CONTEXT_CONFIG_URL);
1771: if (getLogger().isInfoEnabled()) {
1772: getLogger().info(
1773: "Reloading from: "
1774: + configFile.toExternalForm());
1775: }
1776: Cocoon c = (Cocoon) ClassUtils
1777: .newInstance("org.apache.cocoon.Cocoon");
1778: ContainerUtil.enableLogging(c, getCocoonLogger());
1779: c.setLoggerManager(getLoggerManager());
1780: ContainerUtil.contextualize(c, this .appContext);
1781: final ComponentManager parent = this
1782: .getParentComponentManager();
1783: if (parent != null) {
1784: ContainerUtil.compose(c, parent);
1785: }
1786: if (this .enableInstrumentation) {
1787: c.setInstrumentManager(getInstrumentManager());
1788: }
1789: ContainerUtil.initialize(c);
1790: this .creationTime = System.currentTimeMillis();
1791:
1792: disposeCocoon();
1793: this .cocoon = c;
1794: } catch (Exception e) {
1795: if (getLogger().isErrorEnabled()) {
1796: getLogger().error("Exception reloading", e);
1797: }
1798: this .exception = e;
1799: disposeCocoon();
1800: }
1801: }
1802:
1803: private Logger getCocoonLogger() {
1804: final String rootlogger = getInitParameter("cocoon-logger");
1805: if (rootlogger != null) {
1806: return this .getLoggerManager().getLoggerForCategory(
1807: rootlogger);
1808: } else {
1809: return getLogger();
1810: }
1811: }
1812:
1813: /**
1814: * Method to update the environment before Cocoon instances are created.
1815: *
1816: * This is also useful if you wish to customize any of the 'protected'
1817: * variables from this class before a Cocoon instance is built in a derivative
1818: * of this class (eg. Cocoon Context).
1819: */
1820: protected void updateEnvironment() throws PortletException {
1821: this .appContext
1822: .put(Constants.CONTEXT_CLASS_LOADER, classLoader);
1823: this .appContext
1824: .put(Constants.CONTEXT_CLASSPATH, getClassPath());
1825: }
1826:
1827: /**
1828: * Helper method to obtain an <code>InstrumentManager</code> instance
1829: *
1830: * @return an <code>InstrumentManager</code> instance
1831: */
1832: private InstrumentManager getInstrumentManager() throws Exception {
1833: String imConfig = getInitParameter("instrumentation-config");
1834: if (imConfig == null) {
1835: throw new PortletException(
1836: "Please define the init-param 'instrumentation-config' in your web.xml");
1837: }
1838:
1839: final InputStream is = this .portletContext
1840: .getResourceAsStream(imConfig);
1841: final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
1842: final Configuration conf = builder.build(is);
1843:
1844: // Get the logger for the instrument manager
1845: final String imLoggerCategory = conf.getAttribute("logger",
1846: "core.instrument");
1847: Logger imLogger = this .loggerManager
1848: .getLoggerForCategory(imLoggerCategory);
1849:
1850: // Set up the Instrument Manager
1851: DefaultInstrumentManagerImpl instrumentManager = new DefaultInstrumentManagerImpl();
1852: instrumentManager.enableLogging(imLogger);
1853: instrumentManager.configure(conf);
1854: instrumentManager.initialize();
1855:
1856: if (getLogger().isDebugEnabled()) {
1857: getLogger().debug(
1858: "Instrument manager created " + instrumentManager);
1859: }
1860:
1861: this .instrumentManager = instrumentManager;
1862: return instrumentManager;
1863: }
1864:
1865: private String processTime(long time) {
1866: StringBuffer out = new StringBuffer(PROCESSED_BY);
1867: if (time <= SECOND) {
1868: out.append(time);
1869: out.append(" milliseconds.");
1870: } else if (time <= MINUTE) {
1871: out.append(time / SECOND);
1872: out.append(" seconds.");
1873: } else if (time <= HOUR) {
1874: out.append(time / MINUTE);
1875: out.append(" minutes.");
1876: } else {
1877: out.append(time / HOUR);
1878: out.append(" hours.");
1879: }
1880: return out.toString();
1881: }
1882:
1883: /**
1884: * Gets the current cocoon object. Reload cocoon if configuration
1885: * changed or we are reloading.
1886: */
1887: private void getCocoon(final String reloadParam)
1888: throws PortletException {
1889: if (this .allowReload) {
1890: boolean reload = false;
1891:
1892: if (this .cocoon != null) {
1893: if (this .cocoon.modifiedSince(this .creationTime)) {
1894: if (getLogger().isInfoEnabled()) {
1895: getLogger().info(
1896: "Configuration changed reload attempt");
1897: }
1898: reload = true;
1899: } else if (reloadParam != null) {
1900: if (getLogger().isInfoEnabled()) {
1901: getLogger().info("Forced reload attempt");
1902: }
1903: reload = true;
1904: }
1905: } else if (reloadParam != null) {
1906: if (getLogger().isInfoEnabled()) {
1907: getLogger().info("Invalid configurations reload");
1908: }
1909: reload = true;
1910: }
1911:
1912: if (reload) {
1913: initLogger();
1914: createCocoon();
1915: }
1916: }
1917: }
1918:
1919: /**
1920: * Destroy Cocoon
1921: */
1922: private final void disposeCocoon() {
1923: if (this .cocoon != null) {
1924: ContainerUtil.dispose(this .cocoon);
1925: this .cocoon = null;
1926: }
1927: }
1928:
1929: /**
1930: * Get an initialisation parameter. The value is trimmed, and null is returned if the trimmed value
1931: * is empty.
1932: */
1933: public String getInitParameter(String name) {
1934: String result = super .getInitParameter(name);
1935: if (result != null) {
1936: result = result.trim();
1937: if (result.length() == 0) {
1938: result = null;
1939: }
1940: }
1941:
1942: return result;
1943: }
1944:
1945: /** Convenience method to access portlet parameters */
1946: protected String getInitParameter(String name, String defaultValue) {
1947: String result = getInitParameter(name);
1948: if (result == null) {
1949: if (getLogger() != null && getLogger().isDebugEnabled()) {
1950: getLogger().debug(
1951: name + " was not set - defaulting to '"
1952: + defaultValue + "'");
1953: }
1954: return defaultValue;
1955: } else {
1956: return result;
1957: }
1958: }
1959:
1960: /** Convenience method to access boolean portlet parameters */
1961: protected boolean getInitParameterAsBoolean(String name,
1962: boolean defaultValue) {
1963: String value = getInitParameter(name);
1964: if (value == null) {
1965: if (getLogger() != null && getLogger().isDebugEnabled()) {
1966: getLogger().debug(
1967: name + " was not set - defaulting to '"
1968: + defaultValue + "'");
1969: }
1970: return defaultValue;
1971: }
1972:
1973: return BooleanUtils.toBoolean(value);
1974: }
1975:
1976: protected int getInitParameterAsInteger(String name,
1977: int defaultValue) {
1978: String value = getInitParameter(name);
1979: if (value == null) {
1980: if (getLogger() != null && getLogger().isDebugEnabled()) {
1981: getLogger().debug(
1982: name + " was not set - defaulting to '"
1983: + defaultValue + "'");
1984: }
1985: return defaultValue;
1986: } else {
1987: return Integer.parseInt(value);
1988: }
1989: }
1990:
1991: protected Logger getLogger() {
1992: return this .log;
1993: }
1994:
1995: protected LoggerManager getLoggerManager() {
1996: return this.loggerManager;
1997: }
1998: }
|