0001: /*
0002: * This file is part of PFIXCORE.
0003: *
0004: * PFIXCORE is free software; you can redistribute it and/or modify
0005: * it under the terms of the GNU Lesser General Public License as published by
0006: * the Free Software Foundation; either version 2 of the License, or
0007: * (at your option) any later version.
0008: *
0009: * PFIXCORE is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0012: * GNU Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public License
0015: * along with PFIXCORE; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: *
0018: */
0019: package de.schlund.pfixxml;
0020:
0021: import java.io.ByteArrayOutputStream;
0022: import java.io.File;
0023: import java.io.IOException;
0024: import java.io.OutputStream;
0025: import java.io.OutputStreamWriter;
0026: import java.net.SocketException;
0027: import java.util.Enumeration;
0028: import java.util.HashMap;
0029: import java.util.Iterator;
0030: import java.util.Map;
0031: import java.util.Properties;
0032: import java.util.TreeMap;
0033:
0034: import javax.servlet.ServletConfig;
0035: import javax.servlet.ServletException;
0036: import javax.servlet.http.Cookie;
0037: import javax.servlet.http.HttpServletRequest;
0038: import javax.servlet.http.HttpServletResponse;
0039: import javax.servlet.http.HttpSession;
0040: import javax.xml.transform.Templates;
0041: import javax.xml.transform.TransformerConfigurationException;
0042: import javax.xml.transform.TransformerException;
0043: import javax.xml.transform.TransformerFactory;
0044: import javax.xml.transform.TransformerFactoryConfigurationError;
0045: import javax.xml.transform.dom.DOMSource;
0046: import javax.xml.transform.stream.StreamResult;
0047:
0048: import org.apache.log4j.Logger;
0049: import org.w3c.dom.Document;
0050:
0051: import de.schlund.pfixcore.exception.PustefixApplicationException;
0052: import de.schlund.pfixcore.exception.PustefixCoreException;
0053: import de.schlund.pfixcore.workflow.NavigationFactory;
0054: import de.schlund.pfixcore.workflow.NavigationInitializationException;
0055: import de.schlund.pfixxml.config.AbstractXMLServletConfig;
0056: import de.schlund.pfixxml.config.ServletManagerConfig;
0057: import de.schlund.pfixxml.perflogging.AdditionalTrailInfo;
0058: import de.schlund.pfixxml.perflogging.AdditionalTrailInfoFactory;
0059: import de.schlund.pfixxml.perflogging.PerfEvent;
0060: import de.schlund.pfixxml.perflogging.PerfEventType;
0061: import de.schlund.pfixxml.resources.FileResource;
0062: import de.schlund.pfixxml.resources.ResourceUtil;
0063: import de.schlund.pfixxml.serverutil.SessionAdmin;
0064: import de.schlund.pfixxml.serverutil.SessionHelper;
0065: import de.schlund.pfixxml.targets.PageInfo;
0066: import de.schlund.pfixxml.targets.PageInfoFactory;
0067: import de.schlund.pfixxml.targets.PageTargetTree;
0068: import de.schlund.pfixxml.targets.Target;
0069: import de.schlund.pfixxml.targets.TargetGenerationException;
0070: import de.schlund.pfixxml.targets.TargetGenerator;
0071: import de.schlund.pfixxml.targets.TargetGeneratorFactory;
0072: import de.schlund.pfixxml.testrecording.TestRecording;
0073: import de.schlund.pfixxml.testrecording.TrailLogger;
0074: import de.schlund.pfixxml.util.MD5Utils;
0075: import de.schlund.pfixxml.util.CacheValueLRU;
0076: import de.schlund.pfixxml.util.Xml;
0077: import de.schlund.pfixxml.util.Xslt;
0078:
0079: /**
0080: * This class is at the top of the XML/XSLT System.
0081: * It serves as an abstract parent class for all servlets
0082: * needing access to the XML/XSL cache system povided by
0083: * de.schlund.pfixxml.TargetGenerator.<p><br>
0084: * Servlets inheriting from this class need to implement
0085: * getDom(HttpServletRequest req, HttpServletResponse res)
0086: * which returns a SPDocument. <br>
0087: */
0088: public abstract class AbstractXMLServlet extends ServletManager {
0089:
0090: //~ Instance/static variables ..................................................................
0091: // how to write xml to the result stream
0092: private static final int RENDER_NORMAL = 0;
0093: private static final int RENDER_EXTERNAL = 1;
0094: private static final int RENDER_FONTIFY = 2;
0095: private static final int RENDER_XMLONLY = 3;
0096:
0097: private static final int DEF_MAX_STORED_DOMS = 5;
0098:
0099: public static final String DEF_PROP_TMPDIR = "java.io.tmpdir";
0100: private static final String FONTIFY_SSHEET = "core/xsl/xmlfontify.xsl";
0101: public static final String SESS_LANG = "__SELECTED_LANGUAGE__";
0102: public static final String PARAM_XMLONLY = "__xmlonly";
0103: public static final String PARAM_XMLONLY_FONTIFY = "1"; // -> RENDER_FONFIFY
0104: public static final String PARAM_XMLONLY_XMLONLY = "2"; // -> RENDER_XMLONLY
0105: public static final String PARAM_ANCHOR = "__anchor";
0106: private static final String PARAM_EDITMODE = "__editmode";
0107: private static final String PARAM_LANG = "__language";
0108: private static final String PARAM_FRAME = "__frame";
0109: private static final String PARAM_REUSE = "__reuse"; // internally used
0110:
0111: private static final String XSLPARAM_LANG = "lang";
0112: private static final String XSLPARAM_DEREFKEY = "__derefkey";
0113: private static final String XSLPARAM_SESSID = "__sessid";
0114: private static final String XSLPARAM_URI = "__uri";
0115: private static final String XSLPARAM_SERVP = "__servletpath";
0116: private static final String XSLPARAM_CONTEXTPATH = "__contextpath";
0117: private static final String XSLPARAM_REMOTE_ADDR = "__remote_addr";
0118: private static final String XSLPARAM_SERVER_NAME = "__server_name";
0119: private static final String XSLPARAM_REQUEST_SCHEME = "__request_scheme";
0120: private static final String XSLPARAM_QUERYSTRING = "__querystring";
0121: private static final String XSLPARAM_FRAME = "__frame";
0122: private static final String XSLPARAM_REUSE = "__reusestamp";
0123: private static final String VALUE_NONE = "__NONE__";
0124: private static final String SUFFIX_SAVEDDOM = "_SAVED_DOM";
0125: private static final String ATTR_SHOWXMLDOC = "__ATTR_SHOWXMLDOC__";
0126: protected static final String PROP_ADD_TRAIL_INFO = "xmlserver.additionalinfo.implementation";
0127: protected static final String PROP_NAME = "xmlserver.servlet.name";
0128: protected static final String PROP_NOEDIT = "xmlserver.noeditmodeallowed";
0129: protected static final String PROP_RENDER_EXT = "xmlserver.output.externalrenderer";
0130: protected static final String PROP_CLEANER_TO = "sessioncleaner.timeout";
0131: protected static final String PROP_MAX_STORED_DOMS = "xmlserver.maxStoredDoms";
0132: protected static final String PROP_SKIP_GETMODTIME_MU = "targetgenerator.skip_getmodtimemaybeupdate";
0133: protected static final String PROP_PROHIBITDEBUG = "xmlserver.prohibitdebug";
0134: protected static final String PROP_PROHIBITINFO = "xmlserver.prohibitinfo";
0135: private static final String PARAM_DEPENDFILE = "servlet.dependfile";
0136:
0137: public static final String PREPROCTIME = "__PREPROCTIME__";
0138: public static final String GETDOMTIME = "__GETDOMTIME__";
0139: public static final String TRAFOTIME = "__TRAFOTIME__";
0140:
0141: public final static String SESS_CLEANUP_FLAG_STAGE1 = "__pfx_session_cleanup_stage1";
0142: public final static String SESS_CLEANUP_FLAG_STAGE2 = "__pfx_session_cleanup_stage2";
0143:
0144: private int maxStoredDoms = DEF_MAX_STORED_DOMS;
0145:
0146: /**
0147: * Holds the TargetGenerator which is the XML/XSL Cache for this
0148: * class of servlets.
0149: */
0150: protected TargetGenerator generator = null;
0151:
0152: /**
0153: * The unique Name of this servlet, needed to create a Namespace in
0154: * the HttpSession Session.
0155: */
0156: protected String servletname = null;
0157:
0158: protected ServletManagerConfig getServletManagerConfig() {
0159: return this .getAbstractXMLServletConfig();
0160: }
0161:
0162: protected abstract AbstractXMLServletConfig getAbstractXMLServletConfig();
0163:
0164: /**
0165: * The configuration file for the TargetGeneratorFacory.
0166: */
0167: private FileResource targetconf = null;
0168: private boolean render_external = false;
0169: private boolean editmodeAllowed = false;
0170: private boolean skip_getmodtimemaybeupdate = false;
0171: private int scleanertimeout = 300;
0172:
0173: private final static Logger LOGGER_TRAIL = Logger
0174: .getLogger("LOGGER_TRAIL");
0175: private final static Logger LOGGER = Logger
0176: .getLogger(AbstractXMLServlet.class);
0177:
0178: private AdditionalTrailInfo addtrailinfo = null;
0179:
0180: //~ Methods ....................................................................................
0181: /**
0182: * Init method of all servlets inheriting from AbstractXMLServlets.
0183: * It calls super.init(Config) as a first step.
0184: * @param ContextXMLServletConfig config. Passed in from the servlet container.
0185: * @return void
0186: * @exception ServletException thrown when the initialisation goes havoc somehow
0187: */
0188: public void init(ServletConfig config) throws ServletException {
0189: super .init(config);
0190: if (LOGGER.isDebugEnabled()) {
0191: LOGGER.debug("\n>>>> In init of AbstractXMLServlet <<<<");
0192: }
0193: initValues();
0194: if (LOGGER.isDebugEnabled()) {
0195: LOGGER.debug("End of init AbstractXMLServlet");
0196: }
0197: verifyDirExists(System.getProperty(DEF_PROP_TMPDIR));
0198: }
0199:
0200: private void initValues() throws ServletException {
0201: if (this .getAbstractXMLServletConfig().getDependFile() != null) {
0202: targetconf = ResourceUtil.getFileResourceFromDocroot(this
0203: .getAbstractXMLServletConfig().getDependFile());
0204: } else {
0205: targetconf = ResourceUtil.getFileResourceFromDocroot(this
0206: .getInitParameter(PARAM_DEPENDFILE));
0207: }
0208: servletname = this .getAbstractXMLServletConfig()
0209: .getServletName();
0210:
0211: try {
0212: generator = TargetGeneratorFactory.getInstance()
0213: .createGenerator(targetconf);
0214: } catch (Exception e) {
0215: LOGGER.error("Error: ", e);
0216: throw new ServletException("Couldn't get TargetGenerator",
0217: e);
0218: }
0219:
0220: editmodeAllowed = this .getAbstractXMLServletConfig()
0221: .isEditMode();
0222:
0223: String render_external_prop = this
0224: .getAbstractXMLServletConfig().getProperties()
0225: .getProperty(PROP_RENDER_EXT);
0226: render_external = ((render_external_prop != null) && render_external_prop
0227: .equals("true"));
0228:
0229: String skip_gmmu_prop = this .getAbstractXMLServletConfig()
0230: .getProperties().getProperty(PROP_SKIP_GETMODTIME_MU);
0231: skip_getmodtimemaybeupdate = ((skip_gmmu_prop != null) && skip_gmmu_prop
0232: .equals("true"));
0233:
0234: generator
0235: .setIsGetModTimeMaybeUpdateSkipped(skip_getmodtimemaybeupdate);
0236: String timeout;
0237: if ((timeout = this .getAbstractXMLServletConfig()
0238: .getProperties().getProperty(PROP_CLEANER_TO)) != null) {
0239: try {
0240: scleanertimeout = new Integer(timeout).intValue();
0241: } catch (NumberFormatException e) {
0242: throw new ServletException(e.getMessage());
0243: }
0244: }
0245:
0246: String strVal = getAbstractXMLServletConfig().getProperties()
0247: .getProperty(PROP_MAX_STORED_DOMS);
0248: if (strVal != null) {
0249: try {
0250: maxStoredDoms = Integer.parseInt(strVal);
0251: } catch (NumberFormatException e) {
0252: throw new ServletException(
0253: "Illegal property value for '"
0254: + PROP_MAX_STORED_DOMS + "': " + strVal,
0255: e);
0256: }
0257: }
0258:
0259: String addinfoprop = getAbstractXMLServletConfig()
0260: .getProperties().getProperty(PROP_ADD_TRAIL_INFO);
0261: addtrailinfo = AdditionalTrailInfoFactory.getInstance()
0262: .getAdditionalTrailInfo(addinfoprop);
0263:
0264: if (LOGGER.isInfoEnabled()) {
0265: StringBuffer sb = new StringBuffer(255);
0266: sb
0267: .append("\n")
0268: .append(
0269: "AbstractXMLServlet properties after initValues(): \n");
0270: sb.append(" targetconf = ").append(
0271: targetconf).append("\n");
0272: sb.append(" servletname = ").append(
0273: servletname).append("\n");
0274: sb.append(" editModeAllowed = ").append(
0275: editmodeAllowed).append("\n");
0276: sb.append(" maxStoredDoms = ").append(
0277: maxStoredDoms).append("\n");
0278: sb.append(" timeout = ").append(timeout)
0279: .append("\n");
0280: sb.append("skip_getmodtimemaybeupdate = ").append(
0281: skip_getmodtimemaybeupdate).append("\n");
0282: sb.append(" render_external = ").append(
0283: render_external).append("\n");
0284: LOGGER.info(sb.toString());
0285: }
0286: }
0287:
0288: protected boolean tryReloadProperties(PfixServletRequest preq)
0289: throws ServletException {
0290: if (super .tryReloadProperties(preq)) {
0291: initValues(); // this also does generator.tryReinit()
0292: return true;
0293: } else {
0294: try {
0295: // This is a fake. We also return true when only depend.xml change, but the properties not.
0296: // But we can only signal one type of "reload" event with the return value of this method,
0297: // so it's better to reload the properties one time too often.
0298: return generator.tryReinit();
0299: } catch (Exception e) {
0300: throw new ServletException(
0301: "When trying to reinit generator: " + e);
0302: }
0303: }
0304: }
0305:
0306: /**
0307: * A child of AbstractXMLServlet must implement this method.
0308: * It is here where the final Dom tree and parameters for
0309: * applying to the stylesheet are put into SPDocument.
0310: * @param HttpServletRequest req: the current request
0311: * @return SPDocument: The result Dom tree and parameters
0312: * @exception Exception Anything that can go wrong when constructing the resulting
0313: * SPDocument object
0314: */
0315: protected abstract SPDocument getDom(PfixServletRequest preq)
0316: throws PustefixApplicationException, PustefixCoreException;
0317:
0318: @SuppressWarnings("unchecked")
0319: private CacheValueLRU<String, SPDocument> getLRU(HttpSession session) {
0320: return (CacheValueLRU<String, SPDocument>) session
0321: .getAttribute(servletname + SUFFIX_SAVEDDOM);
0322: }
0323:
0324: /**
0325: * This is the method that is called for any servlet that inherits from ServletManager.
0326: * It calls getDom(req, res) to get the SPDocument doc.
0327: * This SPDocument is stored in the HttpSession so it can be reused if
0328: * the request parameter __reuse is set to a timestamp matching the timestamp of the saved SPDocument.
0329: * In other words, <b>if</b> the request parameter __reuse is
0330: * there and it is set to a matching timestamp, getDom(req,res)
0331: * will <b>not</b> be called, instead the saved Dom tree from the previous request
0332: * to this servlet will be used.
0333: *
0334: * Request parameters that are put into the gen_params Hash:
0335: * <pre>__frame</pre><br>
0336: * <pre>__uri</pre><br>
0337: * <pre>__sessid</pre><br>
0338: * <pre>__editmode</pre><br>
0339: * <pre>__reusestamp</pre><br>
0340: * <pre>lang</pre><br>
0341: * @param PfixServletRequest req
0342: * @param HttpServletResponse res
0343: * @exception Exception
0344: */
0345: protected void process(PfixServletRequest preq,
0346: HttpServletResponse res) throws Exception {
0347: Properties params = new Properties();
0348: HttpSession session = preq.getSession(false);
0349: CacheValueLRU<String, SPDocument> storeddoms = null;
0350: if (session != null) {
0351: storeddoms = getLRU(session);
0352: if (storeddoms == null) {
0353: storeddoms = new CacheValueLRU<String, SPDocument>(
0354: maxStoredDoms);
0355: session.setAttribute(servletname + SUFFIX_SAVEDDOM,
0356: storeddoms);
0357: }
0358: }
0359: SPDocument spdoc = doReuse(preq, storeddoms);
0360: boolean doreuse = false;
0361: if (spdoc != null) {
0362: doreuse = true;
0363: }
0364:
0365: // Check if session has been scheduled for invalidation
0366: if (!doreuse
0367: && session.getAttribute(SESS_CLEANUP_FLAG_STAGE2) != null) {
0368: HttpServletRequest req = preq.getRequest();
0369: String redirectUri = SessionHelper.getClearedURL(req
0370: .getScheme(), getServerName(req), req,
0371: getAbstractXMLServletConfig().getProperties());
0372: relocate(res, redirectUri);
0373: return;
0374: }
0375:
0376: RequestParam value;
0377: long currtime;
0378: long preproctime = -1;
0379: long getdomtime = -1;
0380:
0381: // Reset the additional trail info before processing the request
0382: addtrailinfo.reset();
0383:
0384: // We look for the request parameter __frame and __reuse.
0385: // These are needed for possible frame handling by the stylesheet;
0386: // they will be stored in the params properties and will be applied as stylesheet
0387: // parameters in apply Stylesheet
0388: // if __reuse is set, we will try to reuse a stored DomTree, if __reuse is
0389: // not set, we store the DomTree from getDom in the Session as servletname + _saved_dom
0390: if ((value = preq.getRequestParam(PARAM_FRAME)) != null)
0391: if (value.getValue() != null) {
0392: params.put(XSLPARAM_FRAME, value.getValue());
0393: }
0394: params.put(XSLPARAM_URI, preq.getRequestURI());
0395: params.put(XSLPARAM_SERVP, preq.getContextPath()
0396: + preq.getServletPath());
0397: params.put(XSLPARAM_CONTEXTPATH, preq.getContextPath());
0398: if (preq.getRemoteAddr() != null)
0399: params.put(XSLPARAM_REMOTE_ADDR, preq.getRemoteAddr());
0400: if (preq.getServerName() != null)
0401: params.put(XSLPARAM_SERVER_NAME, preq.getServerName());
0402: if (preq.getScheme() != null)
0403: params.put(XSLPARAM_REQUEST_SCHEME, preq.getScheme());
0404: if (preq.getQueryString() != null)
0405: params.put(XSLPARAM_QUERYSTRING, preq.getQueryString());
0406:
0407: params.put(XSLPARAM_DEREFKEY, this
0408: .getAbstractXMLServletConfig().getProperties()
0409: .getProperty(DerefServlet.PROP_DEREFKEY));
0410:
0411: if (session != null) {
0412: params.put(XSLPARAM_SESSID, session
0413: .getAttribute(SessionHelper.SESSION_ID_URL));
0414: if (doreuse) {
0415: synchronized (session) {
0416: // Make sure redirect is only done once
0417: // See also: SSL redirect implementation in Context
0418: spdoc.resetRedirectURL();
0419: }
0420: }
0421:
0422: if ((value = preq.getRequestParam(PARAM_LANG)) != null) {
0423: if (value.getValue() != null) {
0424: session.setAttribute(SESS_LANG, value.getValue());
0425: }
0426: }
0427: // Now look for the parameter __editmode, and store it in the
0428: // session if it's there. Get the parameter from the session, and hand it over to the
0429: // Stylesheet params.
0430: if ((value = preq.getRequestParam(PARAM_EDITMODE)) != null) {
0431: if (value.getValue() != null) {
0432: session.setAttribute(PARAM_EDITMODE, value
0433: .getValue());
0434: }
0435: }
0436: if (session.getAttribute(PARAM_EDITMODE) != null) {
0437: // first we check if the properties prohibit editmode
0438: if (editmodeAllowed) {
0439: params.put(PARAM_EDITMODE, session
0440: .getAttribute(PARAM_EDITMODE));
0441: }
0442: }
0443: }
0444:
0445: // Now we will store the time needed from the creation of the request up to now
0446: preproctime = System.currentTimeMillis()
0447: - preq.getCreationTimeStamp();
0448: preq.getRequest().setAttribute(PREPROCTIME, preproctime);
0449:
0450: if (spdoc == null) {
0451:
0452: // Performace tracking
0453: PerfEvent pe = new PerfEvent(PerfEventType.XMLSERVER_GETDOM);
0454: pe.start();
0455: currtime = System.currentTimeMillis();
0456:
0457: // Now get the document
0458: try {
0459: spdoc = getDom(preq);
0460: } catch (PustefixApplicationException e) {
0461: // get original Exception thrown by Application
0462: // and rethrow it so that it can be catched by the
0463: // exception processor
0464: if (e.getCause() != null
0465: && e.getCause() instanceof Exception) {
0466: throw (Exception) e.getCause();
0467: } else {
0468: throw e;
0469: }
0470: }
0471:
0472: //Performance tracking
0473: pe.setIdentfier(spdoc.getPagename());
0474: pe.save();
0475:
0476: TrailLogger.log(preq, spdoc, session);
0477: RequestParam[] anchors = preq
0478: .getAllRequestParams(PARAM_ANCHOR);
0479: Map<String, String> anchormap;
0480: if (anchors != null && anchors.length > 0) {
0481: anchormap = createAnchorMap(anchors);
0482: spdoc.storeFrameAnchors(anchormap);
0483: }
0484: if (spdoc.getDocument() == null) {
0485: // thats a request to an unkown page!
0486: // do nothing, cause we want a 404 and no NPExpection
0487: if (LOGGER.isDebugEnabled()) {
0488: LOGGER
0489: .debug("Having a null-document in the SPDoc. Unkown page? Returning 404...");
0490: }
0491: }
0492:
0493: // this will remain at -1 when we don't have to enter the businesslogic codepath
0494: // (whenever there is a stored spdoc already)
0495: getdomtime = System.currentTimeMillis() - currtime;
0496: preq.getRequest().setAttribute(GETDOMTIME, getdomtime);
0497: }
0498: params.put(XSLPARAM_REUSE, "" + spdoc.getTimestamp());
0499: if (session != null && session.getAttribute(SESS_LANG) != null) {
0500: params.put(XSLPARAM_LANG, session.getAttribute(SESS_LANG));
0501: }
0502:
0503: handleDocument(preq, res, spdoc, params, doreuse);
0504:
0505: if (session != null
0506: && (getRendering(preq) != AbstractXMLServlet.RENDER_FONTIFY)
0507: && !doreuse) {
0508: // This will store just the last dom, but only when editmode is allowed (so this normally doesn't apply to production mode)
0509: // This is a seperate place from the SessionCleaner as we don't want to interfere with this, nor do we want to use
0510: // the whole queue of possible stored SPDocs only for the viewing of the DOM during development.
0511: if (editmodeAllowed) {
0512: session.setAttribute(ATTR_SHOWXMLDOC, spdoc);
0513: }
0514:
0515: // if (!spdoc.getNostore() || spdoc.isRedirect()) {
0516: if (spdoc.isRedirect()) {
0517: LOGGER.info("**** Storing for redirection! ****");
0518: // } else {
0519: // LOGGER.info("**** Storing because SPDoc wants us to. ****");
0520: // }
0521: SessionCleaner.getInstance().storeSPDocument(spdoc,
0522: storeddoms, scleanertimeout);
0523: } else {
0524: LOGGER
0525: .info("**** Not storing because no redirect... ****");
0526: }
0527: }
0528:
0529: if (session != null
0530: && !doreuse
0531: && (session.getAttribute(SESS_CLEANUP_FLAG_STAGE1) != null && session
0532: .getAttribute(SESS_CLEANUP_FLAG_STAGE2) == null)) {
0533: if (/* !spdoc.getNostore() || */spdoc.isRedirect()) {
0534: // Delay invalidation
0535:
0536: // This is obviously not thread-safe. However, no problems should
0537: // arise if the session is invalidated twice.
0538: session.setAttribute(SESS_CLEANUP_FLAG_STAGE2, true);
0539:
0540: SessionCleaner.getInstance().invalidateSession(session,
0541: scleanertimeout);
0542: } else {
0543: // Invalidate immediately
0544: if (session.getAttribute(SESS_CLEANUP_FLAG_STAGE1) != null
0545: && session
0546: .getAttribute(SESS_CLEANUP_FLAG_STAGE2) == null) {
0547: session.invalidate();
0548: }
0549: }
0550: }
0551: }
0552:
0553: protected void handleDocument(PfixServletRequest preq,
0554: HttpServletResponse res, SPDocument spdoc,
0555: Properties params, boolean doreuse)
0556: throws PustefixCoreException {
0557: long currtime = System.currentTimeMillis();
0558:
0559: // Check the document for supplied headers...
0560: HashMap<String, String> headers = spdoc.getResponseHeader();
0561: if (headers.size() != 0) {
0562: for (Iterator<String> i = headers.keySet().iterator(); i
0563: .hasNext();) {
0564: String key = i.next();
0565: String val = headers.get(key);
0566: if (LOGGER.isDebugEnabled()) {
0567: LOGGER.debug("*** Setting custom supplied header: "
0568: + key + " -> " + val);
0569: }
0570: res.setHeader(key, val);
0571: }
0572: } else {
0573: // set some default values to force generating new requests every time...
0574: res.setHeader("Expires", "Mon, 05 Jul 1970 05:07:00 GMT");
0575: res.setHeader("Cache-Control", "private");
0576: }
0577: // Check if a content type was supplied
0578: String ctype;
0579: if ((ctype = spdoc.getResponseContentType()) != null) {
0580: res.setContentType(ctype);
0581: } else {
0582: res.setContentType(DEF_CONTENT_TYPE);
0583: }
0584: if (spdoc.getResponseError() == HttpServletResponse.SC_NOT_FOUND
0585: && spdoc.getDocument() != null) {
0586: String stylesheet = extractStylesheetFromSPDoc(spdoc);
0587: if (generator.getTarget(stylesheet) != null) {
0588: spdoc.setResponseError(0);
0589: spdoc.setResponseErrorText(null);
0590: }
0591: }
0592:
0593: // if the document contains a error code, do errorhandling here and no further processing.
0594: int err;
0595: String errtxt;
0596: try {
0597: if ((err = spdoc.getResponseError()) != 0) {
0598: setCookies(spdoc, res);
0599: if ((errtxt = spdoc.getResponseErrorText()) != null) {
0600: res.sendError(err, errtxt);
0601: } else {
0602: res.sendError(err);
0603: }
0604: return;
0605: }
0606: } catch (IOException e) {
0607: throw new PustefixCoreException(
0608: "IOException while trying to send an error code", e);
0609: }
0610:
0611: // So no error happened, let's go on with normal processing.
0612: HttpSession session = preq.getSession(false);
0613: TreeMap<String, Object> paramhash = constructParameters(spdoc,
0614: params, session);
0615: String stylesheet = extractStylesheetFromSPDoc(spdoc);
0616: if (stylesheet == null) {
0617: throw new PustefixCoreException(
0618: "Wasn't able to extract any stylesheet specification from page '"
0619: + spdoc.getPagename()
0620: + "' ... bailing out.");
0621: }
0622:
0623: //Performance tracking
0624: PerfEvent pe = new PerfEvent(
0625: PerfEventType.XMLSERVER_HANDLEDOCUMENT, spdoc
0626: .getPagename());
0627: pe.start();
0628:
0629: if (spdoc.docIsUpdateable()) {
0630: if (stylesheet.indexOf("::") > 0) {
0631: spdoc.getDocument().getDocumentElement().setAttribute(
0632: "used-pv", stylesheet);
0633: }
0634: spdoc.setDocument(Xml.parse(generator.getXsltVersion(),
0635: spdoc.getDocument()));
0636: spdoc.setDocIsUpdateable(false);
0637: }
0638:
0639: if (!spdoc.getTrailLogged()) {
0640: if (LOGGER.isDebugEnabled()) {
0641: LOGGER.debug("*** Using document:" + spdoc);
0642: }
0643: if (LOGGER.isInfoEnabled()) {
0644: LOGGER.info(" *** Using stylesheet: " + stylesheet
0645: + " ***");
0646: }
0647: }
0648:
0649: if (!doreuse) {
0650: // we only want to update the Session hit when we are not handling a "reuse" request
0651: if (session != null) {
0652: SessionAdmin.getInstance().touchSession(servletname,
0653: stylesheet, session);
0654: }
0655: // Only process cookies if we don't reuse
0656: setCookies(spdoc, res);
0657: }
0658:
0659: boolean modified_or_no_etag = true;
0660: String etag_incoming = preq.getRequest().getHeader(
0661: "If-None-Match");
0662: ByteArrayOutputStream output = new ByteArrayOutputStream(4096);
0663:
0664: try {
0665: hookBeforeRender(preq, spdoc, paramhash, stylesheet);
0666: render(spdoc, getRendering(preq), res, paramhash,
0667: stylesheet, output);
0668: } finally {
0669: hookAfterRender(preq, spdoc, paramhash, stylesheet);
0670: }
0671:
0672: PerfEvent pe_etag = new PerfEvent(
0673: PerfEventType.XMLSERVER_CREATEETAG, spdoc.getPagename());
0674: pe_etag.start();
0675: String etag_outgoing = MD5Utils.hex_md5(output.toString());
0676: pe_etag.save();
0677: res.setHeader("ETag", etag_outgoing);
0678:
0679: if (getRendering(preq) == RENDER_NORMAL
0680: && etag_incoming != null
0681: && etag_incoming.equals(etag_outgoing)) {
0682: //it's important to reset the content type, because old Apache versions in conjunction
0683: //with mod_ssl and mod_deflate (here enabled for text/html) gzip 304 responses and thus
0684: //cause non-empty response bodies which confuses the browsers
0685: res.setContentType(null);
0686: res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
0687: LOGGER.info("*** Reusing UI: " + spdoc.getPagename());
0688: modified_or_no_etag = false;
0689: } else {
0690: try {
0691: output.writeTo(res.getOutputStream());
0692: } catch (IOException e) {
0693: throw new PustefixCoreException(e);
0694: }
0695: }
0696:
0697: long handletime = System.currentTimeMillis() - currtime;
0698: preq.getRequest().setAttribute(TRAFOTIME, handletime);
0699:
0700: Map<String, Object> addinfo = addtrailinfo.getData(preq);
0701:
0702: if (session != null && !spdoc.getTrailLogged()) {
0703: StringBuffer logbuff = new StringBuffer();
0704: logbuff.append(session.getAttribute(VISIT_ID) + "|");
0705: logbuff.append(session.getId() + "|");
0706: logbuff.append(preq.getRemoteAddr() + "|");
0707: logbuff.append(preq.getServerName() + "|");
0708: logbuff.append(stylesheet + "|");
0709: logbuff.append(preq.getOriginalRequestURI());
0710: if (preq.getQueryString() != null) {
0711: logbuff.append("?" + preq.getQueryString());
0712: }
0713: String flow = (String) paramhash.get("pageflow");
0714: if (flow != null) {
0715: logbuff.append("|" + flow);
0716: }
0717: for (Iterator<String> keys = addinfo.keySet().iterator(); keys
0718: .hasNext();) {
0719: logbuff.append("|" + addinfo.get(keys.next()));
0720: }
0721: LOGGER_TRAIL.warn(logbuff.toString());
0722: spdoc.setTrailLogged();
0723: }
0724:
0725: pe.save();
0726:
0727: if (LOGGER.isInfoEnabled()) {
0728: LOGGER.info(">>> Complete handleDocument(...) took "
0729: + handletime + "ms" + " (needed xslt: "
0730: + modified_or_no_etag + ")");
0731: }
0732:
0733: try {
0734: if (modified_or_no_etag
0735: && (spdoc.getResponseContentType() == null || spdoc
0736: .getResponseContentType().startsWith(
0737: "text/html"))) {
0738: OutputStream out = res.getOutputStream();
0739: OutputStreamWriter writer = new OutputStreamWriter(out,
0740: res.getCharacterEncoding());
0741: writer.write("\n<!--");
0742: for (Iterator<String> keys = addinfo.keySet()
0743: .iterator(); keys.hasNext();) {
0744: String key = keys.next();
0745: writer.write(" " + key + ": " + addinfo.get(key));
0746: }
0747: writer.write(" -->");
0748: writer.flush();
0749: }
0750: } catch (Exception e) {
0751: // ignore
0752: }
0753: }
0754:
0755: private void render(SPDocument spdoc, int rendering,
0756: HttpServletResponse res, TreeMap<String, Object> paramhash,
0757: String stylesheet, OutputStream output)
0758: throws RenderingException {
0759: try {
0760: switch (rendering) {
0761: case RENDER_NORMAL:
0762: renderNormal(spdoc, res, paramhash, stylesheet, output);
0763: break;
0764: case RENDER_FONTIFY:
0765: renderFontify(spdoc, res, paramhash);
0766: break;
0767: case RENDER_EXTERNAL:
0768: renderExternal(spdoc, res, paramhash, stylesheet);
0769: break;
0770: case RENDER_XMLONLY:
0771: renderXmlonly(spdoc, res);
0772: break;
0773: default:
0774: throw new IllegalArgumentException("unkown rendering: "
0775: + rendering);
0776: }
0777: } catch (TransformerConfigurationException e) {
0778: throw new RenderingException(
0779: "Exception while rendering page "
0780: + spdoc.getPagename() + " with stylesheet "
0781: + stylesheet, e);
0782: } catch (TargetGenerationException e) {
0783: throw new RenderingException(
0784: "Exception while rendering page "
0785: + spdoc.getPagename() + " with stylesheet "
0786: + stylesheet, e);
0787: } catch (IOException e) {
0788: throw new RenderingException(
0789: "Exception while rendering page "
0790: + spdoc.getPagename() + " with stylesheet "
0791: + stylesheet, e);
0792: } catch (TransformerException e) {
0793: throw new RenderingException(
0794: "Exception while rendering page "
0795: + spdoc.getPagename() + " with stylesheet "
0796: + stylesheet, e);
0797: }
0798: }
0799:
0800: private void renderXmlonly(SPDocument spdoc, HttpServletResponse res)
0801: throws IOException {
0802: Xml.serialize(spdoc.getDocument(), res.getOutputStream(), true,
0803: true);
0804: }
0805:
0806: private void renderNormal(SPDocument spdoc,
0807: HttpServletResponse res, TreeMap<String, Object> paramhash,
0808: String stylesheet, OutputStream output)
0809: throws TargetGenerationException, IOException,
0810: TransformerException {
0811: Templates stylevalue;
0812: Target target = generator.getTarget(stylesheet);
0813: paramhash.put("themes", target.getThemes().getId());
0814: stylevalue = (Templates) target.getValue();
0815: if (stylevalue == null) { // AH 2004-09-21 added for bugtracing
0816: LOGGER.warn("stylevalue MUST NOT be null: stylesheet="
0817: + stylesheet
0818: + "; "
0819: + ((spdoc != null) ? ("pagename=" + spdoc
0820: .getPagename()) : "spdoc==null"));
0821: }
0822: try {
0823: Xslt.transform(spdoc.getDocument(), stylevalue, paramhash,
0824: new StreamResult(output));
0825: } catch (TransformerException e) {
0826: Throwable inner = e.getException();
0827: Throwable cause = null;
0828: if (inner != null) {
0829: cause = inner.getCause();
0830: }
0831:
0832: if ((inner != null && inner instanceof SocketException)
0833: || (cause != null && cause instanceof SocketException)) {
0834: LOGGER.warn("[Ignored TransformerException] : "
0835: + e.getMessage());
0836: } else {
0837: throw e;
0838: }
0839: }
0840: }
0841:
0842: // FIXME Remove external render mode as it is NOT WORKING
0843: private void renderExternal(SPDocument spdoc,
0844: HttpServletResponse res, TreeMap<String, Object> paramhash,
0845: String stylesheet) throws TransformerException,
0846: TransformerConfigurationException,
0847: TransformerFactoryConfigurationError, IOException {
0848: Document doc = spdoc.getDocument();
0849: Document ext = doc;
0850: if (/* !spdoc.getNostore() || */spdoc.isRedirect()) {
0851: ext = Xml.createDocument();
0852: ext.appendChild(ext.importNode(doc.getDocumentElement(),
0853: true));
0854: }
0855:
0856: ext.insertBefore(ext
0857: .createProcessingInstruction("xml-stylesheet",
0858: "type=\"text/xsl\" media=\"all\" href=\"file: //"
0859: + generator.getName() + "/"
0860: + stylesheet + "\""), ext
0861: .getDocumentElement());
0862: for (Iterator<String> i = paramhash.keySet().iterator(); i
0863: .hasNext();) {
0864: String key = i.next();
0865: String val = (String) paramhash.get(key);
0866: ext.insertBefore(ext.createProcessingInstruction(
0867: "modxslt-param", "name=\"" + key + "\" value=\""
0868: + val + "\""), ext.getDocumentElement());
0869: }
0870: res.setContentType("text/xml");
0871: TransformerFactory.newInstance().newTransformer().transform(
0872: new DOMSource(ext),
0873: new StreamResult(res.getOutputStream()));
0874: }
0875:
0876: private void renderFontify(SPDocument spdoc,
0877: HttpServletResponse res, TreeMap<String, Object> paramhash)
0878: throws TargetGenerationException, IOException {
0879: Templates stylevalue = (Templates) generator
0880: .createXSLLeafTarget(FONTIFY_SSHEET).getValue();
0881: try {
0882: Xslt.transform(spdoc.getDocument(), stylevalue, paramhash,
0883: new StreamResult(res.getOutputStream()));
0884: } catch (TransformerException e) {
0885: LOGGER
0886: .warn("*** Ignored exception when trying to render XML tree ***");
0887: }
0888:
0889: }
0890:
0891: private int getRendering(PfixServletRequest pfreq) {
0892: String value;
0893: int rendering;
0894: RequestParam xmlonly;
0895:
0896: if (render_external) {
0897: return RENDER_EXTERNAL;
0898: }
0899: xmlonly = pfreq.getRequestParam(PARAM_XMLONLY);
0900: if (xmlonly == null) {
0901: return RENDER_NORMAL;
0902: }
0903: value = xmlonly.getValue();
0904: if (value.equals(PARAM_XMLONLY_XMLONLY)) {
0905: rendering = RENDER_XMLONLY;
0906: } else if (value.equals(PARAM_XMLONLY_FONTIFY)) {
0907: rendering = RENDER_FONTIFY;
0908: } else {
0909: throw new IllegalArgumentException("invalid value for "
0910: + PARAM_XMLONLY + ": " + value);
0911: }
0912: if (editmodeAllowed
0913: || TestRecording.getInstance().isKnownClient(
0914: pfreq.getRemoteAddr())) {
0915: return rendering;
0916: } else {
0917: return RENDER_NORMAL;
0918: }
0919: }
0920:
0921: private TreeMap<String, Object> constructParameters(
0922: SPDocument spdoc, Properties gen_params, HttpSession session)
0923: throws NavigationInitializationException {
0924: TreeMap<String, Object> paramhash = new TreeMap<String, Object>();
0925: HashMap<String, Object> params = spdoc.getProperties();
0926: // These are properties which have been set in the process method
0927: // e.g. Frame handling is stored here
0928: if (gen_params != null) {
0929: for (Enumeration<?> e = gen_params.keys(); e
0930: .hasMoreElements();) {
0931: String name = (String) e.nextElement();
0932: String value = gen_params.getProperty(name);
0933: if (name != null && value != null) {
0934: paramhash.put(name, value);
0935: }
0936: }
0937: }
0938: // These are the parameters that may be set by the DOM tree producing
0939: // method of the servlet (something that implements the abstract method getDom())
0940: if (params != null) {
0941: for (Iterator<String> iter = params.keySet().iterator(); iter
0942: .hasNext();) {
0943: String name = iter.next();
0944: Object value = params.get(name);
0945: if (name != null && value != null) {
0946: paramhash.put(name, value);
0947: }
0948: }
0949: }
0950: paramhash.put(TargetGenerator.XSLPARAM_TG, targetconf.toURI()
0951: .toString());
0952: paramhash.put(TargetGenerator.XSLPARAM_TKEY, VALUE_NONE);
0953: paramhash.put(TargetGenerator.XSLPARAM_NAVITREE,
0954: NavigationFactory.getInstance().getNavigation(
0955: targetconf, generator.getXsltVersion())
0956: .getNavigationXMLElement());
0957:
0958: String session_to_link_from_external = SessionAdmin
0959: .getInstance().getExternalSessionId(session);
0960: paramhash.put("__external_session_ref",
0961: session_to_link_from_external);
0962: paramhash.put("__spdoc__", spdoc);
0963: paramhash.put("__register_frame_helper__",
0964: new RegisterFrameHelper(getLRU(session), spdoc));
0965: return paramhash;
0966: }
0967:
0968: private String extractStylesheetFromSPDoc(SPDocument spdoc) {
0969: // First look if the pagename is set
0970: String pagename = spdoc.getPagename();
0971: if (pagename != null) {
0972: Variant variant = spdoc.getVariant();
0973: PageTargetTree pagetree = generator.getPageTargetTree();
0974: PageInfo pinfo = null;
0975: Target target = null;
0976: String variant_id = null;
0977: if (variant != null
0978: && variant.getVariantFallbackArray() != null) {
0979: String[] variants = variant.getVariantFallbackArray();
0980: for (int i = 0; i < variants.length; i++) {
0981: variant_id = variants[i];
0982: LOGGER.info(" ** Trying variant '" + variant_id
0983: + "' **");
0984: pinfo = PageInfoFactory.getInstance().getPage(
0985: generator, pagename, variant_id);
0986: target = pagetree.getTargetForPageInfo(pinfo);
0987: if (target != null) {
0988: return target.getTargetKey();
0989: }
0990: }
0991: }
0992: if (target == null) {
0993: LOGGER.info(" ** Trying root variant **");
0994: pinfo = PageInfoFactory.getInstance().getPage(
0995: generator, pagename, null);
0996: target = pagetree.getTargetForPageInfo(pinfo);
0997: }
0998: if (target == null) {
0999: LOGGER
1000: .warn("\n********************** NO TARGET ******************************");
1001: return null;
1002: } else {
1003: return target.getTargetKey();
1004: }
1005: } else {
1006: // other possibility: an explicit xslkey is given:
1007: return spdoc.getXSLKey();
1008: }
1009: }
1010:
1011: private SPDocument doReuse(PfixServletRequest preq,
1012: CacheValueLRU<String, SPDocument> storeddoms) {
1013: HttpSession session = preq.getSession(false);
1014: if (session != null) {
1015: RequestParam reuse = preq.getRequestParam(PARAM_REUSE);
1016: if (reuse != null && reuse.getValue() != null) {
1017: SPDocument saved = storeddoms.get(reuse.getValue());
1018: if (preq.getPageName() != null
1019: && saved != null
1020: && saved.getPagename() != null
1021: && !preq.getPageName().equals(
1022: saved.getPagename())) {
1023: if (LOGGER.isDebugEnabled())
1024: LOGGER
1025: .debug("Don't reuse SPDocument because pagenames differ: "
1026: + preq.getPageName()
1027: + " -> "
1028: + saved.getPagename());
1029: saved = null;
1030: }
1031: if (LOGGER.isDebugEnabled()) {
1032: if (saved != null)
1033: LOGGER.debug("Reuse SPDocument "
1034: + saved.getTimestamp()
1035: + " restored with key "
1036: + reuse.getValue());
1037: else
1038: LOGGER.debug("No SPDocument with key "
1039: + reuse.getValue() + " found");
1040: }
1041: return saved;
1042: } else if (getRendering(preq) == AbstractXMLServlet.RENDER_FONTIFY) {
1043: return (SPDocument) session
1044: .getAttribute(ATTR_SHOWXMLDOC);
1045: } else {
1046: return null;
1047: }
1048: } else {
1049: return null;
1050: }
1051: }
1052:
1053: private Map<String, String> createAnchorMap(RequestParam[] anchors) {
1054: Map<String, String> map = new HashMap<String, String>();
1055: for (int i = 0; i < anchors.length; i++) {
1056: String value = anchors[i].getValue();
1057: int pos = value.indexOf("|");
1058: if (pos < 0)
1059: pos = value.indexOf(":"); // This is for backwards compatibility, but should not be used anymore!
1060: if (pos < (value.length() - 1) && pos > 0) {
1061: String frame = value.substring(0, pos);
1062: String anchor = value.substring(pos + 1);
1063: map.put(frame, anchor);
1064: }
1065: }
1066: return map;
1067: }
1068:
1069: private void verifyDirExists(String tmpdir) {
1070: File temporary_dir = new File(tmpdir);
1071: if (!temporary_dir.exists()) {
1072: boolean ok = temporary_dir.mkdirs();
1073: if (LOGGER.isInfoEnabled()) {
1074: LOGGER.info(temporary_dir.getPath()
1075: + " did not exist. Created now. Sucess:" + ok);
1076: }
1077: }
1078: }
1079:
1080: private void setCookies(SPDocument spdoc, HttpServletResponse res) {
1081: if (spdoc.getCookies() != null && !spdoc.getCookies().isEmpty()) {
1082: if (LOGGER.isDebugEnabled()) {
1083: LOGGER.debug("*** Sending cookies ***");
1084: }
1085: // Now adding the Cookies from spdoc
1086: for (Iterator<Cookie> i = spdoc.getCookies().iterator(); i
1087: .hasNext();) {
1088: Cookie cookie = i.next();
1089: if (LOGGER.isDebugEnabled()) {
1090: LOGGER.debug(" Add cookie: " + cookie);
1091: }
1092: res.addCookie(cookie);
1093: }
1094: }
1095: }
1096:
1097: /**
1098: * Called before the XML result tree is rendered. This method can
1099: * be overidden in sub-implementations to do initializiation stuff,
1100: * which might be needed for XSLT callback methods.
1101: *
1102: * @param preq current servlet request
1103: * @param spdoc XML document going to be rendered
1104: * @param paramhash parameters supplied to XSLT code
1105: * @param stylesheet name of the stylesheet being used
1106: */
1107: protected void hookBeforeRender(PfixServletRequest preq,
1108: SPDocument spdoc, TreeMap<String, Object> paramhash,
1109: String stylesheet) {
1110: // Empty in default implementation
1111: }
1112:
1113: /**
1114: * Called after the XML result tree is rendered. This method can
1115: * be overidden in sub-implementations to do de-initializiation stuff.
1116: * This method is guaranteed to be always called, when the corresponding
1117: * before method has been called - even if an error occurs.
1118: *
1119: * @param preq current servlet request
1120: * @param spdoc XML document going to be rendered
1121: * @param paramhash parameters supplied to XSLT code
1122: * @param stylesheet name of the stylesheet being used
1123: */
1124: protected void hookAfterRender(PfixServletRequest preq,
1125: SPDocument spdoc, TreeMap<String, Object> paramhash,
1126: String stylesheet) {
1127: // Empty in default implementation
1128: }
1129:
1130: public class RegisterFrameHelper {
1131: private CacheValueLRU<String, SPDocument> storeddoms;
1132: private SPDocument spdoc;
1133:
1134: private RegisterFrameHelper(
1135: CacheValueLRU<String, SPDocument> storeddoms,
1136: SPDocument spdoc) {
1137: this .storeddoms = storeddoms;
1138: this .spdoc = spdoc;
1139: }
1140:
1141: public void registerFrame(String frameName) {
1142: SessionCleaner.getInstance().storeSPDocument(spdoc,
1143: frameName, storeddoms, scleanertimeout);
1144: }
1145:
1146: public void unregisterFrame(String frameName) {
1147: String reuseKey = spdoc.getTimestamp() + "";
1148: if (frameName.equals("_top")) {
1149: frameName = null;
1150: }
1151: if (frameName != null && frameName.length() > 0) {
1152: reuseKey += "." + frameName;
1153: }
1154: if (LOGGER.isDebugEnabled()) {
1155: LOGGER.debug("Remove SPDocument stored under "
1156: + reuseKey);
1157: }
1158: storeddoms.remove(reuseKey);
1159: }
1160: }
1161:
1162: }
|