/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.filters;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.server.http.CauchoRequest;
import com.caucho.server.http.RequestAdapter;
import com.caucho.util.CompileException;
import com.caucho.util.L10N;
import com.caucho.vfs.MergePath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;
import com.caucho.xml.Xml;
import com.caucho.xml.XmlUtil;
import com.caucho.xpath.XPath;
import com.caucho.xpath.XPathException;
import com.caucho.xsl.AbstractStylesheetFactory;
import com.caucho.xsl.CauchoStylesheet;
import com.caucho.xsl.StyleScript;
import com.caucho.xsl.TransformerImpl;
import org.w3c.dom.Document;
import org.w3c.dom.ProcessingInstruction;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Sends the results of the servlet through XSLT.
*
* @since Resin 2.0.6
*/
public class XsltFilter implements Filter {
private static final L10N L = new L10N(XsltFilter.class);
private static final Logger log
= Logger.getLogger(XsltFilter.class.getName());
private MergePath _stylePath;
private ServletContext _application;
private boolean _isConditional = true;
public void setMimeType(String mimeType)
{
}
public void setUnconditional(boolean isUnconditional)
{
_isConditional = ! isUnconditional;
}
public void init(FilterConfig config)
throws ServletException
{
_stylePath = new MergePath();
_stylePath.addMergePath(Vfs.lookup());
DynamicClassLoader loader;
loader = (DynamicClassLoader) Thread.currentThread().getContextClassLoader();
String resourcePath = loader.getResourcePathSpecificFirst();
_stylePath.addClassPath(resourcePath);
_application = config.getServletContext();
if ("true".equals(config.getInitParameter("unconditional")))
_isConditional = false;
}
/**
* Creates a wrapper to compress the output.
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain nextFilter)
throws ServletException, IOException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
XsltResponse xsltResponse = new XsltResponse(req, res);
nextFilter.doFilter(req, xsltResponse);
xsltResponse.finish(req, res);
}
/**
* Any cleanup for the filter.
*/
public void destroy()
{
}
class XsltResponse extends CauchoResponseWrapper {
private HttpServletRequest _request;
private XsltTempStream _xsltStream;
private String _chainingType;
XsltResponse(HttpServletRequest request, HttpServletResponse response)
{
super(response);
_request = request;
}
/**
* This needs to be bypassed because the file's content
* length has nothing to do with the returned length.
*/
public void setContentLength(int length)
{
}
/**
* Sets the content type of the filter.
*/
public void setContentType(String contentType)
{
super.setContentType(contentType);
int p = contentType.indexOf(';');
if (p > 0)
contentType = contentType.substring(0, p);
if (! _isConditional ||
contentType.equals("x-application/xslt") ||
contentType.equals("x-application/xsl") ||
contentType.equals("x-application/stylescript")) {
_chainingType = contentType;
if (log.isLoggable(Level.FINER))
log.finer(L.l("'{0}' chaining xslt with {1}",
_request.getRequestURI(), contentType));
if (_xsltStream == null)
_xsltStream = new XsltTempStream(_response);
_xsltStream.setChaining();
}
}
/**
* Calculates and returns the proper stream.
*/
protected OutputStream getStream() throws IOException
{
if (_xsltStream == null)
_xsltStream = new XsltTempStream(_response);
return _xsltStream;
}
/**
* Flushes the stream's buffer.
*/
public void flushBuffer()
throws IOException
{
super.flushBuffer();
if (_stream != null)
_stream.flush();
}
/**
* Complets the request.
*/
public void finish(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException
{
try {
flushBuffer();
if (_chainingType == null)
return;
TempStream ts = _xsltStream.getTempStream();
Document doc = null;
ReadStream is = ts.openRead();
Path userPath = Vfs.lookup();
if (req instanceof CauchoRequest)
userPath.setUserPath(((CauchoRequest) req).getPageURI());
else
userPath.setUserPath(req.getRequestURI());
is.setPath(userPath);
try {
doc = new Xml().parseDocument(is);
} finally {
is.close();
}
String href = (String) req.getAttribute("caucho.xsl.stylesheet");
if (href == null)
href = getStylesheetHref(doc);
if (href == null)
href = "default.xsl";
Templates stylesheet = null;
//Path path = Vfs.lookup(href);
try {
//ReadStream sis = path.openReadAndSaveBuffer();
TransformerFactory factory;
if (_chainingType.equals("x-application/stylescript"))
factory = new StyleScript();
else {
factory = TransformerFactory.newInstance();
}
if (factory instanceof AbstractStylesheetFactory)
((AbstractStylesheetFactory) factory).setStylePath(_stylePath);
Path path = null;
if (href.startsWith("/"))
path = Vfs.getPwd().lookup(_application.getRealPath(href));
else {
String servletPath = RequestAdapter.getPageServletPath(req);
Path pwd = Vfs.getPwd();
pwd = pwd.lookup(_application.getRealPath(servletPath));
path = pwd.getParent().lookup(href);
}
if (! path.canRead()) {
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
URL url = loader.getResource(href);
if (url != null) {
Path newPath = Vfs.getPwd().lookup(url.toString());
if (newPath.canRead())
path = newPath;
}
}
Source source;
if (path.canRead())
source = new StreamSource(path.getURL());
else
source = new StreamSource(href);
if (log.isLoggable(Level.FINE))
log.fine(L.l("'{0}' XSLT filter using stylesheet {1}",
req.getRequestURI(), source.getSystemId()));
stylesheet = factory.newTemplates(source);
} finally {
// is.close();
}
Transformer transformer = null;
transformer = (Transformer) stylesheet.newTransformer();
TransformerImpl cauchoTransformer = null;
if (transformer instanceof TransformerImpl)
cauchoTransformer = (TransformerImpl) transformer;
String mediaType = (String) transformer.getOutputProperty(OutputKeys.MEDIA_TYPE);
String encoding = (String) transformer.getOutputProperty(OutputKeys.ENCODING);
String method = (String) transformer.getOutputProperty(OutputKeys.METHOD);
if (encoding != null) {
}
else if (method == null) {
}
else if (method.equals("xml"))
encoding = "UTF-8";
if (encoding != null) {
if (mediaType == null)
mediaType = "text/html";
res.setContentType(mediaType + "; charset=" + encoding);
}
else if (mediaType != null)
res.setContentType(mediaType);
else
res.setContentType("text/html");
if (encoding == null)
encoding = "ISO-8859-1";
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
ArrayList<?> params = null;;
if (cauchoTransformer != null) {
params = (ArrayList<?>) cauchoTransformer.getProperty(CauchoStylesheet.GLOBAL_PARAM);
}
for (int i = 0; params != null && i < params.size(); i++) {
String param = (String) params.get(i);
transformer.setParameter(param, req.getParameter(param));
}
DOMSource domSource = new DOMSource(doc);
domSource.setSystemId(userPath.getUserPath());
Result result = getResult(res.getOutputStream());
transformer.transform(domSource, result);
} catch (IOException e) {
throw e;
} catch (Exception e) {
if (e instanceof CompileException)
throw new ServletException(e.getMessage(), e);
else
throw new ServletException(e.toString(), e);
}
}
/**
* Returns the result object.
*/
protected Result getResult(OutputStream out)
{
return new StreamResult(out);
}
/**
* Returns the stylesheet specified by the page.
*
* The syntax is:
* <pre>
* <?xml-stylesheet href='...'?>
* </pre>
*
* @return the href of the xml-stylesheet processing-instruction or
* "default.xsl" if none is found.
*/
private String getStylesheetHref(Document doc)
throws XPathException
{
ProcessingInstruction pi = null;
pi = (ProcessingInstruction) XPath.find("//processing-instruction('xml-stylesheet')", doc);
if (pi == null)
return null;
String value = pi.getNodeValue();
return XmlUtil.getPIAttribute(value, "href");
}
}
static class XsltTempStream extends OutputStream {
private ServletResponse _response;
private OutputStream _os;
private TempStream _tempStream;
XsltTempStream(ServletResponse response)
{
_response = response;
}
void setChaining()
{
if (_os != null)
throw new IllegalStateException(L.l("setContentType for XSLT chaining must be before any data."));
_tempStream = new TempStream();
_tempStream.openWrite();
_os = new WriteStream(_tempStream);
}
TempStream getTempStream()
throws IOException
{
if (_tempStream != null) {
_os.close();
_os = null;
}
return _tempStream;
}
/**
* Writes a buffer to the underlying stream.
*
* @param ch the byte to write
*/
public void write(int ch)
throws IOException
{
if (_os == null)
_os = _response.getOutputStream();
_os.write(ch);
}
/**
* Writes a buffer to the underlying stream.
*
* @param buffer the byte array to write.
* @param offset the offset into the byte array.
* @param length the number of bytes to write.
*/
public void write(byte []buffer, int offset, int length)
throws IOException
{
if (_os == null)
_os = _response.getOutputStream();
_os.write(buffer, offset, length);
}
public void flush()
throws IOException
{
if (_os == null)
_os = _response.getOutputStream();
_os.flush();
}
}
}