blob: 28872d936abdc6e158406b2d0c30eeb05fe117c2 [file] [log] [blame]
/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.service.servlet.impl;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.avalon.framework.logger.Logger;
import org.apache.cocoon.ConnectionResetException;
import org.apache.cocoon.Constants;
import org.apache.cocoon.Processor;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.components.notification.DefaultNotifyingBuilder;
import org.apache.cocoon.components.notification.Notifier;
import org.apache.cocoon.components.notification.Notifying;
import org.apache.cocoon.core.Core;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.environment.http.HttpEnvironment;
import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest;
import org.apache.cocoon.servlet.multipart.RequestFactory;
import org.apache.commons.lang.time.StopWatch;
/**
* This is the entry point for Cocoon execution as an HTTP Servlet.
*
* @version $Id$
*/
public class BlocksServlet extends HttpServlet {
/**
* Application <code>Context</code> Key for the servlet configuration
* @since 2.1.3
*/
public static final String CONTEXT_SERVLET_CONFIG = "servlet-config";
// Processing time message
protected static final String PROCESSED_BY = "Processed by "
+ Constants.COMPLETE_NAME + " in ";
// Used by "show-time"
static final float SECOND = 1000;
static final float MINUTE = 60 * SECOND;
static final float HOUR = 60 * MINUTE;
/**
* The <code>Processor</code> instance
*/
protected Processor processor;
/**
* Holds exception happened during initialization (if any)
*/
protected Exception exception;
private String containerEncoding;
/** The classloader that will be set as the context classloader if init-classloader is true */
protected ClassLoader classLoader;
/**
* The RequestFactory is responsible for wrapping multipart-encoded
* forms and for handing the file payload of incoming requests
*/
protected RequestFactory requestFactory;
/** Core */
protected Core core;
/** The logger */
protected Logger log;
public BlocksServlet(ClassLoader classLoader,
Logger logger,
Core core,
Processor processor) {
this.classLoader = classLoader;
this.log = logger;
this.core = core;
this.processor = processor;
}
/**
* Initialize this <code>BlocksServlet</code> instance. You will
* notice that I have broken the init into sub methods to make it
* easier to maintain (BL). The context is passed to a couple of
* the subroutines. This is also because it is better to explicitly
* pass variables than implicitely. It is both more maintainable,
* and more elegant.
*
* @param conf The ServletConfig object from the servlet engine.
*
* @throws ServletException
*/
public void init(ServletConfig conf)
throws ServletException {
super.init(conf);
this.getLogger().info("Initializing Apache Cocoon " + Constants.VERSION);
this.containerEncoding = this.getInitParameter("container-encoding", "ISO-8859-1");
this.requestFactory = new RequestFactory(this.core.getSettings().isAutosaveUploads(),
new File(this.core.getSettings().getUploadDirectory()),
this.core.getSettings().isAllowOverwrite(),
this.core.getSettings().isSilentlyRename(),
this.core.getSettings().getMaxUploadSize(),
this.containerEncoding);
}
/**
* Dispose Cocoon when servlet is destroyed
*/
public void destroy() {
this.classLoader = null;
this.log = null;
this.core = null;
this.processor = null;
this.requestFactory = null;
super.destroy();
}
/**
* Process the specified <code>HttpServletRequest</code> producing output
* on the specified <code>HttpServletResponse</code>.
*/
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
/* Needed in OSGi environment */
try {
Thread.currentThread().setContextClassLoader(this.classLoader);
} catch (Exception e) {
// ignore
}
// used for timing the processing
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// add the cocoon header timestamp
if (this.core.getSettings().isShowVersion()) {
res.addHeader("X-Cocoon-Version", Constants.VERSION);
}
// get the request (wrapped if contains multipart-form data)
HttpServletRequest request;
try{
if (this.core.getSettings().isEnableUploads()) {
request = requestFactory.getServletRequest(req);
} else {
request = req;
}
} catch (Exception e) {
if (getLogger().isErrorEnabled()) {
getLogger().error("Problem with Cocoon servlet", e);
}
manageException(req, res, null, null,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Problem in creating the Request", null, null, e);
return;
}
// We got it... Process the request
String uri = request.getServletPath();
if (uri == null) {
uri = "";
}
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
// VG: WebLogic fix: Both uri and pathInfo starts with '/'
// This problem exists only in WL6.1sp2, not in WL6.0sp2 or WL7.0b.
if (uri.length() > 0 && uri.charAt(0) == '/') {
uri = uri.substring(1);
}
uri += pathInfo;
}
if (uri.length() == 0) {
/* empty relative URI
-> HTTP-redirect from /cocoon to /cocoon/ to avoid
StringIndexOutOfBoundsException when calling
"".charAt(0)
else process URI normally
*/
String prefix = request.getRequestURI();
if (prefix == null) {
prefix = "";
}
res.sendRedirect(res.encodeRedirectURL(prefix + "/"));
return;
}
String contentType = null;
Object handle = null;
Environment env;
try{
if (uri.charAt(0) == '/') {
uri = uri.substring(1);
}
// Pass uri into environment without URLDecoding, as it is already decoded.
env = getEnvironment(uri, request, res);
} catch (Exception e) {
if (getLogger().isErrorEnabled()) {
getLogger().error("Problem with Cocoon servlet", e);
}
manageException(request, res, null, uri,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Problem in creating the Environment", null, null, e);
return;
}
try {
try {
// FIXME: don't want the Servlet depend on coreUtil, find other way to initialized request
// handle = this.coreUtil.initializeRequest(env);
if (this.processor.process(env)) {
contentType = env.getContentType();
} else {
// We reach this when there is nothing in the processing change that matches
// the request. For example, no matcher matches.
getLogger().fatalError("The Cocoon engine failed to process the request.");
manageException(request, res, env, uri,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Request Processing Failed",
"Cocoon engine failed in process the request",
"The processing engine failed to process the request. This could be due to lack of matching or bugs in the pipeline engine.",
null);
return;
}
} catch (ResourceNotFoundException e) {
if (getLogger().isDebugEnabled()) {
getLogger().warn(e.getMessage(), e);
} else if (getLogger().isWarnEnabled()) {
getLogger().warn(e.getMessage());
}
manageException(request, res, env, uri,
HttpServletResponse.SC_NOT_FOUND,
"Resource Not Found",
"Resource Not Found",
"The requested resource \"" + request.getRequestURI() + "\" could not be found",
e);
return;
} catch (ConnectionResetException e) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(e.toString(), e);
} else if (getLogger().isWarnEnabled()) {
getLogger().warn(e.toString());
}
} catch (IOException e) {
// Tomcat5 wraps SocketException into ClientAbortException which extends IOException.
if (getLogger().isDebugEnabled()) {
getLogger().debug(e.toString(), e);
} else if (getLogger().isWarnEnabled()) {
getLogger().warn(e.toString());
}
} catch (Exception e) {
if (getLogger().isErrorEnabled()) {
getLogger().error("Internal Cocoon Problem", e);
}
manageException(request, res, env, uri,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Internal Server Error", null, null, e);
return;
}
stopWatch.stop();
String timeString = null;
if (getLogger().isInfoEnabled()) {
timeString = processTime(stopWatch.getTime());
getLogger().info("'" + uri + "' " + timeString);
}
if (contentType != null && contentType.equals("text/html")) {
String showTime = request.getParameter(Constants.SHOWTIME_PARAM);
boolean show = this.core.getSettings().isShowTime();
if (showTime != null) {
show = !showTime.equalsIgnoreCase("no");
}
if (show) {
if ( timeString == null ) {
timeString = processTime(stopWatch.getTime());
}
boolean hide = this.core.getSettings().isHideShowTime();
if (showTime != null) {
hide = showTime.equalsIgnoreCase("hide");
}
ServletOutputStream out = res.getOutputStream();
out.print((hide) ? "<!-- " : "<p>");
out.print(timeString);
out.println((hide) ? " -->" : "</p>");
}
}
} finally {
// FIXME: don't want the Servlet depend on coreUtil, find other way to clean up request
// this.coreUtil.cleanUpRequest(handle);
try {
if (request instanceof MultipartHttpServletRequest) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Deleting uploaded file(s).");
}
((MultipartHttpServletRequest) request).cleanup();
}
} catch (IOException e) {
getLogger().error("Cocoon got an Exception while trying to cleanup the uploaded files.", e);
}
try {
OutputStream out = res.getOutputStream();
out.flush();
out.close();
} catch (SocketException se) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("SocketException while trying to close stream.", se);
} else if (getLogger().isWarnEnabled()) {
getLogger().warn("SocketException while trying to close stream.");
}
} catch (IOException e) {
// See: http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=107489037219505
if (getLogger().isDebugEnabled()) {
getLogger().debug("IOException while trying to close stream.", e);
}
} catch (Exception e) {
getLogger().error("Exception while trying to close stream.", e);
}
}
}
protected void manageException(HttpServletRequest req, HttpServletResponse res, Environment env,
String uri, int errorStatus,
String title, String message, String description,
Exception e)
throws IOException {
if (this.core.getSettings().isManageExceptions()) {
if (env != null) {
env.tryResetResponse();
} else {
res.reset();
}
String type = Notifying.FATAL_NOTIFICATION;
HashMap extraDescriptions = null;
if (errorStatus == HttpServletResponse.SC_NOT_FOUND) {
type = "resource-not-found";
// Do not show the exception stacktrace for such common errors.
e = null;
} else {
extraDescriptions = new HashMap(2);
extraDescriptions.put(Notifying.EXTRA_REQUESTURI, req.getRequestURI());
if (uri != null) {
extraDescriptions.put("Request URI", uri);
}
// Do not show exception stack trace when log level is WARN or above. Show only message.
if (!getLogger().isInfoEnabled()) {
Throwable t = DefaultNotifyingBuilder.getRootCause(e);
if (t != null) extraDescriptions.put(Notifying.EXTRA_CAUSE, t.getMessage());
e = null;
}
}
Notifying n = new DefaultNotifyingBuilder().build(this,
e,
type,
title,
"Cocoon Servlet",
message,
description,
extraDescriptions);
res.setContentType("text/html");
res.setStatus(errorStatus);
Notifier.notify(n, res.getOutputStream(), "text/html");
} else {
res.sendError(errorStatus, title);
res.flushBuffer();
}
}
/**
* Create the environment for the request
*/
protected Environment getEnvironment(String uri,
HttpServletRequest req,
HttpServletResponse res)
throws Exception {
HttpEnvironment env;
String formEncoding = req.getParameter("cocoon-form-encoding");
if (formEncoding == null) {
formEncoding = this.core.getSettings().getFormEncoding();
}
env = new HttpEnvironment(uri,
null,
req,
res,
null,
this.core.getEnvironmentContext(),
this.containerEncoding,
formEncoding);
env.enableLogging(getLogger());
return env;
}
private String processTime(long time) {
StringBuffer out = new StringBuffer(PROCESSED_BY);
if (time <= SECOND) {
out.append(time);
out.append(" milliseconds.");
} else if (time <= MINUTE) {
out.append(time / SECOND);
out.append(" seconds.");
} else if (time <= HOUR) {
out.append(time / MINUTE);
out.append(" minutes.");
} else {
out.append(time / HOUR);
out.append(" hours.");
}
return out.toString();
}
/**
* Get an initialisation parameter. The value is trimmed, and null is returned if the trimmed value
* is empty.
*/
public String getInitParameter(String name) {
String result = super.getInitParameter(name);
if (result != null) {
result = result.trim();
if (result.length() == 0) {
result = null;
}
}
return result;
}
/** Convenience method to access servlet parameters */
protected String getInitParameter(String name, String defaultValue) {
String result = getInitParameter(name);
if (result == null) {
if (getLogger() != null && getLogger().isDebugEnabled()) {
getLogger().debug(name + " was not set - defaulting to '" + defaultValue + "'");
}
return defaultValue;
}
return result;
}
protected Logger getLogger() {
return this.log;
}
}