Java tutorial
/* * Copyright (c) 2012. betterFORM Project - http://www.betterform.de * Licensed under the terms of BSD License */ package de.betterform.agent.web; import de.betterform.BetterFORMConstants; import de.betterform.agent.web.event.DefaultUIEventImpl; import de.betterform.agent.web.event.UIEvent; import de.betterform.agent.web.flux.FluxProcessor; import de.betterform.agent.web.servlet.HttpRequestHandler; import de.betterform.agent.web.servlet.XFormsPostServlet; import de.betterform.generator.UIGenerator; import de.betterform.generator.XSLTGenerator; import de.betterform.xml.config.Config; import de.betterform.xml.config.XFormsConfigException; import de.betterform.xml.dom.DOMUtil; import de.betterform.xml.events.BetterFormEventNames; import de.betterform.xml.events.XMLEvent; import de.betterform.xml.ns.NamespaceConstants; import de.betterform.xml.xforms.AbstractProcessorDecorator; import de.betterform.xml.xforms.XFormsProcessorImpl; import de.betterform.xml.xforms.exception.XFormsException; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.events.Event; import org.xml.sax.InputSource; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import java.io.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Superclass for Adapters used in web applications. Does minimal event listening on the processor and provides * a common base to build webadapers. * * @author Joern Turner * @version $Id: WebAdapter.java 2875 2007-09-28 09:43:30Z lars $ * @see de.betterform.agent.web.flux.FluxProcessor * @see de.betterform.agent.web.servlet.PlainHtmlProcessor */ public class WebProcessor extends AbstractProcessorDecorator { /** * Defines the key for accessing (HTTP) session ids. */ // todo: harmonize keys should be: REQUEST_URI="REQUEST_URI"; to have the same name when accessing from form with {$REQUEST_URI} public static final String REQUEST_URI = "requestURI"; public static final String REQUEST_URL = "requestURL"; public static final String PATH_INFO = "pathInfo"; public static final String QUERY_STRING = "queryString"; public static final String CONTEXTROOT = "contextroot"; public static final String SESSION_ID = "betterform.session.id"; public static final String REALPATH = "webapp.realpath"; public static final String XSL_PARAM_NAME = "xslt"; public static final String ACTIONURL_PARAM_NAME = "action_url"; public static final String UIGENERATOR = "betterform.UIGenerator"; public static final String REFERER = "betterform.referer"; public static final String FORWARD_URL = "betterform.base.url"; public static final String ADAPTER_PREFIX = "A"; public static final String REQUEST_PATH = "requestpath"; /** * constant for relative location of resources (relative to web context). * Hardcoded as resources are considered betterFORM-internal and their loading should not be touched by users. */ public static final String RESOURCE_DIR = "WEB-INF/classes/META-INF/resources/"; public static final String ALTERNATIVE_ROOT = "ResourcePath"; //todo:review - can be deleted when ehcache is in place String KEEPALIVE_PULSE = "keepalive"; protected transient HttpRequestHandler httpRequestHandler; protected transient XMLEvent exitEvent = null; protected String contextRoot; private String key = null; protected transient HttpServletRequest request; protected transient HttpServletResponse response; protected transient HttpSession httpSession; protected transient ServletContext context; // protected boolean isXFormsPresent = false; private static final Log LOGGER = LogFactory.getLog(FluxProcessor.class); protected String uploadDestination; protected String useragent; protected String uploadDir; protected transient UIGenerator uiGenerator; public WebProcessor() { super(); } public void configure() throws XFormsException { initConfig(); Cookie[] cookies = request.getCookies(); List<Cookie> cookiesList; if (cookies != null) { cookiesList = Arrays.asList(request.getCookies()); } else { cookiesList = Collections.EMPTY_LIST; } WebUtil.storeCookies(cookiesList, this); WebUtil.setContextParams(request, httpSession, this, getKey()); WebUtil.copyHttpHeaders(request, this); setLocale(); configureUpload(); } /** * the string identifier generated by this XFormsSession for use in the client * * @return the string identifier generated by this XFormsSession for use in the client */ public String getKey() { if (this.key == null) { this.key = generateXFormsSessionKey(); } return this.key; } protected void setKey(String key) { LOGGER.debug("Settting key: " + key); this.key = key; } public void setRequest(HttpServletRequest request) { this.request = request; } public void setResponse(HttpServletResponse response) { this.response = response; } public void setHttpSession(HttpSession httpSession) { this.httpSession = httpSession; } public void setUseragent(String useragent) { this.useragent = useragent; } /** * passes an XForms document to the betterForm Adapter. It supports 4 different ways of passing * a XForms:<br/> * a. passing a request param with a URI e.g. form=/forms/foo.xhtml<br/> * b. passing a URI as request attribute<br/> * c. passing a request attribute of 'XFORMS_NODE' with a value containing the input document<br/> * d. passing an inputstream containing the document<br/> * e. passing a SAX inputsource containing the document. * * @throws de.betterform.xml.xforms.exception.XFormsException * */ public void setXForms() throws XFormsException { if (this.xformsProcessor == null) { throw new XFormsException("Adapter has not yet been initialized"); } if (request.getParameter(WebFactory.FORM_PARAM_NAME) != null) { try { String formURI = WebUtil.getFormUrlAsString(this.request); this.xformsProcessor.setXForms(new URI(formURI)); //set the base URI explicitly here cause it must match the path of the loaded form setBaseURI(formURI); } catch (URISyntaxException e) { throw new XFormsException("URI is malformed: " + e); } catch (MalformedURLException e) { throw new XFormsException("URL is malformed: " + e); } catch (UnsupportedEncodingException e) { throw new XFormsException("Encoding of form Url is not supported: " + e); } } else if (request.getAttribute(WebFactory.XFORMS_URI) != null) { String uri = (String) request.getAttribute(WebFactory.XFORMS_URI); try { this.xformsProcessor.setXForms(new URI(WebUtil.decodeUrl(uri, request))); } catch (URISyntaxException e) { throw new XFormsException("URI is malformed: " + e); } catch (UnsupportedEncodingException e) { throw new XFormsException("Encoding of form Url is not supported: " + e); } } else if (request.getAttribute(WebFactory.XFORMS_NODE) != null) { Node node = (Node) request.getAttribute(WebFactory.XFORMS_NODE); this.xformsProcessor.setXForms(node); } else if (request.getAttribute(WebFactory.XFORMS_INPUTSTREAM) != null) { InputStream inputStream = (InputStream) request.getAttribute(WebFactory.XFORMS_INPUTSTREAM); this.xformsProcessor.setXForms(inputStream); } else if (request.getAttribute(WebFactory.XFORMS_INPUTSOURCE) != null) { InputSource inputSource = (InputSource) request.getAttribute(WebFactory.XFORMS_INPUTSOURCE); this.xformsProcessor.setXForms(inputSource); } else { throw new XFormsException("no XForms input document found - init failed"); } // isXFormsPresent = true; if (this.configuration.getProperty("webprocessor.doIncludes").equals("true")) { doIncludes(); } } //todo: parse accept-language header to decide about locale public void setLocale() throws XFormsException { if (Config.getInstance().getProperty(XFormsProcessorImpl.BETTERFORM_ENABLE_L10N).equals("true")) { //[1] check for request param 'lang' - todo: might need refinement later to not clash with using apps //String locale = request.getParameter("lang"); if (request.getParameter("lang") != null) { if (WebProcessor.LOGGER.isDebugEnabled()) { WebProcessor.LOGGER.debug("using 'lang' Url parameter: " + request.getParameter("lang")); } this.setLocale(request.getParameter("lang")); } else if ((String) request.getAttribute("lang") != null) { if (WebProcessor.LOGGER.isDebugEnabled()) { WebProcessor.LOGGER.debug("using request Attribute 'lang': " + request.getParameter("lang")); } this.setLocale((String) request.getAttribute("lang")); } else if (StringUtils.isNotBlank(Config.getInstance().getProperty("preselect-language"))) { if (WebProcessor.LOGGER.isDebugEnabled()) { WebProcessor.LOGGER.debug("using configured lang setting from Config: " + Config.getInstance().getProperty("preselect-language")); } this.setLocale(Config.getInstance().getProperty("preselect-language")); } else if (request.getHeader("accept-language") != null) { if (WebProcessor.LOGGER.isDebugEnabled()) { WebProcessor.LOGGER .debug("using accept-language header: " + request.getHeader("accept-language")); } //getLocale takes the one with the highest priority setLocale(request.getLocale().getLanguage()); } } else { //fallback default this.setLocale("en"); } } /** * Makes sure to return the context in which the processor is running. * It uses the current httpSession if context is null. If the context * is explicitly set with setContext() it returns this context. * * @return the context in which the processor is running. */ public ServletContext getContext() { // Return the betterform context when set if (this.context != null) { return context; } if (httpSession != null) { // otherwise get the context from the http session. return httpSession.getServletContext(); } return null; } /** * Overwrites the (servlet) context to use. If not set * the http session is used to get the servlet context. * * @param context in which this processor is executed. */ public void setContext(ServletContext context) { this.context = context; } /** * initialize the Adapter. This is necessary cause often the using * application will need to configure the Adapter before actually using it. * * @throws de.betterform.xml.xforms.exception.XFormsException * */ public void init() throws XFormsException { // if (noHttp()) { // throw new XFormsException("request, response and session object are undefined"); // } this.configuration = Config.getInstance(); addEventListeners(); // init processor this.xformsProcessor.init(); } public XMLEvent checkForExitEvent() { return this.exitEvent; } /** * Dispatch a UIEvent to trigger some XForms processing such as updating * of values or execution of triggers. * * @param event an application specific event * @throws de.betterform.xml.xforms.exception.XFormsException * */ public void handleUIEvent(UIEvent event) throws XFormsException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Event " + event.getEventName() + " dispatched"); LOGGER.debug("Event target: " + event.getId()); } } /** * listen to processor and add a DefaultUIEventImpl object to the * EventQueue. * * @param event the handled DOMEvent */ public void handleEvent(Event event) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling Event " + event.getType()); } } /** * set the upload location. This string represents the destination (data-sink) for uploads. * * @param destination a String representing the location where to store uploaded files/data. */ public void setUploadDestination(String destination) { this.uploadDestination = destination; } /** * processes the request after init. * * @throws java.io.IOException * @throws de.betterform.xml.xforms.exception.XFormsException * * @throws java.net.URISyntaxException */ public synchronized void handleRequest() throws XFormsException { boolean updating = false; //this will become true in case PlainHtmlProcessor is in use WebUtil.nonCachingResponse(response); /* todo: extract behavior that checks for exit events and move to init(). */ try { //EXIST-WORKAROUND: TODO triple check ... if (request.getMethod().equalsIgnoreCase("POST") && request.getAttribute(XFormsPostServlet.INIT_BY_POST) == null && request.getAttribute("org.exist.forward") == null) { updating = true; // updating ... - this is only called when PlainHtmlProcessor is in use or an upload happens UIEvent uiEvent = new DefaultUIEventImpl(); uiEvent.initEvent("http-request", null, request); handleUIEvent(uiEvent); } XMLEvent exitEvent = checkForExitEvent(); if (exitEvent != null) { handleExit(exitEvent); } else { String referer = null; if (updating) { //todo: check if this code is still needed (used by XFormsPostServlet?) referer = (String) getContextParam(REFERER); setContextParam("update", "true"); String forwardTo = request.getContextPath() + "/view?sessionKey=" + getKey() + "&referer=" + referer; response.sendRedirect(response.encodeRedirectURL(forwardTo)); } else { //initing ... referer = request.getQueryString(); String userAgent = request.getHeader("User-Agent"); String xuaCompatible = request.getHeader("X-UA-Compatible"); if (xuaCompatible == null && (userAgent.contains("IE8") || userAgent.contains("MSIE 8"))) { response.addHeader("X-UA-Compatible", "IE8"); } else if (xuaCompatible == null && (userAgent.contains("IE9") || userAgent.contains("MSIE 9"))) { response.addHeader("X-UA-Compatible", "IE9"); } response.setContentType(WebUtil.HTML_CONTENT_TYPE); //we got an initialization request (GET) - the session is not registered yet createUIGenerator(); //store queryString as 'referer' in XFormsSession setContextParam(REFERER, request.getContextPath() + request.getServletPath() + "?" + referer); //todo:check if it's still necessary to set an attribute to the session //EXIST-WORKAROUND: TODO triple check ... if (request.isRequestedSessionIdValid()) { httpSession.setAttribute("TimeStamp", System.currentTimeMillis()); } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("WebProcessor: Begin Form: \n"); DOMUtil.prettyPrintDOM(this.xformsProcessor.getXForms()); LOGGER.debug("\nWebProcessor: End Form"); } generateUI(this.xformsProcessor.getXForms(), outputStream); response.setContentLength(outputStream.toByteArray().length); response.getOutputStream().write(outputStream.toByteArray()); } } } catch (IOException e) { throw new XFormsException(e); } catch (URISyntaxException e) { throw new XFormsException(e); } WebUtil.printSessionKeys(this.httpSession); } protected void generateUI(Object input, Object output) throws XFormsException { uiGenerator.setInput(input); uiGenerator.setOutput(output); uiGenerator.generate(); } /** * Handles XForms exist events. There are only 2 situations when this occurs. A load action or a submission/@replace='all' * firing during XForms Model init. * * @param exitEvent the XMLEvent representing the exit condition * @throws java.io.IOException occurs if the redirect fails */ public void handleExit(XMLEvent exitEvent) throws IOException { if (BetterFormEventNames.REPLACE_ALL.equals(exitEvent.getType())) { response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/" + BetterFORMConstants.SUBMISSION_RESPONSE + "?sessionKey=" + getKey())); } else if (BetterFormEventNames.REPLACE_ALL_XFORMS.equals(exitEvent.getType())) { WebUtil.removeSession(getKey()); response.sendRedirect(response.encodeRedirectURL( (String) exitEvent.getContextInfo(BetterFORMConstants.SUBMISSION_REDIRECT_XFORMS))); } else if (BetterFormEventNames.LOAD_URI.equals(exitEvent.getType())) { //todo: this check seems insufficient - should be a load show="replace" if (exitEvent.getContextInfo("show") != null) { String loadURI = (String) exitEvent.getContextInfo("uri"); //kill XFormsSession WebUtil.removeSession(getKey()); if (WebProcessor.LOGGER.isDebugEnabled()) { WebProcessor.LOGGER.debug("loading: " + loadURI); } response.sendRedirect(response.encodeRedirectURL(loadURI)); } } WebProcessor.LOGGER .debug("************************* EXITED DURING XFORMS MODEL INIT *************************"); } /** * close the XFormsSession in case of an exception. This will close the WebAdapter holding the betterForm Processor instance, * remove the XFormsSession from the Manager and redirect to the error page. * * @param e the root exception causing the close * @throws java.io.IOException */ public void close(Exception e) throws IOException { // attempt to shutdown processor if (this.xformsProcessor != null) { try { this.xformsProcessor.shutdown(); } catch (XFormsException xfe) { WebProcessor.LOGGER.error("Message: " + xfe.getMessage() + " Cause: " + xfe.getCause()); } } // store exception httpSession.setAttribute("betterform.exception", e); //remove session from XFormsSessionManager // getManager().deleteXFormsSession(this.key); WebUtil.removeSession(getKey()); // redirect to error page (after encoding session id if required) response.sendRedirect(response.encodeRedirectURL( request.getContextPath() + "/" + configuration.getProperty(WebFactory.ERROPAGE_PROPERTY))); } /** * passes the betterform-defaults.xml config file to betterForm Processor. * * @throws de.betterform.xml.xforms.exception.XFormsException * */ protected void initConfig() throws XFormsException { final String initParameter = getContext().getInitParameter(WebFactory.BETTERFORM_CONFIG_PATH); String configPath = WebFactory.getRealPath(initParameter, getContext()); if ((configPath != null) && !(configPath.equals(""))) { this.xformsProcessor.setConfigPath(configPath); this.configuration = Config.getInstance(); } } //todo: might be pushed up private String generateXFormsSessionKey() { StringBuffer key = new StringBuffer(String.valueOf(System.currentTimeMillis())); key.append(String.valueOf(Math.random())); return DigestUtils.sha512Hex(key.toString()); } protected HttpRequestHandler getHttpRequestHandler() { if (this.httpRequestHandler == null) { this.httpRequestHandler = new HttpRequestHandler(this); this.httpRequestHandler.setUploadRoot(this.uploadDestination); this.httpRequestHandler.setSessionKey(this.getKey()); } return this.httpRequestHandler; } private URI getTransformURI() throws XFormsException, URISyntaxException { URI uri; //if we find a xsl param on the request URI this takes precedence over all String xslFile = request.getParameter(XSL_PARAM_NAME); String xsltPath = RESOURCE_DIR + "xslt"; if (xslFile != null) { return new File(WebFactory.getRealPath(xsltPath, getContext())).toURI().resolve(new URI(xslFile)); } //if we find a 'bf:transform' attribute on the root element of a form this takes priority over the global configuration in betterform-config.xml Element e = this.xformsProcessor.getXForms().getDocumentElement(); if (e.hasAttributeNS(NamespaceConstants.BETTERFORM_NS, "transform")) { String transformValue = e.getAttributeNS(NamespaceConstants.BETTERFORM_NS, "transform"); return new URI(WebUtil.getRequestURI(request) + transformValue); } //finally use the configuration // String configuredTransform = configuration.getStylesheet(this.useragent); String configuredTransform = configuration.getProperty("ui-transform"); //todo: this forces to load the transform from filesystem - should be changed if (configuredTransform != null) { return new File(WebFactory.getRealPath(xsltPath, getContext())).toURI() .resolve(new URI(configuredTransform)); } throw new XFormsConfigException( "There was no xslt stylesheet found on the request URI, the root element of the form or in the configfile"); } /** * creates and configures a UIGenerator that transcodes the XHTML/XForms document into the desired target format. * <p/> * todo: make configuration of xsl file more flexible 3 * todo: add baseURI as stylesheet param * * @return an instance of an UIGenerator * @throws java.net.URISyntaxException * @throws de.betterform.xml.xforms.exception.XFormsException * */ protected void createUIGenerator() throws URISyntaxException, XFormsException { String relativeUris = configuration.getProperty(WebFactory.RELATIVE_URI_PROPERTY); URI uri = getTransformURI(); // XSLTGenerator generator = setupTransformer(uri); XSLTGenerator generator = WebFactory.setupTransformer(uri, getContext()); generator.setParameter("sessionKey", getKey()); generator.setParameter("baseURI", getBaseURI()); String realPath = WebFactory.getRealPath("/", context); generator.setParameter("realPath", realPath); generator.setParameter("locale", locale); generator.setParameter("user-agent", request.getHeader("User-Agent")); generator.setParameter("action-url", getActionURL()); //todo: check this if (relativeUris.equals("true")) { generator.setParameter("contextroot", "."); } else { generator.setParameter("contextroot", WebUtil.getContextRoot(request)); } //todo: keep-alive should be deprecated and removed everywhere if (getContextParam(KEEPALIVE_PULSE) != null) { generator.setParameter("keepalive-pulse", getContextParam(KEEPALIVE_PULSE)); } generator.setParameter("debug-enabled", String.valueOf(configuration.getProperty("betterform.debug-allowed").equals("true"))); generator.setParameter("unloadingMessage", configuration.getProperty("betterform.unloading-message")); String selectorPrefix = Config.getInstance().getProperty(HttpRequestHandler.SELECTOR_PREFIX_PROPERTY, HttpRequestHandler.SELECTOR_PREFIX_DEFAULT); generator.setParameter("selector-prefix", selectorPrefix); String removeUploadPrefix = Config.getInstance().getProperty( HttpRequestHandler.REMOVE_UPLOAD_PREFIX_PROPERTY, HttpRequestHandler.REMOVE_UPLOAD_PREFIX_DEFAULT); generator.setParameter("remove-upload-prefix", removeUploadPrefix); String dataPrefix = Config.getInstance().getProperty("betterform.web.dataPrefix"); generator.setParameter("data-prefix", dataPrefix); String triggerPrefix = Config.getInstance().getProperty("betterform.web.triggerPrefix"); generator.setParameter("trigger-prefix", triggerPrefix); this.uiGenerator = generator; //store UIGenerator in context map of processor for use with load embed setContextParam(UIGENERATOR, this.uiGenerator); } private void doIncludes() { try { Node input = getXForms(); String xsltPath = RESOURCE_DIR + "xslt/"; URI styleURI = new File(WebFactory.getRealPath(xsltPath, getContext())).toURI() .resolve(new URI("include.xsl")); XSLTGenerator xsltGenerator = WebFactory.setupTransformer(styleURI, getContext()); String baseURI = getBaseURI(); String uri = baseURI.substring(0, baseURI.lastIndexOf("/") + 1); xsltGenerator.setParameter("root", uri); DOMResult result = new DOMResult(); DOMSource source = new DOMSource(input); xsltGenerator.setInput(source); xsltGenerator.setOutput(result); xsltGenerator.generate(); setXForms(result.getNode()); } catch (XFormsException e) { e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } } // private XSLTGenerator setupTransformer(URI uri) throws URISyntaxException { // return WebFactory.setupTransformer(uri,getContext()); // } /** * determines the value for the HTML form/@action attribute in the transcoded page * * @return the action url to be used in the HTML form */ private String getActionURL() { String defaultActionURL = this.request.getRequestURI(); String encodedDefaultActionURL = response.encodeURL(defaultActionURL); int sessIdx = encodedDefaultActionURL.indexOf(";jsession"); String sessionId = null; if (sessIdx > -1) { sessionId = encodedDefaultActionURL.substring(sessIdx); } String actionURL = request.getParameter(ACTIONURL_PARAM_NAME); if (null == actionURL) { actionURL = encodedDefaultActionURL; } else if (null != sessionId) { actionURL += sessionId; } WebProcessor.LOGGER.debug("actionURL: " + actionURL); // encode the URL to allow for session id rewriting actionURL = response.encodeURL(actionURL); return actionURL; } private void configureUpload() throws XFormsConfigException { //allow absolute paths otherwise resolve relative to the servlet context this.uploadDir = Config.getInstance().getProperty("uploadDir"); if (uploadDir == null) { throw new XFormsConfigException("upload dir is not set in betterform-config.xml"); } if (!new File(uploadDir).isAbsolute()) { uploadDir = WebFactory.getRealPath(uploadDir, getContext()); } setUploadDestination(new File(uploadDir).getAbsolutePath()); } private boolean noHttp() { if (this.request != null && this.response != null && this.httpSession != null) { return false; } else { return true; } } }