/** * Copyright 2010 Marko Lavikainen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package net.contextfw.web.application.internal; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import net.contextfw.org.dom4j.io.XMLWriter; import net.contextfw.web.application.DocumentProcessor; import net.contextfw.web.application.WebApplicationException; import net.contextfw.web.application.configuration.Configuration; import net.contextfw.web.application.development.XMLResponseLogger; import net.contextfw.web.application.internal.configuration.KeyValue; import net.contextfw.web.application.internal.util.ResourceEntry; import net.contextfw.web.application.internal.util.ResourceScanner; import net.contextfw.web.application.internal.util.Utils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.HTMLWriter; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; @Singleton public class WebResponder { private static final Pattern XSL_ACCEPTOR = Pattern.compile(".*\\.xsl", Pattern.CASE_INSENSITIVE); private Logger logger = LoggerFactory.getLogger(WebResponder.class); private List<String> rootResourcePaths = new ArrayList<String>(); private final List<String> resourcePaths = new ArrayList<String>(); private final List<KeyValue<String, String>> namespaces = new ArrayList<KeyValue<String, String>>(); private final Transformers transformers; private final XMLResponseLogger responseLogger; private final DocumentProcessor xslPostProcessor; private final OutputFormat htmlFormat; public enum Mode { INIT("text/html;charset=UTF-8"), UPDATE("text/xml;charset=UTF-8"), XML("text/xml;charset=UTF-8"); private final String contentType; private Mode(String contentType) { this.contentType = contentType; } public String getContentType() { return contentType; } } @SuppressWarnings("unchecked") @Inject public WebResponder(Configuration configuration, Injector injector) { rootResourcePaths.add("net.contextfw.web.application"); transformers = new Transformers(); resourcePaths.addAll(configuration.get(Configuration.RESOURCE_PATH)); namespaces.addAll(configuration.get(Configuration.NAMESPACE)); htmlFormat = OutputFormat.createCompactFormat(); htmlFormat.setXHTML(true); htmlFormat.setTrimText(false); htmlFormat.setPadText(true); htmlFormat.setNewlines(false); htmlFormat.setExpandEmptyElements(true); if (configuration.get(Configuration.XSL_POST_PROCESSOR) != null) { xslPostProcessor = Utils.toInstance( configuration.get(Configuration.XSL_POST_PROCESSOR), injector); } else { xslPostProcessor = null; } if (configuration.get(Configuration.LOG_XML)) { Object obj = configuration.get(Configuration.XML_RESPONSE_LOGGER); if (obj instanceof XMLResponseLogger) { responseLogger = (XMLResponseLogger) obj; } else if (obj instanceof Class && XMLResponseLogger.class.isAssignableFrom((Class<?>) obj)) { responseLogger = injector.getInstance((Class<XMLResponseLogger>) obj); } else { responseLogger = null; } } else { responseLogger = null; } } public void logXML(Document d) { try { OutputFormat format = OutputFormat.createPrettyPrint(); XMLWriter writer; StringWriter xml = new StringWriter(); writer = new XMLWriter(xml, format); writer.write(d); responseLogger.logXML(xml.toString()); } catch (Exception e) { throw new WebApplicationException(e); } } protected Document getXSLDocument() { List<ResourceEntry> rootResources = ResourceScanner.findResources( rootResourcePaths, XSL_ACCEPTOR); ResourceEntry root = null; Iterator<ResourceEntry> iter = rootResources.iterator(); if (iter != null) { while (iter.hasNext()) { ResourceEntry next = iter.next(); if (next.getPath().endsWith("root.xsl")) { iter.remove(); root = next; break; } } } if (root == null) { throw new InternalWebApplicationException("root.xsl was not found"); } List<ResourceEntry> resources = ResourceScanner.findResources(resourcePaths, XSL_ACCEPTOR); InputStream stream; SAXReader reader = new SAXReader(); try { stream = root.getInputStream(); Document document = reader.read(stream); stream.close(); for (KeyValue<String, String> entry : namespaces) { document.getRootElement().addNamespace(entry.getKey(), entry.getValue()); } Element stylesheet = (Element) document .selectSingleNode("//stylesheet"); // Adding other stylesheets for (ResourceEntry file : resources) { if (file.getPath().endsWith(".xsl")) { reader = new SAXReader(); stream = file.getInputStream(); try { Document child = reader.read(stream); for (Object el : child.getRootElement().elements()) { if (el instanceof Node) { stylesheet.add(((Node) el).detach()); } } } catch (DocumentException de) { transformers.invalidate(); throw new WebApplicationException("Xsl-file " + file.getPath() + " contains errors", de); } finally { stream.close(); } } } if (xslPostProcessor != null) { xslPostProcessor.process(document); } return document; } catch (DocumentException e) { throw new WebApplicationException(e); } catch (UnsupportedEncodingException e) { throw new WebApplicationException(e); } catch (IOException e) { throw new WebApplicationException(e); } } public void sendResponse(Document document, HttpServletResponse resp, Mode mode) throws ServletException, IOException { if (responseLogger != null) { logXML(document); } if (mode != Mode.XML) { sendHTMLResponse(document, resp, mode); } else { sendXMLResponse(document, resp); } } private void sendXMLResponse(Document document, HttpServletResponse resp) throws IOException { resp.setContentType(Mode.XML.getContentType()); resp.setHeader("Expires", "-1"); resp.setHeader("Pragma", "no-cache"); resp.setHeader("Cache-Control", "no-cache, no-store"); OutputFormat format = OutputFormat.createPrettyPrint(); new XMLWriter(resp.getWriter(), format).write(document); } public void sendHTMLResponse(Document document, HttpServletResponse resp, Mode mode) throws ServletException, IOException { resp.setContentType(mode.getContentType()); resp.setHeader("Expires", "-1"); resp.setHeader("Pragma", "no-cache"); resp.setHeader("Cache-Control", "no-cache, no-store"); if (!transformers.isInitialized()) { synchronized (transformers) { if (!transformers.isInitialized()) { transformers.initialize(getXSLDocument()); } } } Document rDocument = transformers.transform(document); if (mode == Mode.INIT) { rDocument.addDocType( "html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"); } new HTMLWriter(resp.getWriter(), htmlFormat).write(rDocument); } public void clean() { logger.debug("Reloading resources"); transformers.invalidate(); } }