001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jasper.compiler;
018:
019: import java.io.InputStream;
020: import java.io.ByteArrayInputStream;
021: import java.io.CharArrayWriter;
022: import java.io.UnsupportedEncodingException;
023: import java.util.ListIterator;
024: import javax.servlet.jsp.tagext.PageData;
025: import org.xml.sax.Attributes;
026: import org.xml.sax.helpers.AttributesImpl;
027: import org.apache.jasper.JasperException;
028:
029: /**
030: * An implementation of <tt>javax.servlet.jsp.tagext.PageData</tt> which
031: * builds the XML view of a given page.
032: *
033: * The XML view is built in two passes:
034: *
035: * During the first pass, the FirstPassVisitor collects the attributes of the
036: * top-level jsp:root and those of the jsp:root elements of any included
037: * pages, and adds them to the jsp:root element of the XML view.
038: * In addition, any taglib directives are converted into xmlns: attributes and
039: * added to the jsp:root element of the XML view.
040: * This pass ignores any nodes other than JspRoot and TaglibDirective.
041: *
042: * During the second pass, the SecondPassVisitor produces the XML view, using
043: * the combined jsp:root attributes determined in the first pass and any
044: * remaining pages nodes (this pass ignores any JspRoot and TaglibDirective
045: * nodes).
046: *
047: * @author Jan Luehe
048: */
049: class PageDataImpl extends PageData implements TagConstants {
050:
051: private static final String JSP_VERSION = "2.0";
052: private static final String CDATA_START_SECTION = "<![CDATA[\n";
053: private static final String CDATA_END_SECTION = "]]>\n";
054:
055: // string buffer used to build XML view
056: private StringBuffer buf;
057:
058: /**
059: * Constructor.
060: *
061: * @param page the page nodes from which to generate the XML view
062: */
063: public PageDataImpl(Node.Nodes page, Compiler compiler)
064: throws JasperException {
065:
066: // First pass
067: FirstPassVisitor firstPass = new FirstPassVisitor(page
068: .getRoot(), compiler.getPageInfo());
069: page.visit(firstPass);
070:
071: // Second pass
072: buf = new StringBuffer();
073: SecondPassVisitor secondPass = new SecondPassVisitor(page
074: .getRoot(), buf, compiler, firstPass.getJspIdPrefix());
075: page.visit(secondPass);
076: }
077:
078: /**
079: * Returns the input stream of the XML view.
080: *
081: * @return the input stream of the XML view
082: */
083: public InputStream getInputStream() {
084: // Turn StringBuffer into InputStream
085: try {
086: return new ByteArrayInputStream(buf.toString().getBytes(
087: "UTF-8"));
088: } catch (UnsupportedEncodingException uee) {
089: // should never happen
090: throw new RuntimeException(uee.toString());
091: }
092: }
093:
094: /*
095: * First-pass Visitor for JspRoot nodes (representing jsp:root elements)
096: * and TablibDirective nodes, ignoring any other nodes.
097: *
098: * The purpose of this Visitor is to collect the attributes of the
099: * top-level jsp:root and those of the jsp:root elements of any included
100: * pages, and add them to the jsp:root element of the XML view.
101: * In addition, this Visitor converts any taglib directives into xmlns:
102: * attributes and adds them to the jsp:root element of the XML view.
103: */
104: static class FirstPassVisitor extends Node.Visitor implements
105: TagConstants {
106:
107: private Node.Root root;
108: private AttributesImpl rootAttrs;
109: private PageInfo pageInfo;
110:
111: // Prefix for the 'id' attribute
112: private String jspIdPrefix;
113:
114: /*
115: * Constructor
116: */
117: public FirstPassVisitor(Node.Root root, PageInfo pageInfo) {
118: this .root = root;
119: this .pageInfo = pageInfo;
120: this .rootAttrs = new AttributesImpl();
121: this .rootAttrs.addAttribute("", "", "version", "CDATA",
122: JSP_VERSION);
123: this .jspIdPrefix = "jsp";
124: }
125:
126: public void visit(Node.Root n) throws JasperException {
127: visitBody(n);
128: if (n == root) {
129: /*
130: * Top-level page.
131: *
132: * Add
133: * xmlns:jsp="http://java.sun.com/JSP/Page"
134: * attribute only if not already present.
135: */
136: if (!JSP_URI.equals(rootAttrs.getValue("xmlns:jsp"))) {
137: rootAttrs.addAttribute("", "", "xmlns:jsp",
138: "CDATA", JSP_URI);
139: }
140:
141: if (pageInfo.isJspPrefixHijacked()) {
142: /*
143: * 'jsp' prefix has been hijacked, that is, bound to a
144: * namespace other than the JSP namespace. This means that
145: * when adding an 'id' attribute to each element, we can't
146: * use the 'jsp' prefix. Therefore, create a new prefix
147: * (one that is unique across the translation unit) for use
148: * by the 'id' attribute, and bind it to the JSP namespace
149: */
150: jspIdPrefix += "jsp";
151: while (pageInfo.containsPrefix(jspIdPrefix)) {
152: jspIdPrefix += "jsp";
153: }
154: rootAttrs.addAttribute("", "", "xmlns:"
155: + jspIdPrefix, "CDATA", JSP_URI);
156: }
157:
158: root.setAttributes(rootAttrs);
159: }
160: }
161:
162: public void visit(Node.JspRoot n) throws JasperException {
163: addAttributes(n.getTaglibAttributes());
164: addAttributes(n.getNonTaglibXmlnsAttributes());
165: addAttributes(n.getAttributes());
166:
167: visitBody(n);
168: }
169:
170: /*
171: * Converts taglib directive into "xmlns:..." attribute of jsp:root
172: * element.
173: */
174: public void visit(Node.TaglibDirective n)
175: throws JasperException {
176: Attributes attrs = n.getAttributes();
177: if (attrs != null) {
178: String qName = "xmlns:" + attrs.getValue("prefix");
179: /*
180: * According to javadocs of org.xml.sax.helpers.AttributesImpl,
181: * the addAttribute method does not check to see if the
182: * specified attribute is already contained in the list: This
183: * is the application's responsibility!
184: */
185: if (rootAttrs.getIndex(qName) == -1) {
186: String location = attrs.getValue("uri");
187: if (location != null) {
188: if (location.startsWith("/")) {
189: location = URN_JSPTLD + location;
190: }
191: rootAttrs.addAttribute("", "", qName, "CDATA",
192: location);
193: } else {
194: location = attrs.getValue("tagdir");
195: rootAttrs.addAttribute("", "", qName, "CDATA",
196: URN_JSPTAGDIR + location);
197: }
198: }
199: }
200: }
201:
202: public String getJspIdPrefix() {
203: return jspIdPrefix;
204: }
205:
206: private void addAttributes(Attributes attrs) {
207: if (attrs != null) {
208: int len = attrs.getLength();
209:
210: for (int i = 0; i < len; i++) {
211: String qName = attrs.getQName(i);
212: if ("version".equals(qName)) {
213: continue;
214: }
215:
216: // Bugzilla 35252: http://issues.apache.org/bugzilla/show_bug.cgi?id=35252
217: if (rootAttrs.getIndex(qName) == -1) {
218: rootAttrs.addAttribute(attrs.getURI(i), attrs
219: .getLocalName(i), qName, attrs
220: .getType(i), attrs.getValue(i));
221: }
222: }
223: }
224: }
225: }
226:
227: /*
228: * Second-pass Visitor responsible for producing XML view and assigning
229: * each element a unique jsp:id attribute.
230: */
231: static class SecondPassVisitor extends Node.Visitor implements
232: TagConstants {
233:
234: private Node.Root root;
235: private StringBuffer buf;
236: private Compiler compiler;
237: private String jspIdPrefix;
238: private boolean resetDefaultNS = false;
239:
240: // Current value of jsp:id attribute
241: private int jspId;
242:
243: /*
244: * Constructor
245: */
246: public SecondPassVisitor(Node.Root root, StringBuffer buf,
247: Compiler compiler, String jspIdPrefix) {
248: this .root = root;
249: this .buf = buf;
250: this .compiler = compiler;
251: this .jspIdPrefix = jspIdPrefix;
252: }
253:
254: /*
255: * Visits root node.
256: */
257: public void visit(Node.Root n) throws JasperException {
258: if (n == this .root) {
259: // top-level page
260: appendXmlProlog();
261: appendTag(n);
262: } else {
263: boolean resetDefaultNSSave = resetDefaultNS;
264: if (n.isXmlSyntax()) {
265: resetDefaultNS = true;
266: }
267: visitBody(n);
268: resetDefaultNS = resetDefaultNSSave;
269: }
270: }
271:
272: /*
273: * Visits jsp:root element of JSP page in XML syntax.
274: *
275: * Any nested jsp:root elements (from pages included via an
276: * include directive) are ignored.
277: */
278: public void visit(Node.JspRoot n) throws JasperException {
279: visitBody(n);
280: }
281:
282: public void visit(Node.PageDirective n) throws JasperException {
283: appendPageDirective(n);
284: }
285:
286: public void visit(Node.IncludeDirective n)
287: throws JasperException {
288: // expand in place
289: visitBody(n);
290: }
291:
292: public void visit(Node.Comment n) throws JasperException {
293: // Comments are ignored in XML view
294: }
295:
296: public void visit(Node.Declaration n) throws JasperException {
297: appendTag(n);
298: }
299:
300: public void visit(Node.Expression n) throws JasperException {
301: appendTag(n);
302: }
303:
304: public void visit(Node.Scriptlet n) throws JasperException {
305: appendTag(n);
306: }
307:
308: public void visit(Node.JspElement n) throws JasperException {
309: appendTag(n);
310: }
311:
312: public void visit(Node.ELExpression n) throws JasperException {
313: if (!n.getRoot().isXmlSyntax()) {
314: buf.append("<").append(JSP_TEXT_ACTION);
315: buf.append(" ");
316: buf.append(jspIdPrefix);
317: buf.append(":id=\"");
318: buf.append(jspId++).append("\">");
319: }
320: buf.append("${");
321: buf.append(JspUtil.escapeXml(n.getText()));
322: buf.append("}");
323: if (!n.getRoot().isXmlSyntax()) {
324: buf.append(JSP_TEXT_ACTION_END);
325: }
326: buf.append("\n");
327: }
328:
329: public void visit(Node.IncludeAction n) throws JasperException {
330: appendTag(n);
331: }
332:
333: public void visit(Node.ForwardAction n) throws JasperException {
334: appendTag(n);
335: }
336:
337: public void visit(Node.GetProperty n) throws JasperException {
338: appendTag(n);
339: }
340:
341: public void visit(Node.SetProperty n) throws JasperException {
342: appendTag(n);
343: }
344:
345: public void visit(Node.ParamAction n) throws JasperException {
346: appendTag(n);
347: }
348:
349: public void visit(Node.ParamsAction n) throws JasperException {
350: appendTag(n);
351: }
352:
353: public void visit(Node.FallBackAction n) throws JasperException {
354: appendTag(n);
355: }
356:
357: public void visit(Node.UseBean n) throws JasperException {
358: appendTag(n);
359: }
360:
361: public void visit(Node.PlugIn n) throws JasperException {
362: appendTag(n);
363: }
364:
365: public void visit(Node.NamedAttribute n) throws JasperException {
366: appendTag(n);
367: }
368:
369: public void visit(Node.JspBody n) throws JasperException {
370: appendTag(n);
371: }
372:
373: public void visit(Node.CustomTag n) throws JasperException {
374: boolean resetDefaultNSSave = resetDefaultNS;
375: appendTag(n, resetDefaultNS);
376: resetDefaultNS = resetDefaultNSSave;
377: }
378:
379: public void visit(Node.UninterpretedTag n)
380: throws JasperException {
381: boolean resetDefaultNSSave = resetDefaultNS;
382: appendTag(n, resetDefaultNS);
383: resetDefaultNS = resetDefaultNSSave;
384: }
385:
386: public void visit(Node.JspText n) throws JasperException {
387: appendTag(n);
388: }
389:
390: public void visit(Node.DoBodyAction n) throws JasperException {
391: appendTag(n);
392: }
393:
394: public void visit(Node.InvokeAction n) throws JasperException {
395: appendTag(n);
396: }
397:
398: public void visit(Node.TagDirective n) throws JasperException {
399: appendTagDirective(n);
400: }
401:
402: public void visit(Node.AttributeDirective n)
403: throws JasperException {
404: appendTag(n);
405: }
406:
407: public void visit(Node.VariableDirective n)
408: throws JasperException {
409: appendTag(n);
410: }
411:
412: public void visit(Node.TemplateText n) throws JasperException {
413: /*
414: * If the template text came from a JSP page written in JSP syntax,
415: * create a jsp:text element for it (JSP 5.3.2).
416: */
417: appendText(n.getText(), !n.getRoot().isXmlSyntax());
418: }
419:
420: /*
421: * Appends the given tag, including its body, to the XML view.
422: */
423: private void appendTag(Node n) throws JasperException {
424: appendTag(n, false);
425: }
426:
427: /*
428: * Appends the given tag, including its body, to the XML view,
429: * and optionally reset default namespace to "", if none specified.
430: */
431: private void appendTag(Node n, boolean addDefaultNS)
432: throws JasperException {
433:
434: Node.Nodes body = n.getBody();
435: String text = n.getText();
436:
437: buf.append("<").append(n.getQName());
438: buf.append("\n");
439:
440: printAttributes(n, addDefaultNS);
441: buf.append(" ").append(jspIdPrefix).append(":id").append(
442: "=\"");
443: buf.append(jspId++).append("\"\n");
444:
445: if (ROOT_ACTION.equals(n.getLocalName()) || body != null
446: || text != null) {
447: buf.append(">\n");
448: if (ROOT_ACTION.equals(n.getLocalName())) {
449: if (compiler.getCompilationContext().isTagFile()) {
450: appendTagDirective();
451: } else {
452: appendPageDirective();
453: }
454: }
455: if (body != null) {
456: body.visit(this );
457: } else {
458: appendText(text, false);
459: }
460: buf.append("</" + n.getQName() + ">\n");
461: } else {
462: buf.append("/>\n");
463: }
464: }
465:
466: /*
467: * Appends the page directive with the given attributes to the XML
468: * view.
469: *
470: * Since the import attribute of the page directive is the only page
471: * attribute that is allowed to appear multiple times within the same
472: * document, and since XML allows only single-value attributes,
473: * the values of multiple import attributes must be combined into one,
474: * separated by comma.
475: *
476: * If the given page directive contains just 'contentType' and/or
477: * 'pageEncoding' attributes, we ignore it, as we've already appended
478: * a page directive containing just these two attributes.
479: */
480: private void appendPageDirective(Node.PageDirective n) {
481: boolean append = false;
482: Attributes attrs = n.getAttributes();
483: int len = (attrs == null) ? 0 : attrs.getLength();
484: for (int i = 0; i < len; i++) {
485: String attrName = attrs.getQName(i);
486: if (!"pageEncoding".equals(attrName)
487: && !"contentType".equals(attrName)) {
488: append = true;
489: break;
490: }
491: }
492: if (!append) {
493: return;
494: }
495:
496: buf.append("<").append(n.getQName());
497: buf.append("\n");
498:
499: // append jsp:id
500: buf.append(" ").append(jspIdPrefix).append(":id").append(
501: "=\"");
502: buf.append(jspId++).append("\"\n");
503:
504: // append remaining attributes
505: for (int i = 0; i < len; i++) {
506: String attrName = attrs.getQName(i);
507: if ("import".equals(attrName)
508: || "contentType".equals(attrName)
509: || "pageEncoding".equals(attrName)) {
510: /*
511: * Page directive's 'import' attribute is considered
512: * further down, and its 'pageEncoding' and 'contentType'
513: * attributes are ignored, since we've already appended
514: * a new page directive containing just these two
515: * attributes
516: */
517: continue;
518: }
519: String value = attrs.getValue(i);
520: buf.append(" ").append(attrName).append("=\"");
521: buf.append(JspUtil.getExprInXml(value)).append("\"\n");
522: }
523: if (n.getImports().size() > 0) {
524: // Concatenate names of imported classes/packages
525: boolean first = true;
526: ListIterator iter = n.getImports().listIterator();
527: while (iter.hasNext()) {
528: if (first) {
529: first = false;
530: buf.append(" import=\"");
531: } else {
532: buf.append(",");
533: }
534: buf.append(JspUtil.getExprInXml((String) iter
535: .next()));
536: }
537: buf.append("\"\n");
538: }
539: buf.append("/>\n");
540: }
541:
542: /*
543: * Appends a page directive with 'pageEncoding' and 'contentType'
544: * attributes.
545: *
546: * The value of the 'pageEncoding' attribute is hard-coded
547: * to UTF-8, whereas the value of the 'contentType' attribute, which
548: * is identical to what the container will pass to
549: * ServletResponse.setContentType(), is derived from the pageInfo.
550: */
551: private void appendPageDirective() {
552: buf.append("<").append(JSP_PAGE_DIRECTIVE_ACTION);
553: buf.append("\n");
554:
555: // append jsp:id
556: buf.append(" ").append(jspIdPrefix).append(":id").append(
557: "=\"");
558: buf.append(jspId++).append("\"\n");
559: buf.append(" ").append("pageEncoding").append(
560: "=\"UTF-8\"\n");
561: buf.append(" ").append("contentType").append("=\"");
562: buf.append(compiler.getPageInfo().getContentType()).append(
563: "\"\n");
564: buf.append("/>\n");
565: }
566:
567: /*
568: * Appends the tag directive with the given attributes to the XML
569: * view.
570: *
571: * If the given tag directive contains just a 'pageEncoding'
572: * attributes, we ignore it, as we've already appended
573: * a tag directive containing just this attributes.
574: */
575: private void appendTagDirective(Node.TagDirective n)
576: throws JasperException {
577:
578: boolean append = false;
579: Attributes attrs = n.getAttributes();
580: int len = (attrs == null) ? 0 : attrs.getLength();
581: for (int i = 0; i < len; i++) {
582: String attrName = attrs.getQName(i);
583: if (!"pageEncoding".equals(attrName)) {
584: append = true;
585: break;
586: }
587: }
588: if (!append) {
589: return;
590: }
591:
592: appendTag(n);
593: }
594:
595: /*
596: * Appends a tag directive containing a single 'pageEncoding'
597: * attribute whose value is hard-coded to UTF-8.
598: */
599: private void appendTagDirective() {
600: buf.append("<").append(JSP_TAG_DIRECTIVE_ACTION);
601: buf.append("\n");
602:
603: // append jsp:id
604: buf.append(" ").append(jspIdPrefix).append(":id").append(
605: "=\"");
606: buf.append(jspId++).append("\"\n");
607: buf.append(" ").append("pageEncoding").append(
608: "=\"UTF-8\"\n");
609: buf.append("/>\n");
610: }
611:
612: private void appendText(String text,
613: boolean createJspTextElement) {
614: if (createJspTextElement) {
615: buf.append("<").append(JSP_TEXT_ACTION);
616: buf.append("\n");
617:
618: // append jsp:id
619: buf.append(" ").append(jspIdPrefix).append(":id")
620: .append("=\"");
621: buf.append(jspId++).append("\"\n");
622: buf.append(">\n");
623:
624: appendCDATA(text);
625: buf.append(JSP_TEXT_ACTION_END);
626: buf.append("\n");
627: } else {
628: appendCDATA(text);
629: }
630: }
631:
632: /*
633: * Appends the given text as a CDATA section to the XML view, unless
634: * the text has already been marked as CDATA.
635: */
636: private void appendCDATA(String text) {
637: buf.append(CDATA_START_SECTION);
638: buf.append(escapeCDATA(text));
639: buf.append(CDATA_END_SECTION);
640: }
641:
642: /*
643: * Escapes any occurrences of "]]>" (by replacing them with "]]>")
644: * within the given text, so it can be included in a CDATA section.
645: */
646: private String escapeCDATA(String text) {
647: if (text == null)
648: return "";
649: int len = text.length();
650: CharArrayWriter result = new CharArrayWriter(len);
651: for (int i = 0; i < len; i++) {
652: if (((i + 2) < len) && (text.charAt(i) == ']')
653: && (text.charAt(i + 1) == ']')
654: && (text.charAt(i + 2) == '>')) {
655: // match found
656: result.write(']');
657: result.write(']');
658: result.write('&');
659: result.write('g');
660: result.write('t');
661: result.write(';');
662: i += 2;
663: } else {
664: result.write(text.charAt(i));
665: }
666: }
667: return result.toString();
668: }
669:
670: /*
671: * Appends the attributes of the given Node to the XML view.
672: */
673: private void printAttributes(Node n, boolean addDefaultNS) {
674:
675: /*
676: * Append "xmlns" attributes that represent tag libraries
677: */
678: Attributes attrs = n.getTaglibAttributes();
679: int len = (attrs == null) ? 0 : attrs.getLength();
680: for (int i = 0; i < len; i++) {
681: String name = attrs.getQName(i);
682: String value = attrs.getValue(i);
683: buf.append(" ").append(name).append("=\"").append(
684: value).append("\"\n");
685: }
686:
687: /*
688: * Append "xmlns" attributes that do not represent tag libraries
689: */
690: attrs = n.getNonTaglibXmlnsAttributes();
691: len = (attrs == null) ? 0 : attrs.getLength();
692: boolean defaultNSSeen = false;
693: for (int i = 0; i < len; i++) {
694: String name = attrs.getQName(i);
695: String value = attrs.getValue(i);
696: buf.append(" ").append(name).append("=\"").append(
697: value).append("\"\n");
698: defaultNSSeen |= "xmlns".equals(name);
699: }
700: if (addDefaultNS && !defaultNSSeen) {
701: buf.append(" xmlns=\"\"\n");
702: }
703: resetDefaultNS = false;
704:
705: /*
706: * Append all other attributes
707: */
708: attrs = n.getAttributes();
709: len = (attrs == null) ? 0 : attrs.getLength();
710: for (int i = 0; i < len; i++) {
711: String name = attrs.getQName(i);
712: String value = attrs.getValue(i);
713: buf.append(" ").append(name).append("=\"");
714: buf.append(JspUtil.getExprInXml(value)).append("\"\n");
715: }
716: }
717:
718: /*
719: * Appends XML prolog with encoding declaration.
720: */
721: private void appendXmlProlog() {
722: buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
723: }
724: }
725: }
|