/*
* Copyright 2001-2006 C:1 Financial Services GmbH
*
* This software is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License Version 2.1, as published by the Free Software Foundation.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
*/
package de.finix.contelligent.servlet;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import de.finix.contelligent.CallData;
import de.finix.contelligent.Component;
import de.finix.contelligent.ComponentManager;
import de.finix.contelligent.ComponentNotFoundException;
import de.finix.contelligent.ComponentPath;
import de.finix.contelligent.Contelligent;
import de.finix.contelligent.HTTPCallData;
import de.finix.contelligent.MaxSessionsExceededException;
import de.finix.contelligent.Session;
import de.finix.contelligent.SessionCreationException;
import de.finix.contelligent.action.Action;
import de.finix.contelligent.category.CategoryException;
import de.finix.contelligent.content.BinaryContent;
import de.finix.contelligent.content.Content;
import de.finix.contelligent.content.ContentProvider;
import de.finix.contelligent.content.Metadata;
import de.finix.contelligent.core.BasicComponentManager;
import de.finix.contelligent.core.BuildInfo;
import de.finix.contelligent.core.ComponentManagerInternal;
import de.finix.contelligent.core.ContelligentImpl;
import de.finix.contelligent.core.ContelligentSession;
import de.finix.contelligent.core.ErrorHandler;
import de.finix.contelligent.core.HTTPCallDataImpl;
import de.finix.contelligent.core.SystemFilesServletImpl;
import de.finix.contelligent.core.TimeService;
import de.finix.contelligent.core.UserHome;
import de.finix.contelligent.core.security.ComponentPermission;
import de.finix.contelligent.core.security.ContelligentSecurityManager;
import de.finix.contelligent.core.security.User;
import de.finix.contelligent.exception.ContelligentException;
import de.finix.contelligent.exception.ContelligentExceptionID;
import de.finix.contelligent.exception.ContelligentRuntimeException;
import de.finix.contelligent.exception.ContelligentSecurityException;
import de.finix.contelligent.exception.DuplicateLoginException;
import de.finix.contelligent.exception.InvalidSessionException;
import de.finix.contelligent.exception.NoReadPermissionException;
import de.finix.contelligent.logging.LoggingService;
import de.finix.contelligent.render.DefaultRenderer;
import de.finix.contelligent.render.PageRenderer;
import de.finix.contelligent.render.Renderable;
import de.finix.contelligent.render.Renderer;
import de.finix.contelligent.util.ThreadedMem;
/**
* The main servlet for handling any HTTP request into the Contelligent system
*
* @web:servlet name="Contelligent Servlet" description="Main Contelligent
* servlet"
* load-on-startup="@contelligent.servlet.load-on-startup@"
*
* @web:servlet-mapping url-pattern="@contelligent.servlet.url-pattern@/*"
* @web:servlet-mapping url-pattern="@contelligent.servlet.sso-pattern@/*"
*
* @web:resource-ref name="jdbc/DataSource" type="javax.sql.DataSource"
* auth="Container"
* @web:resource-ref name="jdbc/DataSourceNoTx" type="javax.sql.DataSource"
* auth="Container"
*
* @web:env-entry name="contelligentDir" value="@contelligent.home@"
* type="java.lang.String"
*
* @jboss:resource-ref res-ref-name="jdbc/DataSource"
* jndi-name="java:contelligentDB"
* @jboss:resource-ref res-ref-name="jdbc/DataSourceNoTx"
* jndi-name="java:contelligentDBnoTx"
*
* @weblogic:resource-description res-ref-name="jdbc/DataSource"
* jndi-name="/weblogic/jdbc/contelligentDB"
* @weblogic:resource-description res-ref-name="jdbc/DataSourceNoTx"
* jndi-name="/weblogic/jdbc/contelligentDBnoTx"
*
* @websphere:resource-ref res-ref-name="jdbc/DataSource"
* jndi-name="java:contelligentDB"
* @websphere:resource-ref res-ref-name="jdbc/DataSourceNoTx"
* jndi-name="java:contelligentDBnoTx"
*/
public class InvokerBase implements Servlet {
final static org.apache.log4j.Logger log = LoggingService.getLogger(InvokerBase.class);
// search engine bots user agents, used as substrings
private final static String[] searchEngineBots = new String[] {
"googlebot", "scooter", "slurp", "ia_archiver", "architextspider",
"sidewinder", "spider", "msnbot", "sproose", "iccrawler", "crawler",
"jeeves", "exabot", "snapbot", "findlinks", "turnitinbot",
"mj12bot", "holmes", "krugle", "seekbot", "envolk" };
/** The http-session key for the contelligent session object. */
private static final String CONTELLIGENT_SESSION_KEY = "contelligent/session";
private static final String METADATA_HEADER_PREFIX = "header:";
private static HashSet instances = new HashSet();
private boolean initialized = false; // flag to prevent double
// initialization.
private ServletConfig config;
private ServletContext servletContext;
protected ContelligentImpl contelligent;
private ErrorHandler errorHandler;
private String defaultEncoding;
private static int threadNum = 1;
/**
* Initializes this servlet and the
* {@link ContelligentImpl Contelligent-System}.
*
* @param config
* the <code>ServletConfig</code> of this servlet
* @exception UnavailableException
* if the {@link ContelligentImpl Contelligent-System} could
* not be initialized.
* @exception ServletException
* if an internal error occurs during initializarion of this
* servlet.
*/
public synchronized void init(ServletConfig config) throws ServletException {
synchronized (instances) {
if (initialized) {
servletContext.log("Attempt to initialize again!!");
return;
}
this.config = config;
servletContext = config.getServletContext();
try {
log.debug("init() - starting initialization ...");
if (!ContelligentImpl.isInitialized()) {
ContelligentImpl.init(new SystemFilesServletImpl(servletContext, "META-INF/")); // throws
// Exception
// on error
}
contelligent = ContelligentImpl.getInstance();
errorHandler = ContelligentImpl.getInstance().getErrorHandler();
if (errorHandler == null) {
throw new ContelligentException("No Error-Handler defined!");
}
defaultEncoding = ContelligentImpl.getInstance().getDefaultEncoding();
initialized = true;
instances.add(this);
log.debug("init() - ... initialization done.");
// XXX: if we throw an UnavailableException and this servlet
// gets called after Contelligent
// was initialized properly but via a different servlet (e.g.
// ServerInfo), this servlet
// never gets into service! (2002/11/08 rs)
} catch (ContelligentException e) {
// log.fatal("init() - ContelligenException while initializing
// Contelligent: "+e);
// XXX: JBoss 3 does a complete undeploy in case of an
// exception, so don't throw it
// throw new UnavailableException("Exception while initializing
// Contelligent: "+e.getMessage());
} catch (Exception e) {
log.fatal("init() - Exception while initializing main servlet: ", e);
throw new ServletException("Exception while initializing main servlet: ", e);
} finally {
log.info("init() - ... end.");
}
}
}
public ServletConfig getServletConfig() {
return config;
}
public String getServletInfo() {
return "Contelligent Main Servlet, (c) 2001 - 2006 C:1 financial services";
}
public synchronized void destroy() {
synchronized (instances) {
instances.remove(this);
if (instances.isEmpty()) {
// if system is shutted down while SystemIndex is still indexing components, the pending components to be indexed would be forgotten.
// because of that, we have to save a list of this components per context/ComponentManager, so that SystemIndex can continue where it stopped.
ContelligentImpl contelligent = ContelligentImpl.getInstance();
if (contelligent.hasSystemIndex()) {
log.debug("Trying to save pending components to be indexed by SystemIndex...");
Set componentManagerNames = contelligent.getComponentManagerHierarchy().getComponentManagerNames();
Iterator componentManagerNamesIterator = componentManagerNames.iterator();
ComponentManager rootCM = contelligent.getRootComponentManager();
User indexUser = ContelligentSecurityManager.getIndexUser();
try {
Session session = contelligent.beginSession(indexUser, rootCM);
contelligent.beginTx(3600);
CallData callData = contelligent.createCallData(session);
ComponentPath homePath = UserHome.getRootPath().append(indexUser.getGroupId()).append(
indexUser.getName());
if (rootCM.componentExists(homePath)) {
rootCM.deleteComponentTree(callData, homePath);
}
while (componentManagerNamesIterator.hasNext()) {
String componentManagerName = (String) componentManagerNamesIterator.next();
BasicComponentManager cm = (BasicComponentManager) contelligent
.getComponentManager(componentManagerName);
// stop the thread/run() method of SystemIndexer
cm.getSystemIndexer().stopThread();
while (cm.getSystemIndexer().isRunning()) {
try {
Thread.sleep(100);
continue;
} catch (InterruptedException e) {
break;
}
}
cm.getSystemIndexer().savePendingComponents(callData);
}
contelligent.commitTx();
} catch (SessionCreationException sce) {
log.error("Failed to save indexing status.", sce);
} catch (Exception e) {
log.error("Failed to save indexing status.", e);
try {
contelligent.rollbackTx();
} catch (javax.transaction.SystemException se) {
log.error("Rollback failed.", se);
}
}
}
log.debug("destroy() - calling contelligent shutdown ...");
ContelligentImpl.shutdown();
}
log.debug("destroy() - instance destroyed.");
}
}
/**
* Handles any HTTP request. <BR>
* Currently this method may only be called once for a single request, so
* forward is not allowed! Since we don't support JSPs with Contelligent
* tags at the moment this is not an issue. <BR>
* Note that this method starts a new transaction for every call.
*
* @param req
* a <code>HttpServletRequest</code> value
* @param res
* a <code>HttpServletResponse</code> value
* @exception ServletException
* if an error occurs
* @exception IOException
* if an error occurs
*/
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
// Make thread identifiable
Thread currentThread = Thread.currentThread();
int myNum = 0;
synchronized (this) {
myNum = threadNum;
threadNum++;
if (threadNum > 10000) {
threadNum = 1;
}
}
currentThread.setName("Request-" + request.getRemoteAddr() + "#" + String.valueOf(myNum));
// Set the character encoding to force Tomcat to decode the body properly.
// This must be done before we do anything else with the request or it will silently return garbage.
try {
request.setCharacterEncoding(ContelligentImpl.getInstance().getParameterEncoding());
} catch (Exception e) {
log.error("Unable to set request encoding: ", e);
}
// Set the timeout once per request; it's a cheap operation anyway
int timeout = ContelligentImpl.getInstance().getDefaultTransactionTimeout();
request.getSession().setMaxInactiveInterval(timeout);
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
final boolean debugEnabled = log.isDebugEnabled();
// XXX: DEBUG ONLY
if (debugEnabled) {
Enumeration enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String headerName = (String) enumeration.nextElement();
log.debug("... header '" + headerName + "'='" + request.getHeader(headerName) + "' ...");
}
}
String method = request.getMethod();
boolean get = method.equals("GET");
boolean post = method.equals("POST");
boolean head = method.equals("HEAD");
String servletPath = null;
HTTPCallDataImpl callData = null;
ContelligentSession session = null;
ComponentPath compPath = null;
if (get || post || head) {
boolean txStarted = false;
boolean txCommited = false;
try {
if (request.getAttribute("javax.servlet.include.path_info") == null) {
servletPath = request.getPathInfo();
} else {
servletPath = (String) request.getAttribute("javax.servlet.include.path_info");
}
compPath = new ComponentPath(servletPath);
String extension = "";
txStarted = contelligent.beginTx();
callData = setupEnvironment(request, response, getServletConfig().getServletContext(), null, null);
session = (ContelligentSession) callData.getContelligentSession();
if (session.isMarkedForLogout()) {
User user = session.getUser();
HttpSession httpSession = callData.getHttpServletRequest().getSession(false);
if (httpSession != null) {
httpSession.invalidate();
}
callData.getSystem().resetSession(session);
throw new DuplicateLoginException(user.getName());
}
// override category map for this call
Map paramMap = callData.getParameters();
if (debugEnabled)
log.debug("service() - ... param-map=" + paramMap);
// XXX: the default for a PageRenderer is MODE_REF now which is
// right for almost any
// call except top-level ones. Therefore set mode to markup if
// not explicitly set. (rs)
if (!paramMap.containsKey(PageRenderer.MODE)) {
paramMap.put(PageRenderer.MODE, new String[] { PageRenderer.MODE_MARKUP });
}
ComponentManager manager = session.getComponentManager();
if (debugEnabled) {
log.debug("service() - trying to get component '" + compPath + "' from manager '" + manager
+ "' ...");
}
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
if (!contelligent.isInPublicDir(compPath)) {
throw new InvalidSessionException("Requested session id not valid (potential session timeout)");
}
}
Component component;
try {
component = manager.getComponent(compPath, callData);
} catch (ComponentNotFoundException cne) {
// Component with extension was not found; try the short path then if possible
int dotPos = servletPath.lastIndexOf('.');
int slashPos = servletPath.lastIndexOf('/');
if ((dotPos != 1) && (slashPos < dotPos)) {
compPath = new ComponentPath(servletPath.substring(0, dotPos));
extension = servletPath.substring(dotPos);
component = manager.getComponent(compPath, callData);
} else {
// Rethrow to normal handler
throw cne;
}
}
// suffix is currently not used ....
// String suffix = (sep==-1) ? "" : servletPath.substring(sep);
// XXX: maybe use unsynchronized stream to improve performance?
// (rs)
ByteArrayOutputStream outStream = new ByteArrayOutputStream(16384);
// we always set the Last-Modified header
long lastModified = getLastModified(component, callData, request);
if (get) {
long ifModifiedSince = request.getDateHeader("If-Modified-Since"); // if
// !=-1
// this is a
// conditional
// GET
if (ifModifiedSince == -1 || (ifModifiedSince < lastModified)) {
handleRequest(outStream, component, true, callData, request, response, true, extension);
} else {
if (debugEnabled)
log.debug("service() - ... 'If-Modified-Since' <= 'Last-Modified' --> sending 304.");
response.setStatus(304); // not-modified
}
} else if (head) {
// false: don't include body
handleRequest(outStream, component, true, callData, request, response, false, extension);
} else { // POST
handleRequest(outStream, component, true, callData, request, response, true, extension);
}
if (component.isDynamic()) {
if (!response.containsHeader("Cache-Control")) {
response.setHeader("Cache-Control", "no-cache");
}
// Be extra explicit, so IE understands us...
if (!response.containsHeader("Pragma")) {
response.setHeader("Pragma", "no-cache");
}
if (!response.containsHeader("Expires")) {
response.setHeader("Expires", "0");
}
} else {
// private since we have always authentication. Use public
// if a proxy should cache it!
if (!response.containsHeader("Cache-Control")) {
response.setHeader("Cache-Control", "private");
}
// response.setDateHeader("Expires",
// (TimeService.getInstance().currentTimeMillis()+(1000*3600)));
// response.setDateHeader("Date",
// TimeService.getInstance().currentTimeMillis());
if (!response.containsHeader("Last-Modified")) {
response.setDateHeader("Last-Modified", lastModified);
if (debugEnabled)
log.debug("service() - set 'Last-Modified' header to '" + lastModified + "' ...");
}
}
// first commit, then write contents !
if (txStarted) {
// important: set this true BEFORE actually trying to commit
// because every exception
// thrown by commitTx() means we can neither commit nor
// rollback in the finally block ! (rs)
txCommited = true;
contelligent.commitTx();
if (debugEnabled)
log.debug("service() - ... successfully commited transaction.");
}
// this is for components that do sendRedirect or use forward!!!
// Never write to stream if already committed!!
if (!response.isCommitted()) {
outStream.writeTo(response.getOutputStream());
response.flushBuffer();
}
} catch (NoReadPermissionException e) {
if (debugEnabled) {
log.warn("service() - trying to handle throwable: ", e);
} else {
log.warn("service() - trying to handle throwable: " + e.getMessage());
}
handleException(e, compPath, callData, request, response); // throws ServletException, IOException
} catch (MaxSessionsExceededException e) {
log.error("service() - max session exceeded exception: ", e);
handleException(e, compPath, callData, request, response); // throws ServletException, IOException
} catch (OutOfMemoryError oome) {
synchronized (this.getClass()) {
log.error("Ran out of memory while processing request. Adjusting cache size.");
ComponentManager rootManager = ContelligentImpl.getInstance().getRootComponentManager();
if (rootManager instanceof ComponentManagerInternal) {
ComponentManagerInternal cmi = (ComponentManagerInternal) rootManager;
int actualCacheSize = cmi.getActualCacheSize();
int lastMaxCacheSize = cmi.getMaxCacheSize();
log.info("Current cache size is " + actualCacheSize);
log.info("Last maximum cache size was " + lastMaxCacheSize);
// Reduce current size by 10% to free some space for overhead
int newMaxCacheSize = (int) Math.round(actualCacheSize * 0.90);
log.info("Setting maximum cache size to " + newMaxCacheSize);
cmi.setMaxCacheSize(newMaxCacheSize);
} else {
log.warn("The current rootManager does not support this feature!");
}
handleException(oome, compPath, callData, request, response); // throws ServletException, IOException
}
} catch (SessionCreationException sce) {
// Cant use the normal error page mechanism here
response.sendError(500, sce.getMessage());
} catch (Throwable t) {
log.error("service() - trying to handle throwable: ", t);
handleException(t, compPath, callData, request, response); // throws ServletException, IOException
} finally {
try {
if (txStarted && !txCommited) { // means something went wrong before trying to call commitTx() !
try {
StringBuffer httpRequest = new StringBuffer();
httpRequest.append(method).append(" ").append(servletPath);
log.info("service() - Exception while serving '" + httpRequest
+ "', calling rollbackTx() ...");
contelligent.rollbackTx();
log.info("service() - ... transaction for serving '" + httpRequest + "' rolled back.");
} catch (Exception e) {
log.error("service() - Exception during rollback: ", e);
throw new ServletException("Exception during rollback!", e);
}
}
} finally {
if (callData != null) {
if (callData.isSearchEngineSpider()) {
contelligent.invalidateSession(callData.getContelligentSession());
}
}
}
}
} else if (method.equals("OPTIONS")) {
response.setHeader("Allow", "GET, HEAD, POST, OPTIONS");
} else {
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "HTTP method '" + method + "' not supported!");
}
}
/**
* Dispatches the request depending on the interfaces the given component
* does implement. The specific methods must set the
* {@link ServletResponse#setContentType content-type} and the
* {@link ServletResponse#setContentLength content-length} of the response.
*/
private void handleRequest(OutputStream outStream, Component component, boolean checkAccess, HTTPCallData callData,
HttpServletRequest request, HttpServletResponse response, boolean includeBody, String extension)
throws Exception {
if (!(component instanceof Action)) {
// Check read permission for any requested object except Actions
ContelligentSession session = (ContelligentSession) callData.getContelligentSession();
User caller = session.getUser();
if (!callData.getActualManager().callerHasPermission(caller, callData, component, ComponentPermission.READ)) {
throw new NoReadPermissionException(caller, component.getComponentContext().getPath());
}
if (component.getComponentContext().requiresSecureTransfer() && !(request.isSecure() || request.getScheme().toLowerCase().equals("https"))
&& ContelligentImpl.getInstance().supportSecureComponents()) {
// throw new InsecureRequestException(component.getComponentContext().getPath());
response.sendRedirect(callData.getSecureBaseURL() + component.getComponentContext().getPath()
+ extension);
return;
}
}
// Set headers as specified in component metadata
Metadata metadata = component.getComponentContext().getMetadata();
Iterator metaKeys = metadata.getKeys().iterator();
while (metaKeys.hasNext()) {
String key = (String) metaKeys.next();
if (key.toLowerCase().startsWith(METADATA_HEADER_PREFIX)) {
String headerName = key.substring(METADATA_HEADER_PREFIX.length());
Iterator metaValues = metadata.getValues(key).iterator();
while (metaValues.hasNext()) {
String headerValue = (String) metaValues.next();
response.addHeader(headerName, headerValue);
}
}
}
if (component instanceof ContentProvider) {
processContent(outStream, (ContentProvider) component, checkAccess, callData, request, response,
includeBody);
} else if (component instanceof Renderable) {
processRenderable(outStream, (Renderable) component, checkAccess, callData, request, response, includeBody);
} else {
log.error("handleRequest() - unknown type '" + component + "'!");
}
if (log.isDebugEnabled())
log.debug("handleRequest() - ... request handled.");
}
private void handleException(Throwable throwable, ComponentPath componentPath, HTTPCallData callData,
HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.debug("handleException() - searching error-page for Throwable '" + throwable.getClass().getName()
+ "' and component-path '" + componentPath + "' ...");
try {
ErrorHandler.ErrorMapping errorMapping = errorHandler.getErrorPage(throwable, componentPath);
sendPageOrError(errorMapping.errorPagePath, componentPath, errorMapping.errorCode, callData, request,
response, throwable);
} catch (ContelligentException e) {
log.warn("handleException() - Exception while fetching path to error-page for path '" + componentPath
+ "' and exception-class '" + throwable.getClass().getName() + "': " + e);
throw new ServletException("Could not get error-page for path '" + componentPath
+ "' and exception-class '" + throwable.getClass().getName() + "'!", e);
}
}
/**
* Either prints the contents of the component found in path
* error-path+errorCode or if such a component does not exist the given
* <tt>errorCode</tt> is simply passed to
* {@link HttpServletResponse#sendError} using the
* {@link Throwable#getMessage message} of the given <tt>throwable</tt> as
* descriptive message. If the response has already been commited a
* <code>ServletException</code> is thrown which gets wrapped around the
* given <tt>throwable</tt>.
*/
private void sendPageOrError(ComponentPath errorPagePath, ComponentPath componentPath, int errorCode,
HTTPCallData callData, HttpServletRequest request, HttpServletResponse response, Throwable throwable)
throws ServletException, IOException {
StringBuffer sb = new StringBuffer("Render stack at time of failure:\n");
Vector renderStack = callData.cloneRenderStack();
Iterator it = renderStack.iterator();
while (it.hasNext()) {
ComponentPath stackElement = (ComponentPath) it.next();
sb.append(stackElement.toString());
sb.append("\n");
}
log.info(sb.toString());
if (response.isCommitted()) {
log
.warn("sendPageOrError() - response was already committed, can not send error -> throwing ServletException!");
throw new ServletException(
"Could not display error-page nor send error-code because response was already commited!",
throwable);
}
ComponentManager manager = callData.getActualManager();
try {
// XXX: maybe switch user to SystemUser to avoid (maybe another) SecurityException ?
if (manager.componentExists(errorPagePath)) {
Component errorPage = manager.getComponent(errorPagePath, callData);
String channel = getChannel(callData.getCategoryMap());
String contentType = getContentType(channel);
response.setContentType(contentType);
response.setStatus(errorCode);
// write information of problematic page into request
callData.setRequestAttribute("errorCode", (new Integer(errorCode)).toString());
callData.setRequestAttribute("originalRequestPath", componentPath.toPath());
// dispatch again but ignore access restrictions:
handleRequest(response.getOutputStream(), errorPage, false, callData, request, response, true, ".html");
return;
} else {
log.warn("sendPageOrError() - error-page '" + errorPagePath + "' does not exist! Sending error-code "
+ errorCode + " instead ...");
}
} catch (Exception e) {
log.warn("sendPageOrError() - Exception while trying to serve error-page '" + errorPagePath
+ "' (ignored), sending error-code " + errorCode + " instead ... (exception was: ", e);
}
response.sendError(errorCode, throwable.getMessage());
}
private String getEncoding(String channel) {
if ("XML".equalsIgnoreCase(channel)) {
return "UTF-8";
} else {
return defaultEncoding;
}
}
private String getContentType(String channel) {
StringBuffer s = new StringBuffer();
if (channel.indexOf("app_") == 0) {
s.append("application/");
try {
channel = channel.substring(4);
} catch (Exception e) {
channel = "";
}
} else {
s.append("text/");
}
if ("wml".equals(channel)) {
s.append("vnd.wap.wml");
} else {
s.append(channel);
}
s.append("; charset=").append(getEncoding(channel));
if (log.isDebugEnabled())
log.debug("getContentType() - returning '" + s + "' ...");
return s.toString();
}
private String getChannel(Map categoryMap) {
return ((categoryMap.containsKey("channel")) ? (String) categoryMap.get("channel") : "html");
}
/**
* Describe <code>setupEnvironment</code> method here.
*
* @param request
* a <code>HttpServletRequest</code> value
* @param response
* a <code>HttpServletResponse</code> value
* @param context
* a <code>ServletContext</code> value
* @return a <code>CallData</code> value
* @exception SessionCreationException
* if an error occurs
* @exception CategoryException
* if an error occurs
*/
HTTPCallDataImpl setupEnvironment(HttpServletRequest request, HttpServletResponse response, ServletContext context,
String requestBaseURL, String requestHttpAuthBaseURL) throws SessionCreationException, CategoryException {
final boolean debugEnabled = log.isDebugEnabled();
boolean newSession = false;
boolean configurePreview = false;
String requestType = request.getContentType();
String userAgent = request.getHeader("User-Agent");
if (userAgent == null) {
userAgent = "unspecified";
}
if (debugEnabled) {
log.debug("setupEnvironment() - remote user is: " + userAgent);
}
boolean flashRequest = false;
boolean searchEngineSpider = isSearchEngineSpider(userAgent);
if (userAgent != null && userAgent.startsWith("Shockwave Flash")) {
flashRequest = true;
}
if (requestType != null && requestType.equals("text/xml")) {
if (debugEnabled) {
log.debug("setupEnvironment() - request type is: " + requestType);
}
flashRequest = true;
}
HttpSession httpSession = request.getSession(!searchEngineSpider);
ContelligentSession contellSession = null;
if (!searchEngineSpider) {
contellSession = (ContelligentSession) httpSession.getAttribute(CONTELLIGENT_SESSION_KEY);
}
if (contellSession == null) {
log.debug("setupEnvironment() - ... contelligent-session not set -> generating new one ...");
// check for auto-login
/*
* int sCount = contelligent.getNumberOfSessions();
*
* if(sCount >= contelligent.getLicense().getMaxSessions() &&
* contelligent.getLicense().getMaxSessions() > -1 ) { throw new
* MaxSessionsExceededException("max number of sessions exceeded.
* license restricts the number of sessions to:
* "+contelligent.getLicense().getMaxSessions()); }
*/
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(de.finix.contelligent.core.security.AutologinCookie.NAME)) {
log
.debug("setupEnvironment() - ... found cookie for autologin, trying to generate session from it ...");
contellSession = (ContelligentSession) contelligent.beginSession(cookie);
}
}
}
if (contellSession == null) {
ComponentManager manager = contelligent.getRootComponentManager();
User initialUser = ContelligentSecurityManager.getInstance().getGuest();
// FIXME: use authentication data for initial user
contellSession = (ContelligentSession) contelligent.beginSession(initialUser, manager);
newSession = true;
}
// store contelligent-session in http-session:
if (!searchEngineSpider) {
httpSession.setAttribute(CONTELLIGENT_SESSION_KEY, contellSession);
}
// to work around a strange bug in tomcat, remove the call data from
// the request
// the callData is not null only after invalidating the HTTP session
// if (request.getAttribute(HTTPCallData.SESSION_KEY) != null) {
// request.removeAttribute(HTTPCallData.SESSION_KEY);
// }
if (debugEnabled)
log.debug("setupEnvironment() - ... created session: " + contellSession);
}
// honor contelligent user principal in request
if (request.getUserPrincipal() != null && request.getUserPrincipal() instanceof User) {
contellSession.setNamedUser((User) request.getUserPrincipal());
}
String cSessionId = null;
int previewMode = ContelligentSession.PREVIEW;
if (!flashRequest) {
cSessionId = request.getParameter(Contelligent.CONTELLIGENT_SESSION_COOKIE);
String mode = request.getParameter(Contelligent.PREVIEW_MODE);
if (mode != null && mode.equals(Contelligent.EDIT)) {
previewMode = ContelligentSession.EDIT;
}
}
// cSessionId overloads the userPrincipal from
// request.getUserPrincipal as set above
if (cSessionId != null) {
Session s = contelligent.getSession(cSessionId);
if (s != null) {
contellSession.addObserverSession((ContelligentSession) s);
contellSession.setComponentManager(s.getComponentManager());
contellSession.setPreviewMode(previewMode);
contellSession.setNamedUser(s.getUser());
configurePreview = true;
log.debug("Added session " + cSessionId + " as observer to " + contellSession.getId());
}
}
ThreadedMem.setActualManager(contellSession.getComponentManager());
if (debugEnabled)
log.debug("setupEnvironment() - set actual manager " + contellSession.getComponentManager());
HTTPCallDataImpl callData = null;// (HTTPCallData)request.getAttribute(HTTPCallData.SESSION_KEY);
boolean currentHttpAuth = false;
boolean currentProtocolHttps = false;
if (requestBaseURL == null) {
requestBaseURL = request.getContextPath(); // = webApp
requestHttpAuthBaseURL = request.getContextPath(); // = webApp
String requestServletMapping = request.getServletPath();
if (!(request.getAttribute("javax.servlet.include.servlet_path") == null)) {
requestServletMapping = (String) request.getAttribute("javax.servlet.include.servlet_path");
}
// if ( requestServletMapping != null) {
// requestBaseURL = requestBaseURL + requestServletMapping;
// } else if(contelligent.getServletMapping() != null) {
String tmp = contelligent.getServletMapping();
if (!tmp.startsWith("/"))
tmp = "/" + tmp;
requestBaseURL = requestBaseURL + tmp;
tmp = contelligent.getSSOServletMapping();
if (!tmp.startsWith("/"))
tmp = "/" + tmp;
requestHttpAuthBaseURL = requestHttpAuthBaseURL + tmp;
// if SSO context
if (requestServletMapping.substring(0).equals(tmp)) {
currentHttpAuth = true;
} else {
currentHttpAuth = false;
}
if (request.isSecure() || request.getScheme().toLowerCase().equals("https")) {
currentProtocolHttps = true;
} else {
currentProtocolHttps = false;
}
// }
}
if (debugEnabled) {
log.debug("setupEnvironment() - ... content type is: " + requestType);
log.debug("setupEnvironment() - ... remote user is: " + userAgent);
}
if (requestType != null && requestType.startsWith("multipart")) {
callData = new HTTPCallDataImpl(contellSession, requestBaseURL, requestHttpAuthBaseURL, contelligent
.getHTTPPort(), contelligent.getHTTPSPort(), currentProtocolHttps, currentHttpAuth, request,
response, context, true);
} else if (flashRequest) {
String requestBody = null;
StringBuffer buffer = new StringBuffer(1024);
try {
// XXX: we use UTF-8 here because we assume body contains XML in
// this encoding
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
requestBody = buffer.toString();
if (debugEnabled) {
log.debug("setupEnvironment() - ... request body is: " + requestBody);
}
} catch (IOException e) {
requestBody = null;
}
callData = new HTTPCallDataImpl(contellSession, requestBaseURL, requestHttpAuthBaseURL, contelligent
.getHTTPPort(), contelligent.getHTTPSPort(), currentProtocolHttps, currentHttpAuth, request,
response, context, false);
// XXX HACK for Flash XML handling START
if (BuildInfo.CHECK_FLASH_XML_REQUEST && requestBody.indexOf("<http-request") != -1) {
int realBodyStart = requestBody.indexOf("<request");
int realBodyEnd = requestBody.indexOf("</request>") + 10;
String realBody = requestBody.substring(realBodyStart, realBodyEnd);
if (debugEnabled) {
log.debug("setupEnvironment() - real-body=" + realBody);
}
try {
InputStream inStream = new ByteArrayInputStream(requestBody.getBytes());
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(inStream);
Element parameters = (Element) document.getElementsByTagName("parameters").item(0);
NodeList params = parameters.getElementsByTagName("param");
for (int i = 0; i < params.getLength(); i++) {
Element param = (Element) params.item(i);
String name = param.getAttributes().getNamedItem("name").getNodeValue();
NodeList contentList = param.getChildNodes();
StringBuffer valBuf = new StringBuffer();
for (int a = 0; a < contentList.getLength(); a++) {
valBuf.append(contentList.item(a).getNodeValue());
}
String value = valBuf.toString().trim();
if (debugEnabled) {
log.debug("setupEnvironment() - defining parameter name=" + name + ", value=" + value);
}
callData.getParameters().put(name, new String[] { value });
}
} catch (Throwable t) {
log.error("setupEnvironment() - Exception while parsing request-body [ignored]: ", t);
}
callData.setRequestBody(realBody);
// XXX HACK for Flash XML handling END
} else {
callData.setRequestBody(requestBody);
}
} else {
callData = new HTTPCallDataImpl(contellSession, requestBaseURL, requestHttpAuthBaseURL, contelligent
.getHTTPPort(), contelligent.getHTTPSPort(), currentProtocolHttps, currentHttpAuth, request,
response, getServletConfig().getServletContext(), true);
}
// the contelligent-taglib and jsp's access the call-data through the
// http-request:
// request.setAttribute(HTTPCallData.SESSION_KEY, callData);
log.debug("setupEnvironment() - created calldata " + callData);
// }
ArrayList locales = new ArrayList();
Enumeration enumeration = request.getLocales();
while (enumeration.hasMoreElements()) {
Object locale = enumeration.nextElement();
if (locale != null) { // this is for orion
locales.add(locale);
}
}
callData.setLocales(locales);
// create the category-map if not already done:
// if (contellSession.getCategoryMap()==null ||
// contellSession.getCategoryMap().size()==0) {
// Map categoryMap =
// contelligent.getCategoryManager().getCategoryMap(callData);
// contellSession.setCategoryMap(categoryMap);
// log.debug("setupEnvironment() - created category map
// "+contellSession.getCategoryMap());
// }
if (newSession) {
Map categoryMap = contelligent.getCategoryManager().getCategoryMap(callData, true);
contellSession.setCategoryMap(categoryMap);
}
Map categoryMap = new HashMap(contellSession.getCategoryMap());
categoryMap.putAll(contelligent.getCategoryManager().getCategoryMap(callData, false));
// Actually check the clientsession flag instead of relying on the
// user agent.
if (contellSession.isClientSession()) {
if (contelligent.isSecureClient() && !(request.isSecure() || request.getScheme().toLowerCase().equals("https"))) {
throw new ContelligentRuntimeException(ContelligentExceptionID.security_client_requireSecure);
}
categoryMap.putAll(contelligent.getCategoryManager().getCategoryMap(callData.getParameters()));
}
callData.setCategoryMap(categoryMap);
callData.setSearchEngineSpider(searchEngineSpider);
ThreadedMem.setCallData(callData);
if (debugEnabled)
log.debug("setupEnvironment() - ... callData '" + callData + "' bound to threaded-mem.");
if (configurePreview) {
String config = request.getParameter(Contelligent.PREVIEW_CONFIG);
String date = request.getParameter(Contelligent.PREVIEW_DATE);
if (config != null) {
try {
contelligent.configurePreview(new ComponentPath(config), date, contellSession, callData);
} catch (ContelligentSecurityException cse) {
throw new SessionCreationException(cse.getMessage(), cse);
} catch (Exception e) {
// if we cant configure the requested preview (possibly for
// security reasons), dont preview at all.
throw new SessionCreationException(e);
}
}
}
return callData;
}
private boolean isSearchEngineSpider(String userAgent) {
userAgent = userAgent.toLowerCase();
for (int i = 0; i < searchEngineBots.length; i++) {
if (userAgent.indexOf(searchEngineBots[i]) != -1) {
return true;
}
}
return false;
}
/**
* Returns the date the given component was modified in a per second
* resolution to be used as Last-Modified header or -1 if the component is
* either full dynamic or does not specifiy a modification date.
*/
private long getLastModified(Component component, HTTPCallData callData, HttpServletRequest request) {
// Since currently we have no way of finding the most recent component
// of *all* components involved in the request, we leave this at the
// current timestamp for everything. The only exception to this are
// binaries, for which the content object is asked for a timestamp.
// (Cant just use the ComponentContext, since sometimes even binaries
// are dynamic)
try {
if (component instanceof ContentProvider) {
Content content = ((ContentProvider) component).getContent();
if (content instanceof BinaryContent) {
return content.lastModified(callData);
}
}
} catch (Exception e) {
log.error("getLastModified() - Exception: ", e);
}
return TimeService.getInstance().currentTimeMillis();
}
/**
* Returns a map containing (String,String[]) entries mapping parameters
* names to their corresponding value(s). If a parameter has only one value
* the string array has length 1.
*
* @param request
* a <code>HttpServletRequest</code> value
* @return a non-null <code>Map</code> value
* @see ServletRequest#getParameterValues
*/
protected Map getRequestMap(HttpServletRequest request) {
Enumeration enumeration = request.getParameterNames();
Map requestMap = new HashMap();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String[] values = request.getParameterValues(key);
requestMap.put(key, values);
}
return requestMap;
}
private void processRenderable(OutputStream outStream, Renderable renderable, boolean checkAccess,
HTTPCallData callData, HttpServletRequest request, HttpServletResponse response, boolean includeBody)
throws Exception {
String channel = getChannel(callData.getCategoryMap());
String encoding = getEncoding(channel);
response.setContentType(getContentType(channel));
if (includeBody) {
Renderer renderer = renderable.getRenderer();
Writer writer = new BufferedWriter(new OutputStreamWriter(outStream, encoding));
ComponentPath realPath = renderable.getComponentContext().getPath();
callData.pushRenderStack(realPath);
// FIXME: get encoding from renderer
renderer.render(writer, callData.getParameters(), callData);
callData.popRenderStack();
writer.flush();
}
}
void processContent(OutputStream outStream, ContentProvider component, boolean checkAccess, HTTPCallData callData,
HttpServletRequest request, HttpServletResponse response, boolean includeBody) throws Exception {
Content content = component.getContent();
if (content instanceof BinaryContent) {
BinaryContent binaryContent = (BinaryContent) content;
response.setContentType(binaryContent.getContentType(callData));
// response.setContentLength(content.length(callData));
if (includeBody) {
// XXX: we directly use the output-stream of the response here
// because of memory-usage!
// this means we could not have a binary ContelligentProvider
// which sets cookies
// or make a proper error handling. (2002/12/10, rs)
// if
// (binaryContent.length(callData)<=contelligent.getSmallBinaryThreshold())
// {
// binaryContent.streamBinary(outStream, callData);
// } else {
binaryContent.streamBinary(response.getOutputStream(), callData);
if (log.isDebugEnabled()) {
log.debug("... successfully streamed binary-content of '" + content + "'.");
}
}
} else {
if (component instanceof Renderable) {
processRenderable(outStream, (Renderable) component, checkAccess, callData, request, response,
includeBody);
} else {
// FIXME: set content-type (but Content interface doesn't have
// one ...!)
// response.setContentType(content.getContentType(callData));
String channel = getChannel(callData.getCategoryMap());
String encoding = getEncoding(channel);
response.setContentType(getContentType(channel));
// response.setHeader("Content-Encoding", encoding);
// response.setContentLength(content.length(callData));
if (includeBody) {
String contentAsString = new DefaultRenderer(content).getContentAsString(callData);
Writer writer = new BufferedWriter(new OutputStreamWriter(outStream, encoding));
writer.write(contentAsString);
writer.flush();
}
}
}
}
}
|