001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Portions Copyright Apache Software Foundation.
007: *
008: * The contents of this file are subject to the terms of either the GNU
009: * General Public License Version 2 only ("GPL") or the Common Development
010: * and Distribution License("CDDL") (collectively, the "License"). You
011: * may not use this file except in compliance with the License. You can obtain
012: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
013: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
014: * language governing permissions and limitations under the License.
015: *
016: * When distributing the software, include this License Header Notice in each
017: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
018: * Sun designates this particular file as subject to the "Classpath" exception
019: * as provided by Sun in the GPL Version 2 section of the License file that
020: * accompanied this code. If applicable, add the following below the License
021: * Header, with the fields enclosed by brackets [] replaced by your own
022: * identifying information: "Portions Copyrighted [year]
023: * [name of copyright owner]"
024: *
025: * Contributor(s):
026: *
027: * If you wish your version of this file to be governed by only the CDDL or
028: * only the GPL Version 2, indicate your decision by adding "[Contributor]
029: * elects to include this software in this distribution under the [CDDL or GPL
030: * Version 2] license." If you don't indicate a single choice of license, a
031: * recipient has the option to distribute your version of this file under
032: * either the CDDL, the GPL Version 2 or to extend the choice of license to
033: * its licensees as provided above. However, if you add GPL Version 2 code
034: * and therefore, elected the GPL Version 2 license, then the option applies
035: * only if the new code is made subject to such option by the copyright
036: * holder.
037: */
038:
039: package org.apache.taglibs.standard.tag.common.xml;
040:
041: import java.io.IOException;
042: import java.io.InputStream;
043: import java.io.Reader;
044: import java.io.StringReader;
045: import java.io.Writer;
046: import java.util.List;
047:
048: import javax.servlet.http.HttpServletRequest;
049: import javax.servlet.jsp.JspException;
050: import javax.servlet.jsp.JspTagException;
051: import javax.servlet.jsp.PageContext;
052: import javax.servlet.jsp.tagext.BodyTagSupport;
053: import javax.xml.parsers.DocumentBuilder;
054: import javax.xml.parsers.DocumentBuilderFactory;
055: import javax.xml.parsers.ParserConfigurationException;
056: import javax.xml.transform.Result;
057: import javax.xml.transform.Source;
058: import javax.xml.transform.Transformer;
059: import javax.xml.transform.TransformerConfigurationException;
060: import javax.xml.transform.TransformerException;
061: import javax.xml.transform.TransformerFactory;
062: import javax.xml.transform.URIResolver;
063: import javax.xml.transform.dom.DOMResult;
064: import javax.xml.transform.dom.DOMSource;
065: import javax.xml.transform.sax.SAXSource;
066: import javax.xml.transform.stream.StreamResult;
067: import javax.xml.transform.stream.StreamSource;
068:
069: import org.apache.taglibs.standard.resources.Resources;
070: import org.apache.taglibs.standard.tag.common.core.ImportSupport;
071: import org.apache.taglibs.standard.tag.common.core.Util;
072: import org.w3c.dom.Document;
073: import org.w3c.dom.Node;
074: import org.xml.sax.InputSource;
075: import org.xml.sax.SAXException;
076: import org.xml.sax.XMLReader;
077: import org.xml.sax.helpers.XMLReaderFactory;
078:
079: /**
080: * <p>Support for tag handlers for <transform>, the XML transformation
081: * tag.</p>
082: *
083: * @author Shawn Bayern
084: */
085: public abstract class TransformSupport extends BodyTagSupport {
086:
087: //*********************************************************************
088: // Protected state
089:
090: protected Object xml; // attribute
091: protected String xmlSystemId; // attribute
092: protected Object xslt; // attribute
093: protected String xsltSystemId; // attribute
094: protected Result result; // attribute
095:
096: //*********************************************************************
097: // Private state
098:
099: private String var; // 'var' attribute
100: private int scope; // processed 'scope' attr
101: private Transformer t; // actual Transformer
102: private TransformerFactory tf; // reusable factory
103: private DocumentBuilder db; // reusable factory
104: private DocumentBuilderFactory dbf; // reusable factory
105:
106: //*********************************************************************
107: // Constructor and initialization
108:
109: public TransformSupport() {
110: super ();
111: init();
112: }
113:
114: private void init() {
115: xml = xslt = null;
116: xmlSystemId = xsltSystemId = null;
117: var = null;
118: result = null;
119: tf = null;
120: scope = PageContext.PAGE_SCOPE;
121: }
122:
123: //*********************************************************************
124: // Tag logic
125:
126: public int doStartTag() throws JspException {
127: /*
128: * We can set up our Transformer here, so we do so, and we let
129: * it receive parameters directly from subtags (instead of
130: * caching them.
131: */
132: try {
133:
134: //************************************
135: // Initialize
136:
137: // set up our DocumentBuilderFactory if necessary
138: if (dbf == null) {
139: dbf = DocumentBuilderFactory.newInstance();
140: dbf.setNamespaceAware(true);
141: dbf.setValidating(false);
142: }
143: if (db == null)
144: db = dbf.newDocumentBuilder();
145:
146: // set up the TransformerFactory if necessary
147: if (tf == null)
148: tf = TransformerFactory.newInstance();
149:
150: //************************************
151: // Produce transformer
152:
153: Source s;
154: if (xslt != null) {
155: if (!(xslt instanceof String)
156: && !(xslt instanceof Reader)
157: && !(xslt instanceof javax.xml.transform.Source))
158: throw new JspTagException(Resources
159: .getMessage("TRANSFORM_XSLT_UNRECOGNIZED"));
160: s = getSource(xslt, xsltSystemId);
161: } else {
162: throw new JspTagException(Resources
163: .getMessage("TRANSFORM_NO_TRANSFORMER"));
164: }
165: tf.setURIResolver(new JstlUriResolver(pageContext));
166: t = tf.newTransformer(s);
167:
168: return EVAL_BODY_BUFFERED;
169:
170: } catch (SAXException ex) {
171: throw new JspException(ex);
172: } catch (ParserConfigurationException ex) {
173: throw new JspException(ex);
174: } catch (IOException ex) {
175: throw new JspException(ex);
176: } catch (TransformerConfigurationException ex) {
177: throw new JspException(ex);
178: }
179: }
180:
181: // parse 'xml' or body, transform via our Transformer,
182: // and store as 'var' or through 'result'
183: public int doEndTag() throws JspException {
184: try {
185:
186: //************************************
187: // Determine source XML
188:
189: // if we haven't gotten a source, use the body (which may be empty)
190: Object xml = this .xml;
191: if (xml == null) // still equal
192: if (bodyContent != null
193: && bodyContent.getString() != null)
194: xml = bodyContent.getString().trim();
195: else
196: xml = "";
197:
198: // let the Source be with you
199: Source source = getSource(xml, xmlSystemId);
200:
201: //************************************
202: // Conduct the transformation
203:
204: // we can assume at most one of 'var' or 'result' is specified
205: if (result != null)
206: // we can write directly to the Result
207: t.transform(source, result);
208: else if (var != null) {
209: // we need a Document
210: Document d = db.newDocument();
211: Result doc = new DOMResult(d);
212: t.transform(source, doc);
213: pageContext.setAttribute(var, d, scope);
214: } else {
215: Result page = new StreamResult(new SafeWriter(
216: pageContext.getOut()));
217: t.transform(source, page);
218: }
219:
220: return EVAL_PAGE;
221: } catch (SAXException ex) {
222: throw new JspException(ex);
223: } catch (ParserConfigurationException ex) {
224: throw new JspException(ex);
225: } catch (IOException ex) {
226: throw new JspException(ex);
227: } catch (TransformerException ex) {
228: throw new JspException(ex);
229: }
230: }
231:
232: // Releases any resources we may have (or inherit)
233: public void release() {
234: init();
235: }
236:
237: //*********************************************************************
238: // Public methods for subtags
239:
240: /** Sets (adds) a transformation parameter on our transformer. */
241: public void addParameter(String name, Object value) {
242: t.setParameter(name, value);
243: }
244:
245: //*********************************************************************
246: // Utility methods
247:
248: /**
249: * Wraps systemId with a "jstl:" prefix to prevent the parser from
250: * thinking that the URI is truly relative and resolving it against
251: * the current directory in the filesystem.
252: */
253: private static String wrapSystemId(String systemId) {
254: if (systemId == null)
255: return "jstl:";
256: else if (ImportSupport.isAbsoluteUrl(systemId))
257: return systemId;
258: else
259: return ("jstl:" + systemId);
260: }
261:
262: /**
263: * Retrieves a Source from the given Object, whether it be a String,
264: * Reader, Node, or other supported types (even a Source already).
265: * If 'url' is true, then we must be passed a String and will interpret
266: * it as a URL. A null input always results in a null output.
267: */
268: private Source getSource(Object o, String systemId)
269: throws SAXException, ParserConfigurationException,
270: IOException {
271: if (o == null)
272: return null;
273: else if (o instanceof Source) {
274: return (Source) o;
275: } else if (o instanceof String) {
276: // if we've got a string, chain to Reader below
277: return getSource(new StringReader((String) o), systemId);
278: } else if (o instanceof Reader) {
279: // explicitly go through SAX to maintain control
280: // over how relative external entities resolve
281: XMLReader xr = XMLReaderFactory.createXMLReader();
282: xr.setEntityResolver(new ParseSupport.JstlEntityResolver(
283: pageContext));
284: InputSource s = new InputSource((Reader) o);
285: s.setSystemId(wrapSystemId(systemId));
286: Source result = new SAXSource(xr, s);
287: result.setSystemId(wrapSystemId(systemId));
288: return result;
289: } else if (o instanceof Node) {
290: return new DOMSource((Node) o);
291: } else if (o instanceof List) {
292: // support 1-item List because our XPath processor outputs them
293: List l = (List) o;
294: if (l.size() == 1) {
295: return getSource(l.get(0), systemId); // unwrap List
296: } else {
297: throw new IllegalArgumentException(Resources
298: .getMessage("TRANSFORM_SOURCE_INVALID_LIST"));
299: }
300: } else {
301: throw new IllegalArgumentException(Resources
302: .getMessage("TRANSFORM_SOURCE_UNRECOGNIZED")
303: + o.getClass());
304: }
305: }
306:
307: //*********************************************************************
308: // Tag attributes
309:
310: public void setVar(String var) {
311: this .var = var;
312: }
313:
314: public void setScope(String scope) {
315: this .scope = Util.getScope(scope);
316: }
317:
318: //*********************************************************************
319: // Private utility classes
320:
321: /**
322: * A Writer based on a wrapped Writer but ignoring requests to
323: * close() and flush() it. (Someone must have wrapped the
324: * toilet in my office similarly...)
325: */
326: private static class SafeWriter extends Writer {
327: private Writer w;
328:
329: public SafeWriter(Writer w) {
330: this .w = w;
331: }
332:
333: public void close() {
334: }
335:
336: public void flush() {
337: }
338:
339: public void write(char[] cbuf, int off, int len)
340: throws IOException {
341: w.write(cbuf, off, len);
342: }
343: }
344:
345: //*********************************************************************
346: // JSTL-specific URIResolver class
347:
348: /** Lets us resolve relative external entities. */
349: private static class JstlUriResolver implements URIResolver {
350: private final PageContext ctx;
351:
352: public JstlUriResolver(PageContext ctx) {
353: this .ctx = ctx;
354: }
355:
356: public Source resolve(String href, String base)
357: throws TransformerException {
358:
359: // pass if we don't have a systemId
360: if (href == null)
361: return null;
362:
363: // remove "jstl" marker from 'base'
364: // NOTE: how 'base' is determined varies among different Xalan
365: // xsltc implementations
366: int index;
367: if (base != null && (index = base.indexOf("jstl:")) != -1) {
368: base = base.substring(index + 5);
369: }
370:
371: // we're only concerned with relative URLs
372: if (ImportSupport.isAbsoluteUrl(href)
373: || (base != null && ImportSupport
374: .isAbsoluteUrl(base)))
375: return null;
376:
377: // base is relative; remove everything after trailing '/'
378: if (base == null || base.lastIndexOf("/") == -1)
379: base = "";
380: else
381: base = base.substring(0, base.lastIndexOf("/") + 1);
382:
383: // concatenate to produce the real URL we're interested in
384: String target = base + href;
385:
386: // for relative URLs, load and wrap the resource.
387: // don't bother checking for 'null' since we specifically want
388: // the parser to fail if the resource doesn't exist
389: InputStream s;
390: if (target.startsWith("/")) {
391: s = ctx.getServletContext().getResourceAsStream(target);
392: if (s == null)
393: throw new TransformerException(Resources
394: .getMessage("UNABLE_TO_RESOLVE_ENTITY",
395: href));
396: } else {
397: String pagePath = ((HttpServletRequest) ctx
398: .getRequest()).getServletPath();
399: String basePath = pagePath.substring(0, pagePath
400: .lastIndexOf("/"));
401: s = ctx.getServletContext().getResourceAsStream(
402: basePath + "/" + target);
403: if (s == null)
404: throw new TransformerException(Resources
405: .getMessage("UNABLE_TO_RESOLVE_ENTITY",
406: href));
407: }
408: return new StreamSource(s);
409: }
410: }
411:
412: }
|