001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.filters;
030:
031: import com.caucho.loader.DynamicClassLoader;
032: import com.caucho.log.Log;
033: import com.caucho.server.connection.CauchoRequest;
034: import com.caucho.server.connection.RequestAdapter;
035: import com.caucho.util.CompileException;
036: import com.caucho.util.L10N;
037: import com.caucho.vfs.MergePath;
038: import com.caucho.vfs.Path;
039: import com.caucho.vfs.ReadStream;
040: import com.caucho.vfs.TempStream;
041: import com.caucho.vfs.Vfs;
042: import com.caucho.vfs.WriteStream;
043: import com.caucho.xml.Xml;
044: import com.caucho.xml.XmlUtil;
045: import com.caucho.xpath.XPath;
046: import com.caucho.xpath.XPathException;
047: import com.caucho.xsl.AbstractStylesheetFactory;
048: import com.caucho.xsl.CauchoStylesheet;
049: import com.caucho.xsl.StyleScript;
050: import com.caucho.xsl.TransformerImpl;
051:
052: import org.w3c.dom.Document;
053: import org.w3c.dom.ProcessingInstruction;
054:
055: import javax.servlet.*;
056: import javax.servlet.http.HttpServletRequest;
057: import javax.servlet.http.HttpServletResponse;
058: import javax.xml.transform.OutputKeys;
059: import javax.xml.transform.Result;
060: import javax.xml.transform.Source;
061: import javax.xml.transform.Templates;
062: import javax.xml.transform.Transformer;
063: import javax.xml.transform.TransformerFactory;
064: import javax.xml.transform.dom.DOMSource;
065: import javax.xml.transform.stream.StreamResult;
066: import javax.xml.transform.stream.StreamSource;
067: import java.io.IOException;
068: import java.io.OutputStream;
069: import java.net.URL;
070: import java.util.ArrayList;
071: import java.util.logging.Level;
072: import java.util.logging.Logger;
073:
074: /**
075: * Sends the results of the servlet through XSLT.
076: *
077: * @since Resin 2.0.6
078: */
079: public class XsltFilter implements Filter {
080: private static final L10N L = new L10N(XsltFilter.class);
081: private static final Logger log = Log.open(XsltFilter.class);
082:
083: private String _mimeType = "x-application/xslt";
084: private MergePath _stylePath;
085: private ServletContext _application;
086: private boolean _isConditional = true;
087:
088: public void setMimeType(String mimeType) {
089: _mimeType = mimeType;
090: }
091:
092: public void setUnconditional(boolean isUnconditional) {
093: _isConditional = !isUnconditional;
094: }
095:
096: public void init(FilterConfig config) throws ServletException {
097: _stylePath = new MergePath();
098: _stylePath.addMergePath(Vfs.lookup());
099: DynamicClassLoader loader;
100: loader = (DynamicClassLoader) Thread.currentThread()
101: .getContextClassLoader();
102: String resourcePath = loader.getResourcePathSpecificFirst();
103: _stylePath.addClassPath(resourcePath);
104:
105: _application = config.getServletContext();
106:
107: if ("true".equals(config.getInitParameter("unconditional")))
108: _isConditional = false;
109: }
110:
111: /**
112: * Creates a wrapper to compress the output.
113: */
114: public void doFilter(ServletRequest request,
115: ServletResponse response, FilterChain nextFilter)
116: throws ServletException, IOException {
117: HttpServletRequest req = (HttpServletRequest) request;
118: HttpServletResponse res = (HttpServletResponse) response;
119:
120: XsltResponse xsltResponse = new XsltResponse(req, res);
121:
122: nextFilter.doFilter(req, xsltResponse);
123: xsltResponse.finish(req, res);
124: }
125:
126: /**
127: * Any cleanup for the filter.
128: */
129: public void destroy() {
130: }
131:
132: class XsltResponse extends CauchoResponseWrapper {
133: private HttpServletRequest _request;
134: private XsltTempStream _xsltStream;
135: private String _chainingType;
136:
137: XsltResponse(HttpServletRequest request,
138: HttpServletResponse response) {
139: super (response);
140:
141: _request = request;
142: }
143:
144: /**
145: * This needs to be bypassed because the file's content
146: * length has nothing to do with the returned length.
147: */
148: public void setContentLength(int length) {
149: }
150:
151: /**
152: * Sets the content type of the filter.
153: */
154: public void setContentType(String contentType) {
155: super .setContentType(contentType);
156:
157: int p = contentType.indexOf(';');
158:
159: if (p > 0)
160: contentType = contentType.substring(0, p);
161:
162: if (!_isConditional
163: || contentType.equals("x-application/xslt")
164: || contentType.equals("x-application/xsl")
165: || contentType.equals("x-application/stylescript")) {
166: _chainingType = contentType;
167:
168: if (log.isLoggable(Level.FINER))
169: log.finer(L.l("'{0}' chaining xslt with {1}",
170: _request.getRequestURI(), contentType));
171:
172: if (_xsltStream == null)
173: _xsltStream = new XsltTempStream(_response);
174:
175: _xsltStream.setChaining();
176: }
177: }
178:
179: /**
180: * Calculates and returns the proper stream.
181: */
182: protected OutputStream getStream() throws IOException {
183: if (_xsltStream == null)
184: _xsltStream = new XsltTempStream(_response);
185:
186: return _xsltStream;
187: }
188:
189: /**
190: * Flushes the stream's buffer.
191: */
192: public void flushBuffer() throws IOException {
193: super .flushBuffer();
194:
195: if (_stream != null)
196: _stream.flush();
197: }
198:
199: /**
200: * Complets the request.
201: */
202: public void finish(HttpServletRequest req,
203: HttpServletResponse res) throws IOException,
204: ServletException {
205: try {
206: flushBuffer();
207:
208: if (_chainingType == null)
209: return;
210:
211: TempStream ts = _xsltStream.getTempStream();
212:
213: Document doc = null;
214:
215: ReadStream is = ts.openRead();
216: Path userPath = Vfs.lookup();
217: if (req instanceof CauchoRequest)
218: userPath.setUserPath(((CauchoRequest) req)
219: .getPageURI());
220: else
221: userPath.setUserPath(req.getRequestURI());
222: is.setPath(userPath);
223:
224: try {
225: doc = new Xml().parseDocument(is);
226: } finally {
227: is.close();
228: }
229:
230: String href = (String) req
231: .getAttribute("caucho.xsl.stylesheet");
232:
233: if (href == null)
234: href = getStylesheetHref(doc);
235:
236: if (href == null)
237: href = "default.xsl";
238:
239: Templates stylesheet = null;
240:
241: //Path path = Vfs.lookup(href);
242: try {
243: //ReadStream sis = path.openReadAndSaveBuffer();
244:
245: TransformerFactory factory;
246:
247: if (_chainingType
248: .equals("x-application/stylescript"))
249: factory = new StyleScript();
250: else {
251: factory = TransformerFactory.newInstance();
252: }
253:
254: if (factory instanceof AbstractStylesheetFactory)
255: ((AbstractStylesheetFactory) factory)
256: .setStylePath(_stylePath);
257:
258: Path path = null;
259:
260: if (href.startsWith("/"))
261: path = Vfs.getPwd().lookup(
262: _application.getRealPath(href));
263: else {
264: String servletPath = RequestAdapter
265: .getPageServletPath(req);
266:
267: Path pwd = Vfs.getPwd();
268: pwd = pwd.lookup(_application
269: .getRealPath(servletPath));
270: path = pwd.getParent().lookup(href);
271: }
272:
273: if (!path.canRead()) {
274: Thread thread = Thread.currentThread();
275: ClassLoader loader = thread
276: .getContextClassLoader();
277:
278: URL url = loader.getResource(href);
279:
280: if (url != null) {
281: Path newPath = Vfs.getPwd().lookup(
282: url.toString());
283: if (newPath.canRead())
284: path = newPath;
285: }
286: }
287:
288: Source source;
289: if (path.canRead())
290: source = new StreamSource(path.getURL());
291: else
292: source = new StreamSource(href);
293:
294: if (log.isLoggable(Level.FINE))
295: log
296: .fine(L
297: .l(
298: "'{0}' XSLT filter using stylesheet {1}",
299: req.getRequestURI(),
300: source.getSystemId()));
301:
302: stylesheet = factory.newTemplates(source);
303: } finally {
304: // is.close();
305: }
306:
307: Transformer transformer = null;
308:
309: transformer = (Transformer) stylesheet.newTransformer();
310:
311: TransformerImpl cauchoTransformer = null;
312: if (transformer instanceof TransformerImpl)
313: cauchoTransformer = (TransformerImpl) transformer;
314:
315: String mediaType = (String) transformer
316: .getOutputProperty(OutputKeys.MEDIA_TYPE);
317: String encoding = (String) transformer
318: .getOutputProperty(OutputKeys.ENCODING);
319: String method = (String) transformer
320: .getOutputProperty(OutputKeys.METHOD);
321:
322: if (encoding != null) {
323: } else if (method == null) {
324: } else if (method.equals("xml"))
325: encoding = "UTF-8";
326:
327: if (encoding != null) {
328: if (mediaType == null)
329: mediaType = "text/html";
330: res.setContentType(mediaType + "; charset="
331: + encoding);
332: } else if (mediaType != null)
333: res.setContentType(mediaType);
334: else
335: res.setContentType("text/html");
336:
337: if (encoding == null)
338: encoding = "ISO-8859-1";
339: transformer.setOutputProperty(OutputKeys.ENCODING,
340: encoding);
341:
342: ArrayList params = null;
343: ;
344: if (cauchoTransformer != null) {
345: params = (ArrayList) cauchoTransformer
346: .getProperty(CauchoStylesheet.GLOBAL_PARAM);
347: }
348:
349: for (int i = 0; params != null && i < params.size(); i++) {
350: String param = (String) params.get(i);
351:
352: transformer.setParameter(param, req
353: .getParameter(param));
354: }
355:
356: DOMSource domSource = new DOMSource(doc);
357: domSource.setSystemId(userPath.getUserPath());
358:
359: Result result = getResult(res.getOutputStream());
360:
361: transformer.transform(domSource, result);
362: } catch (IOException e) {
363: throw e;
364: } catch (Exception e) {
365: if (e instanceof CompileException)
366: throw new ServletException(e.getMessage(), e);
367: else
368: throw new ServletException(e.toString(), e);
369: }
370: }
371:
372: /**
373: * Returns the result object.
374: */
375: protected Result getResult(OutputStream out) {
376: return new StreamResult(out);
377: }
378:
379: /**
380: * Returns the stylesheet specified by the page.
381: *
382: * The syntax is:
383: * <pre>
384: * <?xml-stylesheet href='...'?>
385: * </pre>
386: *
387: * @return the href of the xml-stylesheet processing-instruction or
388: * "default.xsl" if none is found.
389: */
390: private String getStylesheetHref(Document doc)
391: throws XPathException {
392: ProcessingInstruction pi = null;
393:
394: pi = (ProcessingInstruction) XPath.find(
395: "//processing-instruction('xml-stylesheet')", doc);
396:
397: if (pi == null)
398: return null;
399:
400: String value = pi.getNodeValue();
401:
402: return XmlUtil.getPIAttribute(value, "href");
403: }
404: }
405:
406: static class XsltTempStream extends OutputStream {
407: private ServletResponse _response;
408: private OutputStream _os;
409:
410: private TempStream _tempStream;
411:
412: XsltTempStream(ServletResponse response) {
413: _response = response;
414: }
415:
416: void setChaining() {
417: if (_os != null)
418: throw new IllegalStateException(
419: L
420: .l("setContentType for XSLT chaining must be before any data."));
421:
422: _tempStream = new TempStream();
423: _tempStream.openWrite();
424:
425: _os = new WriteStream(_tempStream);
426: }
427:
428: TempStream getTempStream() throws IOException {
429: if (_tempStream != null) {
430: _os.close();
431: _os = null;
432: }
433:
434: return _tempStream;
435: }
436:
437: /**
438: * Writes a buffer to the underlying stream.
439: *
440: * @param ch the byte to write
441: */
442: public void write(int ch) throws IOException {
443: if (_os == null)
444: _os = _response.getOutputStream();
445:
446: _os.write(ch);
447: }
448:
449: /**
450: * Writes a buffer to the underlying stream.
451: *
452: * @param buffer the byte array to write.
453: * @param offset the offset into the byte array.
454: * @param length the number of bytes to write.
455: */
456: public void write(byte[] buffer, int offset, int length)
457: throws IOException {
458: if (_os == null)
459: _os = _response.getOutputStream();
460:
461: _os.write(buffer, offset, length);
462: }
463:
464: public void flush() throws IOException {
465: if (_os == null)
466: _os = _response.getOutputStream();
467:
468: _os.flush();
469: }
470: }
471: }
|