View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.struts2.jasper.compiler;
19  
20  import org.apache.struts2.jasper.JasperException;
21  import org.apache.struts2.jasper.JspCompilationContext;
22  import org.apache.struts2.jasper.runtime.JspSourceDependent;
23  import org.apache.struts2.jasper.servlet.JspServletWrapper;
24  
25  import javax.servlet.jsp.tagext.*;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.net.URL;
29  import java.net.URLClassLoader;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Vector;
34  
35  /***
36   * 1. Processes and extracts the directive info in a tag file.
37   * 2. Compiles and loads tag files used in a JSP file.
38   *
39   * @author Kin-man Chung
40   */
41  
42  class TagFileProcessor {
43  
44      private Vector tempVector;
45  
46      /***
47       * A visitor the tag file
48       */
49      private static class TagFileDirectiveVisitor extends Node.Visitor {
50  
51          private static final JspUtil.ValidAttribute[] tagDirectiveAttrs = {
52                  new JspUtil.ValidAttribute("display-name"),
53                  new JspUtil.ValidAttribute("body-content"),
54                  new JspUtil.ValidAttribute("dynamic-attributes"),
55                  new JspUtil.ValidAttribute("small-icon"),
56                  new JspUtil.ValidAttribute("large-icon"),
57                  new JspUtil.ValidAttribute("description"),
58                  new JspUtil.ValidAttribute("example"),
59                  new JspUtil.ValidAttribute("pageEncoding"),
60                  new JspUtil.ValidAttribute("language"),
61                  new JspUtil.ValidAttribute("import"),
62                  new JspUtil.ValidAttribute("isELIgnored")};
63  
64          private static final JspUtil.ValidAttribute[] attributeDirectiveAttrs = {
65                  new JspUtil.ValidAttribute("name", true),
66                  new JspUtil.ValidAttribute("required"),
67                  new JspUtil.ValidAttribute("fragment"),
68                  new JspUtil.ValidAttribute("rtexprvalue"),
69                  new JspUtil.ValidAttribute("type"),
70                  new JspUtil.ValidAttribute("description")
71          };
72  
73          private static final JspUtil.ValidAttribute[] variableDirectiveAttrs = {
74                  new JspUtil.ValidAttribute("name-given"),
75                  new JspUtil.ValidAttribute("name-from-attribute"),
76                  new JspUtil.ValidAttribute("alias"),
77                  new JspUtil.ValidAttribute("variable-class"),
78                  new JspUtil.ValidAttribute("scope"),
79                  new JspUtil.ValidAttribute("declare"),
80                  new JspUtil.ValidAttribute("description")
81          };
82  
83          private ErrorDispatcher err;
84          private TagLibraryInfo tagLibInfo;
85  
86          private String name = null;
87          private String path = null;
88          private TagExtraInfo tei = null;
89          private String bodycontent = null;
90          private String description = null;
91          private String displayName = null;
92          private String smallIcon = null;
93          private String largeIcon = null;
94          private String dynamicAttrsMapName;
95          private String example = null;
96  
97          private Vector attributeVector;
98          private Vector variableVector;
99  
100         private static final String ATTR_NAME =
101                 "the name attribute of the attribute directive";
102         private static final String VAR_NAME_GIVEN =
103                 "the name-given attribute of the variable directive";
104         private static final String VAR_NAME_FROM =
105                 "the name-from-attribute attribute of the variable directive";
106         private static final String VAR_ALIAS =
107                 "the alias attribute of the variable directive";
108         private static final String TAG_DYNAMIC =
109                 "the dynamic-attributes attribute of the tag directive";
110         private HashMap nameTable = new HashMap();
111         private HashMap nameFromTable = new HashMap();
112 
113         public TagFileDirectiveVisitor(Compiler compiler,
114                                        TagLibraryInfo tagLibInfo,
115                                        String name,
116                                        String path) {
117             err = compiler.getErrorDispatcher();
118             this.tagLibInfo = tagLibInfo;
119             this.name = name;
120             this.path = path;
121             attributeVector = new Vector();
122             variableVector = new Vector();
123         }
124 
125         public void visit(Node.TagDirective n) throws JasperException {
126 
127             JspUtil.checkAttributes("Tag directive", n, tagDirectiveAttrs,
128                     err);
129 
130             bodycontent = checkConflict(n, bodycontent, "body-content");
131             if (bodycontent != null &&
132                     !bodycontent.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY) &&
133                     !bodycontent.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT) &&
134                     !bodycontent.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
135                 err.jspError(n, "jsp.error.tagdirective.badbodycontent",
136                         bodycontent);
137             }
138             dynamicAttrsMapName = checkConflict(n, dynamicAttrsMapName,
139                     "dynamic-attributes");
140             if (dynamicAttrsMapName != null) {
141                 checkUniqueName(dynamicAttrsMapName, TAG_DYNAMIC, n);
142             }
143             smallIcon = checkConflict(n, smallIcon, "small-icon");
144             largeIcon = checkConflict(n, largeIcon, "large-icon");
145             description = checkConflict(n, description, "description");
146             displayName = checkConflict(n, displayName, "display-name");
147             example = checkConflict(n, example, "example");
148         }
149 
150         private String checkConflict(Node n, String oldAttrValue, String attr)
151                 throws JasperException {
152 
153             String result = oldAttrValue;
154             String attrValue = n.getAttributeValue(attr);
155             if (attrValue != null) {
156                 if (oldAttrValue != null && !oldAttrValue.equals(attrValue)) {
157                     err.jspError(n, "jsp.error.tag.conflict.attr", attr,
158                             oldAttrValue, attrValue);
159                 }
160                 result = attrValue;
161             }
162             return result;
163         }
164 
165 
166         public void visit(Node.AttributeDirective n) throws JasperException {
167 
168             JspUtil.checkAttributes("Attribute directive", n,
169                     attributeDirectiveAttrs, err);
170 
171             String attrName = n.getAttributeValue("name");
172             boolean required = JspUtil.booleanValue(
173                     n.getAttributeValue("required"));
174             boolean rtexprvalue = true;
175             String rtexprvalueString = n.getAttributeValue("rtexprvalue");
176             if (rtexprvalueString != null) {
177                 rtexprvalue = JspUtil.booleanValue(rtexprvalueString);
178             }
179             boolean fragment = JspUtil.booleanValue(
180                     n.getAttributeValue("fragment"));
181             String type = n.getAttributeValue("type");
182             if (fragment) {
183                 // type is fixed to "JspFragment" and a translation error
184                 // must occur if specified.
185                 if (type != null) {
186                     err.jspError(n, "jsp.error.fragmentwithtype");
187                 }
188                 // rtexprvalue is fixed to "true" and a translation error
189                 // must occur if specified.
190                 rtexprvalue = true;
191                 if (rtexprvalueString != null) {
192                     err.jspError(n, "jsp.error.frgmentwithrtexprvalue");
193                 }
194             } else {
195                 if (type == null)
196                     type = "java.lang.String";
197             }
198 
199             TagAttributeInfo tagAttributeInfo =
200                     new TagAttributeInfo(attrName, required, type, rtexprvalue,
201                             fragment);
202             attributeVector.addElement(tagAttributeInfo);
203             checkUniqueName(attrName, ATTR_NAME, n, tagAttributeInfo);
204         }
205 
206         public void visit(Node.VariableDirective n) throws JasperException {
207 
208             JspUtil.checkAttributes("Variable directive", n,
209                     variableDirectiveAttrs, err);
210 
211             String nameGiven = n.getAttributeValue("name-given");
212             String nameFromAttribute = n.getAttributeValue("name-from-attribute");
213             if (nameGiven == null && nameFromAttribute == null) {
214                 err.jspError("jsp.error.variable.either.name");
215             }
216 
217             if (nameGiven != null && nameFromAttribute != null) {
218                 err.jspError("jsp.error.variable.both.name");
219             }
220 
221             String alias = n.getAttributeValue("alias");
222             if (nameFromAttribute != null && alias == null ||
223                     nameFromAttribute == null && alias != null) {
224                 err.jspError("jsp.error.variable.alias");
225             }
226 
227             String className = n.getAttributeValue("variable-class");
228             if (className == null)
229                 className = "java.lang.String";
230 
231             String declareStr = n.getAttributeValue("declare");
232             boolean declare = true;
233             if (declareStr != null)
234                 declare = JspUtil.booleanValue(declareStr);
235 
236             int scope = VariableInfo.NESTED;
237             String scopeStr = n.getAttributeValue("scope");
238             if (scopeStr != null) {
239                 if ("NESTED".equals(scopeStr)) {
240                     // Already the default
241                 } else if ("AT_BEGIN".equals(scopeStr)) {
242                     scope = VariableInfo.AT_BEGIN;
243                 } else if ("AT_END".equals(scopeStr)) {
244                     scope = VariableInfo.AT_END;
245                 }
246             }
247 
248             if (nameFromAttribute != null) {
249                 /*
250 		 * An alias has been specified. We use 'nameGiven' to hold the
251 		 * value of the alias, and 'nameFromAttribute' to hold the 
252 		 * name of the attribute whose value (at invocation-time)
253 		 * denotes the name of the variable that is being aliased
254 		 */
255                 nameGiven = alias;
256                 checkUniqueName(nameFromAttribute, VAR_NAME_FROM, n);
257                 checkUniqueName(alias, VAR_ALIAS, n);
258             } else {
259                 // name-given specified
260                 checkUniqueName(nameGiven, VAR_NAME_GIVEN, n);
261             }
262 
263             variableVector.addElement(new TagVariableInfo(
264                     nameGiven,
265                     nameFromAttribute,
266                     className,
267                     declare,
268                     scope));
269         }
270 
271         /*
272          * Returns the vector of attributes corresponding to attribute
273          * directives.
274          */
275         public Vector getAttributesVector() {
276             return attributeVector;
277         }
278 
279         /*
280          * Returns the vector of variables corresponding to variable
281          * directives.
282          */
283         public Vector getVariablesVector() {
284             return variableVector;
285         }
286 
287         /*
288        * Returns the value of the dynamic-attributes tag directive
289        * attribute.
290        */
291         public String getDynamicAttributesMapName() {
292             return dynamicAttrsMapName;
293         }
294 
295         public TagInfo getTagInfo() throws JasperException {
296 
297             if (name == null) {
298                 // XXX Get it from tag file name
299             }
300 
301             if (bodycontent == null) {
302                 bodycontent = TagInfo.BODY_CONTENT_SCRIPTLESS;
303             }
304 
305             String tagClassName = JspUtil.getTagHandlerClassName(path, err);
306 
307             TagVariableInfo[] tagVariableInfos
308                     = new TagVariableInfo[variableVector.size()];
309             variableVector.copyInto(tagVariableInfos);
310 
311             TagAttributeInfo[] tagAttributeInfo
312                     = new TagAttributeInfo[attributeVector.size()];
313             attributeVector.copyInto(tagAttributeInfo);
314 
315             return new JasperTagInfo(name,
316                     tagClassName,
317                     bodycontent,
318                     description,
319                     tagLibInfo,
320                     tei,
321                     tagAttributeInfo,
322                     displayName,
323                     smallIcon,
324                     largeIcon,
325                     tagVariableInfos,
326                     dynamicAttrsMapName);
327         }
328 
329         static class NameEntry {
330             private String type;
331             private Node node;
332             private TagAttributeInfo attr;
333 
334             NameEntry(String type, Node node, TagAttributeInfo attr) {
335                 this.type = type;
336                 this.node = node;
337                 this.attr = attr;
338             }
339 
340             String getType() {
341                 return type;
342             }
343 
344             Node getNode() {
345                 return node;
346             }
347 
348             TagAttributeInfo getTagAttributeInfo() {
349                 return attr;
350             }
351         }
352 
353         /***
354          * Reports a translation error if names specified in attributes of
355          * directives are not unique in this translation unit.
356          * <p/>
357          * The value of the following attributes must be unique.
358          * 1. 'name' attribute of an attribute directive
359          * 2. 'name-given' attribute of a variable directive
360          * 3. 'alias' attribute of variable directive
361          * 4. 'dynamic-attributes' of a tag directive
362          * except that 'dynamic-attributes' can (and must) have the same
363          * value when it appears in multiple tag directives.
364          * <p/>
365          * Also, 'name-from' attribute of a variable directive cannot have
366          * the same value as that from another variable directive.
367          */
368         private void checkUniqueName(String name, String type, Node n)
369                 throws JasperException {
370             checkUniqueName(name, type, n, null);
371         }
372 
373         private void checkUniqueName(String name, String type, Node n,
374                                      TagAttributeInfo attr)
375                 throws JasperException {
376 
377             HashMap table = (type == VAR_NAME_FROM) ? nameFromTable : nameTable;
378             NameEntry nameEntry = (NameEntry) table.get(name);
379             if (nameEntry != null) {
380                 if (type != TAG_DYNAMIC || nameEntry.getType() != TAG_DYNAMIC) {
381                     int line = nameEntry.getNode().getStart().getLineNumber();
382                     err.jspError(n, "jsp.error.tagfile.nameNotUnique",
383                             type, nameEntry.getType(), Integer.toString(line));
384                 }
385             } else {
386                 table.put(name, new NameEntry(type, n, attr));
387             }
388         }
389 
390         /***
391          * Perform miscellean checks after the nodes are visited.
392          */
393         void postCheck() throws JasperException {
394             // Check that var.name-from-attributes has valid values.
395             Iterator iter = nameFromTable.keySet().iterator();
396             while (iter.hasNext()) {
397                 String nameFrom = (String) iter.next();
398                 NameEntry nameEntry = (NameEntry) nameTable.get(nameFrom);
399                 NameEntry nameFromEntry =
400                         (NameEntry) nameFromTable.get(nameFrom);
401                 Node nameFromNode = nameFromEntry.getNode();
402                 if (nameEntry == null) {
403                     err.jspError(nameFromNode,
404                             "jsp.error.tagfile.nameFrom.noAttribute",
405                             nameFrom);
406                 } else {
407                     Node node = nameEntry.getNode();
408                     TagAttributeInfo tagAttr = nameEntry.getTagAttributeInfo();
409                     if (!"java.lang.String".equals(tagAttr.getTypeName())
410                             || !tagAttr.isRequired()
411                             || tagAttr.canBeRequestTime()) {
412                         err.jspError(nameFromNode,
413                                 "jsp.error.tagfile.nameFrom.badAttribute",
414                                 nameFrom,
415                                 Integer.toString(node.getStart().getLineNumber()));
416                     }
417                 }
418             }
419         }
420     }
421 
422     /***
423      * Parses the tag file, and collects information on the directives included
424      * in it.  The method is used to obtain the info on the tag file, when the
425      * handler that it represents is referenced.  The tag file is not compiled
426      * here.
427      *
428      * @param pc         the current ParserController used in this compilation
429      * @param name       the tag name as specified in the TLD
430      * @param tagfile    the path for the tagfile
431      * @param tagLibInfo the TagLibraryInfo object associated with this TagInfo
432      * @return a TagInfo object assembled from the directives in the tag file.
433      */
434     public static TagInfo parseTagFileDirectives(ParserController pc,
435                                                  String name,
436                                                  String path,
437                                                  TagLibraryInfo tagLibInfo)
438             throws JasperException {
439 
440         ErrorDispatcher err = pc.getCompiler().getErrorDispatcher();
441 
442         Node.Nodes page = null;
443         try {
444             page = pc.parseTagFileDirectives(path);
445         } catch (FileNotFoundException e) {
446             err.jspError("jsp.error.file.not.found", path);
447         } catch (IOException e) {
448             err.jspError("jsp.error.file.not.found", path);
449         }
450 
451         TagFileDirectiveVisitor tagFileVisitor
452                 = new TagFileDirectiveVisitor(pc.getCompiler(), tagLibInfo, name,
453                 path);
454         page.visit(tagFileVisitor);
455         tagFileVisitor.postCheck();
456 
457         return tagFileVisitor.getTagInfo();
458     }
459 
460     /***
461      * Compiles and loads a tagfile.
462      */
463     private Class loadTagFile(Compiler compiler,
464                               String tagFilePath, TagInfo tagInfo,
465                               PageInfo parentPageInfo)
466             throws JasperException {
467 
468         JspCompilationContext ctxt = compiler.getCompilationContext();
469         JspRuntimeContext rctxt = ctxt.getRuntimeContext();
470         JspServletWrapper wrapper =
471                 (JspServletWrapper) rctxt.getWrapper(tagFilePath);
472 
473         synchronized (rctxt) {
474             if (wrapper == null) {
475                 wrapper = new JspServletWrapper(ctxt.getServletContext(),
476                         ctxt.getOptions(),
477                         tagFilePath,
478                         tagInfo,
479                         ctxt.getRuntimeContext(),
480                         (URL) ctxt.getTagFileJarUrls().get(tagFilePath));
481                 rctxt.addWrapper(tagFilePath, wrapper);
482 
483                 // Use same classloader and classpath for compiling tag files
484                 wrapper.getJspEngineContext().setClassLoader(
485                         (URLClassLoader) ctxt.getClassLoader());
486                 wrapper.getJspEngineContext().setClassPath(ctxt.getClassPath());
487             } else {
488                 // Make sure that JspCompilationContext gets the latest TagInfo
489                 // for the tag file.  TagInfo instance was created the last
490                 // time the tag file was scanned for directives, and the tag
491                 // file may have been modified since then.
492                 wrapper.getJspEngineContext().setTagInfo(tagInfo);
493             }
494 
495             Class tagClazz;
496             int tripCount = wrapper.incTripCount();
497             try {
498                 if (tripCount > 0) {
499                     // When tripCount is greater than zero, a circular
500                     // dependency exists.  The circularily dependant tag
501                     // file is compiled in prototype mode, to avoid infinite
502                     // recursion.
503 
504                     JspServletWrapper tempWrapper
505                             = new JspServletWrapper(ctxt.getServletContext(),
506                             ctxt.getOptions(),
507                             tagFilePath,
508                             tagInfo,
509                             ctxt.getRuntimeContext(),
510                             (URL) ctxt.getTagFileJarUrls().get(tagFilePath));
511                     tagClazz = tempWrapper.loadTagFilePrototype();
512                     tempVector.add(
513                             tempWrapper.getJspEngineContext().getCompiler());
514                 } else {
515                     tagClazz = wrapper.loadTagFile();
516                 }
517             } finally {
518                 wrapper.decTripCount();
519             }
520 
521             // Add the dependants for this tag file to its parent's
522             // dependant list.  The only reliable dependency information
523             // can only be obtained from the tag instance.
524             try {
525                 Object tagIns = tagClazz.newInstance();
526                 if (tagIns instanceof JspSourceDependent) {
527                     Iterator iter =
528                             ((List) ((JspSourceDependent) tagIns).getDependants()).iterator();
529                     while (iter.hasNext()) {
530                         parentPageInfo.addDependant((String) iter.next());
531                     }
532                 }
533             } catch (Exception e) {
534                 // ignore errors
535             }
536 
537             return tagClazz;
538         }
539     }
540 
541 
542     /*
543      * Visitor which scans the page and looks for tag handlers that are tag
544      * files, compiling (if necessary) and loading them.
545      */
546     private class TagFileLoaderVisitor extends Node.Visitor {
547 
548         private Compiler compiler;
549         private PageInfo pageInfo;
550 
551         TagFileLoaderVisitor(Compiler compiler) {
552 
553             this.compiler = compiler;
554             this.pageInfo = compiler.getPageInfo();
555         }
556 
557         public void visit(Node.CustomTag n) throws JasperException {
558             TagFileInfo tagFileInfo = n.getTagFileInfo();
559             if (tagFileInfo != null) {
560                 String tagFilePath = tagFileInfo.getPath();
561                 JspCompilationContext ctxt = compiler.getCompilationContext();
562                 if (ctxt.getTagFileJarUrls().get(tagFilePath) == null) {
563                     // Omit tag file dependency info on jar files for now.
564                     pageInfo.addDependant(tagFilePath);
565                 }
566                 Class c = loadTagFile(compiler, tagFilePath, n.getTagInfo(),
567                         pageInfo);
568                 n.setTagHandlerClass(c);
569             }
570             visitBody(n);
571         }
572     }
573 
574     /***
575      * Implements a phase of the translation that compiles (if necessary)
576      * the tag files used in a JSP files.  The directives in the tag files
577      * are assumed to have been proccessed and encapsulated as TagFileInfo
578      * in the CustomTag nodes.
579      */
580     public void loadTagFiles(Compiler compiler, Node.Nodes page)
581             throws JasperException {
582 
583         tempVector = new Vector();
584         page.visit(new TagFileLoaderVisitor(compiler));
585     }
586 
587     /***
588      * Removed the java and class files for the tag prototype
589      * generated from the current compilation.
590      *
591      * @param classFileName If non-null, remove only the class file with
592      *                      with this name.
593      */
594     public void removeProtoTypeFiles(String classFileName) {
595         Iterator iter = tempVector.iterator();
596         while (iter.hasNext()) {
597             Compiler c = (Compiler) iter.next();
598             if (classFileName == null) {
599                 c.removeGeneratedClassFiles();
600             } else if (classFileName.equals(
601                     c.getCompilationContext().getClassFileName())) {
602                 c.removeGeneratedClassFiles();
603                 tempVector.remove(c);
604                 return;
605             }
606         }
607     }
608 }
609