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.FileNotFoundException;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.io.Reader;
045: import java.io.StringReader;
046:
047: import javax.servlet.http.HttpServletRequest;
048: import javax.servlet.jsp.JspException;
049: import javax.servlet.jsp.JspTagException;
050: import javax.servlet.jsp.PageContext;
051: import javax.servlet.jsp.tagext.BodyTagSupport;
052: import javax.xml.parsers.DocumentBuilder;
053: import javax.xml.parsers.DocumentBuilderFactory;
054: import javax.xml.parsers.ParserConfigurationException;
055: import javax.xml.transform.TransformerConfigurationException;
056: import javax.xml.transform.TransformerFactory;
057: import javax.xml.transform.dom.DOMResult;
058: import javax.xml.transform.sax.SAXTransformerFactory;
059: import javax.xml.transform.sax.TransformerHandler;
060:
061: import org.apache.taglibs.standard.resources.Resources;
062: import org.apache.taglibs.standard.tag.common.core.ImportSupport;
063: import org.apache.taglibs.standard.tag.common.core.Util;
064: import org.w3c.dom.Document;
065: import org.xml.sax.EntityResolver;
066: import org.xml.sax.InputSource;
067: import org.xml.sax.SAXException;
068: import org.xml.sax.XMLFilter;
069: import org.xml.sax.XMLReader;
070: import org.xml.sax.helpers.XMLReaderFactory;
071:
072: /**
073: * <p>Support for tag handlers for <parse>, the XML parsing tag.</p>
074: *
075: * @author Shawn Bayern
076: */
077: public abstract class ParseSupport extends BodyTagSupport {
078:
079: //*********************************************************************
080: // Protected state
081:
082: protected Object xml; // 'xml' attribute
083: protected String systemId; // 'systemId' attribute
084: protected XMLFilter filter; // 'filter' attribute
085:
086: //*********************************************************************
087: // Private state
088:
089: private String var; // 'var' attribute
090: private String varDom; // 'varDom' attribute
091: private int scope; // processed 'scope' attr
092: private int scopeDom; // processed 'scopeDom' attr
093:
094: // state in support of XML parsing...
095: private DocumentBuilderFactory dbf;
096: private DocumentBuilder db;
097: private TransformerFactory tf;
098: private TransformerHandler th;
099:
100: //*********************************************************************
101: // Constructor and initialization
102:
103: public ParseSupport() {
104: super ();
105: init();
106: }
107:
108: private void init() {
109: var = varDom = null;
110: xml = null;
111: systemId = null;
112: filter = null;
113: dbf = null;
114: db = null;
115: tf = null;
116: th = null;
117: scope = PageContext.PAGE_SCOPE;
118: scopeDom = PageContext.PAGE_SCOPE;
119: }
120:
121: //*********************************************************************
122: // Tag logic
123:
124: // parse 'source' or body, storing result in 'var'
125: public int doEndTag() throws JspException {
126: try {
127:
128: // set up our DocumentBuilder
129: if (dbf == null) {
130: dbf = DocumentBuilderFactory.newInstance();
131: dbf.setNamespaceAware(true);
132: dbf.setValidating(false);
133: }
134: db = dbf.newDocumentBuilder();
135:
136: // if we've gotten a filter, set up a transformer to support it
137: if (filter != null) {
138: if (tf == null)
139: tf = TransformerFactory.newInstance();
140: if (!tf.getFeature(SAXTransformerFactory.FEATURE))
141: throw new JspTagException(Resources
142: .getMessage("PARSE_NO_SAXTRANSFORMER"));
143: SAXTransformerFactory stf = (SAXTransformerFactory) tf;
144: th = stf.newTransformerHandler();
145: }
146:
147: // produce a Document by parsing whatever the attributes tell us to use
148: Document d;
149: Object xmlText = this .xml;
150: if (xmlText == null) {
151: // if the attribute was specified, use the body as 'xml'
152: if (bodyContent != null
153: && bodyContent.getString() != null)
154: xmlText = bodyContent.getString().trim();
155: else
156: xmlText = "";
157: }
158: if (xmlText instanceof String)
159: d = parseStringWithFilter((String) xmlText, filter);
160: else if (xmlText instanceof Reader)
161: d = parseReaderWithFilter((Reader) xmlText, filter);
162: else
163: throw new JspTagException(Resources
164: .getMessage("PARSE_INVALID_SOURCE"));
165:
166: // we've got a Document object; store it out as appropriate
167: // (let any exclusivity or other constraints be enforced by TEI/TLV)
168: if (var != null)
169: pageContext.setAttribute(var, d, scope);
170: if (varDom != null)
171: pageContext.setAttribute(varDom, d, scopeDom);
172:
173: return EVAL_PAGE;
174: } catch (SAXException ex) {
175: throw new JspException(ex);
176: } catch (IOException ex) {
177: throw new JspException(ex);
178: } catch (ParserConfigurationException ex) {
179: throw new JspException(ex);
180: } catch (TransformerConfigurationException ex) {
181: throw new JspException(ex);
182: }
183: }
184:
185: // Releases any resources we may have (or inherit)
186: public void release() {
187: init();
188: }
189:
190: //*********************************************************************
191: // Private utility methods
192:
193: /** Parses the given InputSource after, applying the given XMLFilter. */
194: private Document parseInputSourceWithFilter(InputSource s,
195: XMLFilter f) throws SAXException, IOException {
196: if (f != null) {
197: // prepare an output Document
198: Document o = db.newDocument();
199:
200: // use TrAX to adapt SAX events to a Document object
201: th.setResult(new DOMResult(o));
202: XMLReader xr = XMLReaderFactory.createXMLReader();
203: xr.setEntityResolver(new JstlEntityResolver(pageContext));
204: // (note that we overwrite the filter's parent. this seems
205: // to be expected usage. we could cache and reset the old
206: // parent, but you can't setParent(null), so this wouldn't
207: // be perfect.)
208: f.setParent(xr);
209: f.setContentHandler(th);
210: f.parse(s);
211: return o;
212: } else
213: return parseInputSource(s);
214: }
215:
216: /** Parses the given Reader after applying the given XMLFilter. */
217: private Document parseReaderWithFilter(Reader r, XMLFilter f)
218: throws SAXException, IOException {
219: return parseInputSourceWithFilter(new InputSource(r), f);
220: }
221:
222: /** Parses the given String after applying the given XMLFilter. */
223: private Document parseStringWithFilter(String s, XMLFilter f)
224: throws SAXException, IOException {
225: StringReader r = new StringReader(s);
226: return parseReaderWithFilter(r, f);
227: }
228:
229: /** Parses the given Reader after applying the given XMLFilter. */
230: private Document parseURLWithFilter(String url, XMLFilter f)
231: throws SAXException, IOException {
232: return parseInputSourceWithFilter(new InputSource(url), f);
233: }
234:
235: /** Parses the given InputSource into a Document. */
236: private Document parseInputSource(InputSource s)
237: throws SAXException, IOException {
238: db.setEntityResolver(new JstlEntityResolver(pageContext));
239:
240: // normalize URIs so they can be processed consistently by resolver
241: if (systemId == null)
242: s.setSystemId("jstl:");
243: else if (ImportSupport.isAbsoluteUrl(systemId))
244: s.setSystemId(systemId);
245: else
246: s.setSystemId("jstl:" + systemId);
247: return db.parse(s);
248: }
249:
250: /** Parses the given Reader into a Document. */
251: private Document parseReader(Reader r) throws SAXException,
252: IOException {
253: return parseInputSource(new InputSource(r));
254: }
255:
256: /** Parses the given String into a Document. */
257: private Document parseString(String s) throws SAXException,
258: IOException {
259: StringReader r = new StringReader(s);
260: return parseReader(r);
261: }
262:
263: /** Parses the URL (passed as a String) into a Document. */
264: private Document parseURL(String url) throws SAXException,
265: IOException {
266: return parseInputSource(new InputSource(url));
267: }
268:
269: //*********************************************************************
270: // JSTL-specific EntityResolver class
271:
272: /** Lets us resolve relative external entities. */
273: public static class JstlEntityResolver implements EntityResolver {
274: private final PageContext ctx;
275:
276: public JstlEntityResolver(PageContext ctx) {
277: this .ctx = ctx;
278: }
279:
280: public InputSource resolveEntity(String publicId,
281: String systemId) throws FileNotFoundException {
282:
283: // pass if we don't have a systemId
284: if (systemId == null)
285: return null;
286:
287: // strip leading "jstl:" off URL if applicable
288: if (systemId.startsWith("jstl:"))
289: systemId = systemId.substring(5);
290:
291: // we're only concerned with relative URLs
292: if (ImportSupport.isAbsoluteUrl(systemId))
293: return null;
294:
295: // for relative URLs, load and wrap the resource.
296: // don't bother checking for 'null' since we specifically want
297: // the parser to fail if the resource doesn't exist
298: InputStream s;
299: if (systemId.startsWith("/")) {
300: s = ctx.getServletContext().getResourceAsStream(
301: systemId);
302: if (s == null)
303: throw new FileNotFoundException(Resources
304: .getMessage("UNABLE_TO_RESOLVE_ENTITY",
305: systemId));
306: } else {
307: String pagePath = ((HttpServletRequest) ctx
308: .getRequest()).getServletPath();
309: String basePath = pagePath.substring(0, pagePath
310: .lastIndexOf("/"));
311: s = ctx.getServletContext().getResourceAsStream(
312: basePath + "/" + systemId);
313: if (s == null)
314: throw new FileNotFoundException(Resources
315: .getMessage("UNABLE_TO_RESOLVE_ENTITY",
316: systemId));
317: }
318: return new InputSource(s);
319: }
320: }
321:
322: //*********************************************************************
323: // Tag attributes
324:
325: public void setVar(String var) {
326: this .var = var;
327: }
328:
329: public void setVarDom(String varDom) {
330: this .varDom = varDom;
331: }
332:
333: public void setScope(String scope) {
334: this .scope = Util.getScope(scope);
335: }
336:
337: public void setScopeDom(String scopeDom) {
338: this.scopeDom = Util.getScope(scopeDom);
339: }
340: }
|