001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1999 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055: package com.rimfaxe.webserver.compiler.jsp;
056:
057: import java.io.InputStream;
058: import java.io.ByteArrayInputStream;
059: import java.io.CharArrayWriter;
060: import java.io.UnsupportedEncodingException;
061: import java.util.ListIterator;
062: import javax.servlet.jsp.tagext.PageData;
063: import org.xml.sax.Attributes;
064: import org.xml.sax.helpers.AttributesImpl;
065:
066: import com.rimfaxe.webserver.compiler.JspToJavaException;
067:
068: /**
069: * An implementation of <tt>javax.servlet.jsp.tagext.PageData</tt> which
070: * builds the XML view of a given page.
071: *
072: * The XML view is built in two passes:
073: *
074: * During the first pass, the FirstPassVisitor collects the attributes of the
075: * top-level jsp:root and those of the jsp:root elements of any included
076: * pages, and adds them to the jsp:root element of the XML view.
077: * In addition, any taglib directives are converted into xmlns: attributes and
078: * added to the jsp:root element of the XML view.
079: * This pass ignores any nodes other than JspRoot and TaglibDirective.
080: *
081: * During the second pass, the SecondPassVisitor produces the XML view, using
082: * the combined jsp:root attributes determined in the first pass and any
083: * remaining pages nodes (this pass ignores any JspRoot and TaglibDirective
084: * nodes).
085: *
086: * @author Jan Luehe
087: * @author Lars Andersen
088: */
089:
090: public class PageDataImpl extends PageData implements TagConstants {
091:
092: private static final String JSP_NAMESPACE = "http://java.sun.com/JSP/Page";
093: private static final String JSP_VERSION = "1.2";
094: private static final String CDATA_START_SECTION = "<![CDATA[\n";
095: private static final String CDATA_END_SECTION = "]]>\n";
096:
097: // default "xmlns:jsp" and "version" attributes of jsp:root element
098: private static AttributesImpl defaultJspRootAttrs;
099:
100: // string buffer used to build XML view
101: private StringBuffer buf;
102:
103: /*
104: * Static initializer which sets the "xmlns:jsp" and "version"
105: * attributes of the jsp:root element to their default values.
106: */
107: static {
108: defaultJspRootAttrs = new AttributesImpl();
109: defaultJspRootAttrs.addAttribute("", "", "xmlns:jsp", "CDATA",
110: JSP_NAMESPACE);
111: defaultJspRootAttrs.addAttribute("", "", "version", "CDATA",
112: JSP_VERSION);
113: }
114:
115: /**
116: * Constructor.
117: *
118: * @param page the page nodes from which to generate the XML view
119: */
120: public PageDataImpl(Node.Nodes page) throws JasperException,
121: JspToJavaException {
122:
123: // First pass
124: FirstPassVisitor firstPassVisitor = new FirstPassVisitor(page
125: .getRoot());
126: page.visit(firstPassVisitor);
127:
128: // Second pass
129: buf = new StringBuffer();
130: SecondPassVisitor secondPassVisitor = new SecondPassVisitor(
131: page.getRoot(), buf);
132: page.visit(secondPassVisitor);
133: }
134:
135: /**
136: * Returns the input stream of the XML view.
137: *
138: * @return the input stream of the XML view
139: */
140: public InputStream getInputStream() {
141: // Turn StringBuffer into InputStream
142: try {
143: return new ByteArrayInputStream(buf.toString().getBytes(
144: "UTF-8"));
145: } catch (UnsupportedEncodingException uee) {
146: // should never happen
147: throw new RuntimeException(uee.toString());
148: }
149: }
150:
151: /*
152: * First-pass Visitor for JspRoot nodes (representing jsp:root elements)
153: * and TablibDirective nodes, ignoring any other nodes.
154: *
155: * The purpose of this Visitor is to collect the attributes of the
156: * top-level jsp:root and those of the jsp:root elements of any included
157: * pages, and add them to the jsp:root element of the XML view.
158: * In addition, this Visitor converts any taglib directives into xmlns:
159: * attributes and adds them to the jsp:root element of the XML view.
160: */
161: static class FirstPassVisitor extends Node.Visitor {
162:
163: private Node.Root root;
164: private AttributesImpl rootAttrs;
165:
166: /*
167: * Constructor
168: */
169: public FirstPassVisitor(Node.Root root) {
170: this .root = root;
171: this .rootAttrs = new AttributesImpl(defaultJspRootAttrs);
172: }
173:
174: public void visit(Node.Root n) throws JasperException,
175: JspToJavaException {
176: visitBody(n);
177: root.setAttributes(rootAttrs);
178: }
179:
180: public void visit(Node.JspRoot n) throws JasperException,
181: JspToJavaException {
182: Attributes attrs = n.getAttributes();
183: if (attrs == null) {
184: throw new JasperException(
185: "Missing attributes in jsp:root");
186: }
187: int len = attrs.getLength();
188: for (int i = 0; i < len; i++) {
189: String qName = attrs.getQName(i);
190: if ((qName.startsWith("xmlns:jsp") || qName
191: .equals("version"))) {
192: continue;
193: }
194: rootAttrs.addAttribute(attrs.getURI(i), attrs
195: .getLocalName(i), attrs.getQName(i), attrs
196: .getType(i), attrs.getValue(i));
197: }
198: visitBody(n);
199: if (n == this .root) {
200: // top-level jsp:root element
201: root.setAttributes(rootAttrs);
202: }
203: }
204:
205: /*
206: * Converts taglib directive into xmlns: attribute of jsp:root element.
207: */
208: public void visit(Node.TaglibDirective n)
209: throws JasperException, JspToJavaException {
210: Attributes attrs = n.getAttributes();
211: if (attrs != null) {
212: String uri = attrs.getValue("uri");
213: String prefix = attrs.getValue("prefix");
214: rootAttrs.addAttribute("", "", "xmlns:" + prefix,
215: "CDATA", uri);
216: }
217: }
218: }
219:
220: /*
221: * Second-pass Visitor responsible for producing XML view and assigning
222: * each JSP element a jsp:id attribute.
223: */
224: static class SecondPassVisitor extends Node.Visitor implements
225: TagConstants {
226:
227: private Node.Root root;
228: private StringBuffer buf;
229:
230: // current jsp:id attribute value
231: private int jspId;
232:
233: /*
234: * Constructor
235: */
236: public SecondPassVisitor(Node.Root root, StringBuffer buf) {
237: this .root = root;
238: this .buf = buf;
239: }
240:
241: /*
242: * Visits root node of JSP page in JSP syntax.
243: */
244: public void visit(Node.Root n) throws JasperException,
245: JspToJavaException {
246: appendTag(JSP_ROOT_TAG, n.getAttributes(), n.getBody());
247: }
248:
249: /*
250: * Visits jsp:root element of JSP page in XML syntax.
251: *
252: * Any nested jsp:root elements (from pages included via an
253: * include directive) are ignored.
254: */
255: public void visit(Node.JspRoot n) throws JasperException,
256: JspToJavaException {
257: if (n == this .root) {
258: // top-level jsp:root element
259: appendTag(JSP_ROOT_TAG, n.getAttributes(), n.getBody());
260: } else {
261: visitBody(n);
262: }
263: }
264:
265: public void visit(Node.PageDirective n) throws JasperException,
266: JspToJavaException {
267: appendPageDirective(n);
268: }
269:
270: public void visit(Node.IncludeDirective n)
271: throws JasperException, JspToJavaException {
272: // expand in place
273: visitBody(n);
274: }
275:
276: public void visit(Node.Comment n) throws JasperException,
277: JspToJavaException {
278: // Comments are ignored in XML view
279: }
280:
281: public void visit(Node.Declaration n) throws JasperException,
282: JspToJavaException {
283: // jsp:declaration has no attributes, except for jsp:id
284: appendTag(JSP_DECLARATION_TAG, n.getAttributes(), n
285: .getText());
286: }
287:
288: public void visit(Node.Expression n) throws JasperException,
289: JspToJavaException {
290: // jsp:scriptlet has no attributes, except for jsp:id
291: appendTag(JSP_EXPRESSION_TAG, n.getAttributes(), n
292: .getText());
293: }
294:
295: public void visit(Node.Scriptlet n) throws JasperException,
296: JspToJavaException {
297: // jsp:scriptlet has no attributes, except for jsp:id
298: appendTag(JSP_SCRIPTLET_TAG, n.getAttributes(), n.getText());
299: }
300:
301: public void visit(Node.IncludeAction n) throws JasperException,
302: JspToJavaException {
303: appendTag(JSP_INCLUDE_TAG, n.getAttributes(), n.getBody());
304: }
305:
306: public void visit(Node.ForwardAction n) throws JasperException,
307: JspToJavaException {
308: appendTag(JSP_FORWARD_TAG, n.getAttributes(), n.getBody());
309: }
310:
311: public void visit(Node.GetProperty n) throws JasperException,
312: JspToJavaException {
313: appendTag(JSP_GET_PROPERTY_TAG, n.getAttributes(), n
314: .getBody());
315: }
316:
317: public void visit(Node.SetProperty n) throws JasperException,
318: JspToJavaException {
319: appendTag(JSP_SET_PROPERTY_TAG, n.getAttributes(), n
320: .getBody());
321: }
322:
323: public void visit(Node.ParamAction n) throws JasperException,
324: JspToJavaException {
325: appendTag(JSP_PARAM_TAG, n.getAttributes(), n.getBody());
326: }
327:
328: public void visit(Node.ParamsAction n) throws JasperException,
329: JspToJavaException {
330: appendTag(JSP_PARAMS_TAG, n.getAttributes(), n.getBody());
331: }
332:
333: public void visit(Node.FallBackAction n)
334: throws JasperException, JspToJavaException {
335: appendTag(JSP_FALLBACK_TAG, n.getAttributes(), n.getBody());
336: }
337:
338: public void visit(Node.UseBean n) throws JasperException,
339: JspToJavaException {
340: appendTag(JSP_USE_BEAN_TAG, n.getAttributes(), n.getBody());
341: }
342:
343: public void visit(Node.PlugIn n) throws JasperException,
344: JspToJavaException {
345: appendTag(JSP_PLUGIN_TAG, n.getAttributes(), n.getBody());
346: }
347:
348: public void visit(Node.CustomTag n) throws JasperException,
349: JspToJavaException {
350: appendTag(n.getName(), n.getAttributes(), n.getBody());
351: }
352:
353: public void visit(Node.UninterpretedTag n)
354: throws JasperException, JspToJavaException {
355: appendTag(n.getName(), n.getAttributes(), n.getBody());
356: }
357:
358: public void visit(Node.JspText n) throws JasperException,
359: JspToJavaException {
360: // jsp:text has no attributes, except for jsp:id
361: appendTag(JSP_TEXT_TAG, n.getAttributes(), n.getBody());
362: }
363:
364: public void visit(Node.TemplateText n) throws JasperException,
365: JspToJavaException {
366: /*
367: * If the template text came from a JSP page written in JSP syntax,
368: * create a jsp:text element for it (JSP 5.3.2).
369: */
370: appendText(n.getText(), !n.isXmlSyntax());
371: }
372:
373: /*
374: * Appends the given tag (including its body, if present) to the XML
375: * view.
376: */
377: private void appendTag(String tag, Attributes attrs,
378: Node.Nodes body) throws JasperException,
379: JspToJavaException {
380: buf.append("<").append(tag);
381: buf.append("\n");
382: buf.append(" ").append("jsp:id").append("=\"");
383: buf.append(jspId++).append("\"\n");
384: if (attrs != null) {
385: printAttributes(attrs);
386: }
387: if (body != null) {
388: buf.append(">\n");
389: body.visit(this );
390: buf.append("</" + tag + ">\n");
391: } else {
392: buf.append("/>\n");
393: }
394: }
395:
396: /*
397: * Appends the given tag, including its text body, to the XML view.
398: */
399: private void appendTag(String tag, Attributes attrs, char[] text)
400: throws JasperException, JspToJavaException {
401: buf.append("<").append(tag);
402: buf.append("\n");
403: buf.append(" ").append("jsp:id").append("=\"");
404: buf.append(jspId++).append("\"\n");
405: if (attrs != null) {
406: printAttributes(attrs);
407: }
408: if (text != null) {
409: buf.append(">\n");
410: appendText(text, false);
411: buf.append("</" + tag + ">\n");
412: } else {
413: buf.append("/>\n");
414: }
415: }
416:
417: /*
418: * Appends the page directive with the given attributes to the XML
419: * view.
420: *
421: * Since the import attribute of the page directive is the only page
422: * attribute that is allowed to appear multiple times within the same
423: * document, and since XML allows only single-value attributes,
424: * the values of multiple import attributes must be combined into one,
425: * separated by comma.
426: */
427: private void appendPageDirective(Node.PageDirective pageDir) {
428: Attributes attrs = pageDir.getAttributes();
429: buf.append("<").append(JSP_PAGE_DIRECTIVE_TAG);
430: buf.append("\n");
431:
432: // append jsp:id
433: buf.append(" ").append("jsp:id").append("=\"");
434: buf.append(jspId++).append("\"\n");
435:
436: // append remaining attributes
437: int len = attrs.getLength();
438: for (int i = 0; i < len; i++) {
439: String attrName = attrs.getQName(i);
440: if ("import".equals(attrName)) {
441: // Ignore page directive's import attribute for now
442: continue;
443: }
444: String value = attrs.getValue(i);
445: buf.append(" ").append(attrName).append("=\"");
446: buf.append(JspUtil.getExprInXml(value)).append("\"\n");
447: }
448: if (pageDir.getImports().size() > 0) {
449: // Concatenate names of imported classes/packages
450: boolean first = true;
451: ListIterator iter = pageDir.getImports().listIterator();
452: while (iter.hasNext()) {
453: if (first) {
454: first = false;
455: buf.append(" import=\"");
456: } else {
457: buf.append(",");
458: }
459: buf.append(JspUtil.getExprInXml((String) iter
460: .next()));
461: }
462: buf.append("\"\n");
463: }
464: buf.append("/>\n");
465: }
466:
467: private void appendText(char[] text,
468: boolean createJspTextElement) {
469: if (createJspTextElement) {
470: buf.append(JSP_TEXT_TAG_START);
471: appendCDATA(text);
472: buf.append(JSP_TEXT_TAG_END);
473: } else {
474: appendCDATA(text);
475: }
476: }
477:
478: /*
479: * Appends the given text as a CDATA section to the XML view, unless
480: * the text has already been marked as CDATA.
481: */
482: private void appendCDATA(char[] text) {
483: buf.append(CDATA_START_SECTION);
484: buf.append(escapeCDATA(text));
485: buf.append(CDATA_END_SECTION);
486: }
487:
488: /*
489: * Escapes any occurrences of "]]>" (by replacing them with "]]>")
490: * within the given text, so it can be included in a CDATA section.
491: */
492: private char[] escapeCDATA(char[] text) {
493: CharArrayWriter result = new CharArrayWriter(text.length);
494: for (int i = 0; i < text.length; i++) {
495: if (((i + 2) < text.length) && (text[i] == ']')
496: && (text[i + 1] == ']') && (text[i + 2] == '>')) {
497: // match found
498: result.write(']');
499: result.write(']');
500: result.write('&');
501: result.write('g');
502: result.write('t');
503: result.write(';');
504: i += 2;
505: } else {
506: result.write(text[i]);
507: }
508: }
509: return result.toCharArray();
510: }
511:
512: /*
513: * Appends the given attributes to the XML view.
514: */
515: private void printAttributes(Attributes attrs) {
516: int len = attrs.getLength();
517: for (int i = 0; i < len; i++) {
518: String name = attrs.getQName(i);
519: String value = attrs.getValue(i);
520: buf.append(" ").append(name).append("=\"");
521: buf.append(JspUtil.getExprInXml(value)).append("\"\n");
522: }
523: }
524: }
525: }
|