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 com.opensymphony.xwork2.util.logging.Logger;
21  import com.opensymphony.xwork2.util.logging.LoggerFactory;
22  import org.apache.struts2.jasper.JasperException;
23  import org.apache.struts2.jasper.JspCompilationContext;
24  import org.apache.struts2.jasper.xmlparser.ParserUtils;
25  import org.apache.struts2.jasper.xmlparser.TreeNode;
26  
27  import javax.servlet.jsp.tagext.*;
28  import java.io.*;
29  import java.net.JarURLConnection;
30  import java.net.URL;
31  import java.util.*;
32  import java.util.jar.JarFile;
33  import java.util.zip.ZipEntry;
34  
35  /***
36   * Implementation of the TagLibraryInfo class from the JSP spec.
37   *
38   * @author Anil K. Vijendran
39   * @author Mandar Raje
40   * @author Pierre Delisle
41   * @author Kin-man Chung
42   * @author Jan Luehe
43   */
44  class TagLibraryInfoImpl extends TagLibraryInfo implements TagConstants {
45  
46      // Logger
47      private Logger log = LoggerFactory.getLogger(TagLibraryInfoImpl.class);
48  
49      private Hashtable jarEntries;
50      private JspCompilationContext ctxt;
51      private ErrorDispatcher err;
52      private ParserController parserController;
53  
54      private final void print(String name, String value, PrintWriter w) {
55          if (value != null) {
56              w.print(name + " = {\n\t");
57              w.print(value);
58              w.print("\n}\n");
59          }
60      }
61  
62      public String toString() {
63          StringWriter sw = new StringWriter();
64          PrintWriter out = new PrintWriter(sw);
65          print("tlibversion", tlibversion, out);
66          print("jspversion", jspversion, out);
67          print("shortname", shortname, out);
68          print("urn", urn, out);
69          print("info", info, out);
70          print("uri", uri, out);
71          print("tagLibraryValidator", "" + tagLibraryValidator, out);
72  
73          for (int i = 0; i < tags.length; i++)
74              out.println(tags[i].toString());
75  
76          for (int i = 0; i < tagFiles.length; i++)
77              out.println(tagFiles[i].toString());
78  
79          for (int i = 0; i < functions.length; i++)
80              out.println(functions[i].toString());
81  
82          return sw.toString();
83      }
84  
85      // XXX FIXME
86      // resolveRelativeUri and/or getResourceAsStream don't seem to properly
87      // handle relative paths when dealing when home and getDocBase are set
88      // the following is a workaround until these problems are resolved.
89      private InputStream getResourceAsStream(String uri)
90              throws FileNotFoundException {
91          try {
92              // see if file exists on the filesystem first
93              String real = ctxt.getRealPath(uri);
94              if (real == null) {
95                  return ctxt.getResourceAsStream(uri);
96              } else {
97                  return new FileInputStream(real);
98              }
99          }
100         catch (FileNotFoundException ex) {
101             // if file not found on filesystem, get the resource through
102             // the context
103             return ctxt.getResourceAsStream(uri);
104         }
105 
106     }
107 
108     /***
109      * Constructor.
110      */
111     public TagLibraryInfoImpl(JspCompilationContext ctxt,
112                               ParserController pc,
113                               String prefix,
114                               String uriIn,
115                               String[] location,
116                               ErrorDispatcher err) throws JasperException {
117         super(prefix, uriIn);
118 
119         this.ctxt = ctxt;
120         this.parserController = pc;
121         this.err = err;
122         InputStream in = null;
123         JarFile jarFile = null;
124 
125         if (location == null) {
126             // The URI points to the TLD itself or to a JAR file in which the
127             // TLD is stored
128             location = generateTLDLocation(uri, ctxt);
129         }
130 
131         try {
132             if (!location[0].endsWith("jar")) {
133                 // Location points to TLD file
134                 try {
135                     in = getResourceAsStream(location[0]);
136                     if (in == null) {
137                         throw new FileNotFoundException(location[0]);
138                     }
139                 } catch (FileNotFoundException ex) {
140                     err.jspError("jsp.error.file.not.found", location[0]);
141                 }
142 
143                 parseTLD(ctxt, location[0], in, null);
144                 // Add TLD to dependency list
145                 PageInfo pageInfo = ctxt.createCompiler().getPageInfo();
146                 if (pageInfo != null) {
147                     pageInfo.addDependant(location[0]);
148                 }
149             } else {
150                 // Tag library is packaged in JAR file
151                 try {
152                     URL jarFileUrl = new URL("jar:" + location[0] + "!/");
153                     JarURLConnection conn =
154                             (JarURLConnection) jarFileUrl.openConnection();
155                     conn.setUseCaches(false);
156                     conn.connect();
157                     jarFile = conn.getJarFile();
158                     ZipEntry jarEntry = jarFile.getEntry(location[1]);
159                     in = jarFile.getInputStream(jarEntry);
160                     parseTLD(ctxt, location[0], in, jarFileUrl);
161                 } catch (Exception ex) {
162                     err.jspError("jsp.error.tld.unable_to_read", location[0],
163                             location[1], ex.toString());
164                 }
165             }
166         } finally {
167             if (in != null) {
168                 try {
169                     in.close();
170                 } catch (Throwable t) {
171                 }
172             }
173             if (jarFile != null) {
174                 try {
175                     jarFile.close();
176                 } catch (Throwable t) {
177                 }
178             }
179         }
180 
181     }
182 
183     /*
184     * @param ctxt The JSP compilation context
185     * @param uri The TLD's uri
186     * @param in The TLD's input stream
187     * @param jarFileUrl The JAR file containing the TLD, or null if the tag
188     * library is not packaged in a JAR
189     */
190     private void parseTLD(JspCompilationContext ctxt,
191                           String uri, InputStream in, URL jarFileUrl)
192             throws JasperException {
193         Vector tagVector = new Vector();
194         Vector tagFileVector = new Vector();
195         Hashtable functionTable = new Hashtable();
196 
197         // Create an iterator over the child elements of our <taglib> element
198         ParserUtils pu = new ParserUtils();
199         TreeNode tld = pu.parseXMLDocument(uri, in);
200 
201         // Check to see if the <taglib> root element contains a 'version'
202         // attribute, which was added in JSP 2.0 to replace the <jsp-version>
203         // subelement
204         this.jspversion = tld.findAttribute("version");
205 
206         // Process each child element of our <taglib> element
207         Iterator list = tld.findChildren();
208 
209         while (list.hasNext()) {
210             TreeNode element = (TreeNode) list.next();
211             String tname = element.getName();
212 
213             if ("tlibversion".equals(tname)                    // JSP 1.1
214                     || "tlib-version".equals(tname)) {     // JSP 1.2
215                 this.tlibversion = element.getBody();
216             } else if ("jspversion".equals(tname)
217                     || "jsp-version".equals(tname)) {
218                 this.jspversion = element.getBody();
219             } else if ("shortname".equals(tname) ||
220                     "short-name".equals(tname))
221                 this.shortname = element.getBody();
222             else if ("uri".equals(tname))
223                 this.urn = element.getBody();
224             else if ("info".equals(tname) ||
225                     "description".equals(tname))
226                 this.info = element.getBody();
227             else if ("validator".equals(tname))
228                 this.tagLibraryValidator = createValidator(element);
229             else if ("tag".equals(tname))
230                 tagVector.addElement(createTagInfo(element, jspversion));
231             else if ("tag-file".equals(tname)) {
232                 TagFileInfo tagFileInfo = createTagFileInfo(element, uri,
233                         jarFileUrl);
234                 tagFileVector.addElement(tagFileInfo);
235             } else if ("function".equals(tname)) {         // JSP2.0
236                 FunctionInfo funcInfo = createFunctionInfo(element);
237                 String funcName = funcInfo.getName();
238                 if (functionTable.containsKey(funcName)) {
239                     err.jspError("jsp.error.tld.fn.duplicate.name",
240                             funcName, uri);
241 
242                 }
243                 functionTable.put(funcName, funcInfo);
244             } else if ("display-name".equals(tname) ||    // Ignored elements
245                     "small-icon".equals(tname) ||
246                     "large-icon".equals(tname) ||
247                     "listener".equals(tname)) {
248                 ;
249             } else if ("taglib-extension".equals(tname)) {
250                 // Recognized but ignored
251             } else {
252                 if (log.isWarnEnabled()) {
253                     log.warn(Localizer.getMessage(
254                             "jsp.warning.unknown.element.in.taglib", tname));
255                 }
256             }
257 
258         }
259 
260         if (tlibversion == null) {
261             err.jspError("jsp.error.tld.mandatory.element.missing",
262                     "tlib-version");
263         }
264         if (jspversion == null) {
265             err.jspError("jsp.error.tld.mandatory.element.missing",
266                     "jsp-version");
267         }
268 
269         this.tags = new TagInfo[tagVector.size()];
270         tagVector.copyInto(this.tags);
271 
272         this.tagFiles = new TagFileInfo[tagFileVector.size()];
273         tagFileVector.copyInto(this.tagFiles);
274 
275         this.functions = new FunctionInfo[functionTable.size()];
276         int i = 0;
277         Enumeration enumeration = functionTable.elements();
278         while (enumeration.hasMoreElements()) {
279             this.functions[i++] = (FunctionInfo) enumeration.nextElement();
280         }
281     }
282 
283     /*
284     * @param uri The uri of the TLD
285     * @param ctxt The compilation context
286     *
287     * @return String array whose first element denotes the path to the TLD.
288     * If the path to the TLD points to a jar file, then the second element
289     * denotes the name of the TLD entry in the jar file, which is hardcoded
290     * to META-INF/taglib.tld.
291     */
292     private String[] generateTLDLocation(String uri,
293                                          JspCompilationContext ctxt)
294             throws JasperException {
295 
296         int uriType = TldLocationsCache.uriType(uri);
297         if (uriType == TldLocationsCache.ABS_URI) {
298             err.jspError("jsp.error.taglibDirective.absUriCannotBeResolved",
299                     uri);
300         } else if (uriType == TldLocationsCache.NOROOT_REL_URI) {
301             //TODO: implement for tld
302             //uri = ctxt.resolveRelativeUri(uri);
303         }
304 
305         String[] location = new String[2];
306         location[0] = uri;
307         if (location[0].endsWith("jar")) {
308             URL url = null;
309             try {
310                 url = ctxt.getResource(location[0]);
311             } catch (Exception ex) {
312                 err.jspError("jsp.error.tld.unable_to_get_jar", location[0],
313                         ex.toString());
314             }
315             if (url == null) {
316                 err.jspError("jsp.error.tld.missing_jar", location[0]);
317             }
318             location[0] = url.toString();
319             location[1] = "META-INF/taglib.tld";
320         }
321 
322         return location;
323     }
324 
325     private TagInfo createTagInfo(TreeNode elem, String jspVersion)
326             throws JasperException {
327 
328         String tagName = null;
329         String tagClassName = null;
330         String teiClassName = null;
331 
332         /*
333          * Default body content for JSP 1.2 tag handlers (<body-content> has
334          * become mandatory in JSP 2.0, because the default would be invalid
335          * for simple tag handlers)
336          */
337         String bodycontent = "JSP";
338 
339         String info = null;
340         String displayName = null;
341         String smallIcon = null;
342         String largeIcon = null;
343         boolean dynamicAttributes = false;
344 
345         Vector attributeVector = new Vector();
346         Vector variableVector = new Vector();
347         Iterator list = elem.findChildren();
348         while (list.hasNext()) {
349             TreeNode element = (TreeNode) list.next();
350             String tname = element.getName();
351 
352             if ("name".equals(tname)) {
353                 tagName = element.getBody();
354             } else if ("tagclass".equals(tname) ||
355                     "tag-class".equals(tname)) {
356                 tagClassName = element.getBody();
357             } else if ("teiclass".equals(tname) ||
358                     "tei-class".equals(tname)) {
359                 teiClassName = element.getBody();
360             } else if ("bodycontent".equals(tname) ||
361                     "body-content".equals(tname)) {
362                 bodycontent = element.getBody();
363             } else if ("display-name".equals(tname)) {
364                 displayName = element.getBody();
365             } else if ("small-icon".equals(tname)) {
366                 smallIcon = element.getBody();
367             } else if ("large-icon".equals(tname)) {
368                 largeIcon = element.getBody();
369             } else if ("icon".equals(tname)) {
370                 TreeNode icon = element.findChild("small-icon");
371                 if (icon != null) {
372                     smallIcon = icon.getBody();
373                 }
374                 icon = element.findChild("large-icon");
375                 if (icon != null) {
376                     largeIcon = icon.getBody();
377                 }
378             } else if ("info".equals(tname) ||
379                     "description".equals(tname)) {
380                 info = element.getBody();
381             } else if ("variable".equals(tname)) {
382                 variableVector.addElement(createVariable(element));
383             } else if ("attribute".equals(tname)) {
384                 attributeVector.addElement(createAttribute(element, jspVersion));
385             } else if ("dynamic-attributes".equals(tname)) {
386                 dynamicAttributes = JspUtil.booleanValue(element.getBody());
387             } else if ("example".equals(tname)) {
388                 // Ignored elements
389             } else if ("tag-extension".equals(tname)) {
390                 // Ignored
391             } else {
392                 if (log.isWarnEnabled()) {
393                     log.warn(Localizer.getMessage(
394                             "jsp.warning.unknown.element.in.tag", tname));
395                 }
396             }
397         }
398 
399         TagExtraInfo tei = null;
400         if (teiClassName != null && !teiClassName.equals("")) {
401             try {
402                 Class teiClass = ctxt.getClassLoader().loadClass(teiClassName);
403                 tei = (TagExtraInfo) teiClass.newInstance();
404             } catch (Exception e) {
405                 err.jspError("jsp.error.teiclass.instantiation", teiClassName,
406                         e);
407             }
408         }
409 
410         TagAttributeInfo[] tagAttributeInfo
411                 = new TagAttributeInfo[attributeVector.size()];
412         attributeVector.copyInto(tagAttributeInfo);
413 
414         TagVariableInfo[] tagVariableInfos
415                 = new TagVariableInfo[variableVector.size()];
416         variableVector.copyInto(tagVariableInfos);
417 
418         TagInfo taginfo = new TagInfo(tagName,
419                 tagClassName,
420                 bodycontent,
421                 info,
422                 this,
423                 tei,
424                 tagAttributeInfo,
425                 displayName,
426                 smallIcon,
427                 largeIcon,
428                 tagVariableInfos,
429                 dynamicAttributes);
430         return taginfo;
431     }
432 
433     /*
434      * Parses the tag file directives of the given TagFile and turns them into
435      * a TagInfo.
436      *
437      * @param elem The <tag-file> element in the TLD
438      * @param uri The location of the TLD, in case the tag file is specified
439      * relative to it
440      * @param jarFile The JAR file, in case the tag file is packaged in a JAR
441      *
442      * @return TagInfo correspoding to tag file directives
443      */
444     private TagFileInfo createTagFileInfo(TreeNode elem, String uri,
445                                           URL jarFileUrl)
446             throws JasperException {
447 
448         String name = null;
449         String path = null;
450 
451         Iterator list = elem.findChildren();
452         while (list.hasNext()) {
453             TreeNode child = (TreeNode) list.next();
454             String tname = child.getName();
455             if ("name".equals(tname)) {
456                 name = child.getBody();
457             } else if ("path".equals(tname)) {
458                 path = child.getBody();
459             } else if ("example".equals(tname)) {
460                 // Ignore <example> element: Bugzilla 33538
461             } else if ("tag-extension".equals(tname)) {
462                 // Ignore <tag-extension> element: Bugzilla 33538
463             } else if ("icon".equals(tname) ||
464                     "display-name".equals(tname) ||
465                     "description".equals(tname)) {
466                 // Ignore these elements: Bugzilla 38015
467             } else {
468                 if (log.isWarnEnabled()) {
469                     log.warn(Localizer.getMessage(
470                             "jsp.warning.unknown.element.in.tagfile", tname));
471                 }
472             }
473         }
474 
475         if (path.startsWith("/META-INF/tags")) {
476             // Tag file packaged in JAR
477             // check if jarFileUrl is not null before adding it, or this will
478             // cause a NPE.
479             if (jarFileUrl != null) {
480                 ctxt.getTagFileJarUrls().put(path, jarFileUrl);
481             }
482         } else if (!path.startsWith("/WEB-INF/tags")) {
483             err.jspError("jsp.error.tagfile.illegalPath", path);
484         }
485 
486         TagInfo tagInfo
487                 = TagFileProcessor.parseTagFileDirectives(parserController, name,
488                 path, this);
489         return new TagFileInfo(name, path, tagInfo);
490     }
491 
492     TagAttributeInfo createAttribute(TreeNode elem, String jspVersion) {
493         String name = null;
494         String type = null;
495         boolean required = false, rtexprvalue = false, reqTime = false,
496                 isFragment = false;
497 
498         Iterator list = elem.findChildren();
499         while (list.hasNext()) {
500             TreeNode element = (TreeNode) list.next();
501             String tname = element.getName();
502 
503             if ("name".equals(tname)) {
504                 name = element.getBody();
505             } else if ("required".equals(tname)) {
506                 String s = element.getBody();
507                 if (s != null)
508                     required = JspUtil.booleanValue(s);
509             } else if ("rtexprvalue".equals(tname)) {
510                 String s = element.getBody();
511                 if (s != null)
512                     rtexprvalue = JspUtil.booleanValue(s);
513             } else if ("type".equals(tname)) {
514                 type = element.getBody();
515                 if ("1.2".equals(jspVersion)
516                         && (type.equals("Boolean")
517                         || type.equals("Byte")
518                         || type.equals("Character")
519                         || type.equals("Double")
520                         || type.equals("Float")
521                         || type.equals("Integer")
522                         || type.equals("Long")
523                         || type.equals("Object")
524                         || type.equals("Short")
525                         || type.equals("String"))) {
526                     type = "java.lang." + type;
527                 }
528             } else if ("fragment".equals(tname)) {
529                 String s = element.getBody();
530                 if (s != null) {
531                     isFragment = JspUtil.booleanValue(s);
532                 }
533             } else if ("description".equals(tname) ||    // Ignored elements
534                     false) {
535                 ;
536             } else {
537                 if (log.isWarnEnabled()) {
538                     log.warn(Localizer.getMessage(
539                             "jsp.warning.unknown.element.in.attribute", tname));
540                 }
541             }
542         }
543 
544         if (isFragment) {
545             /*
546              * According to JSP.C-3 ("TLD Schema Element Structure - tag"), 
547              * 'type' and 'rtexprvalue' must not be specified if 'fragment'
548              * has been specified (this will be enforced by validating parser).
549              * Also, if 'fragment' is TRUE, 'type' is fixed at
550              * javax.servlet.jsp.tagext.JspFragment, and 'rtexprvalue' is
551              * fixed at true. See also JSP.8.5.2.
552              */
553             type = "javax.servlet.jsp.tagext.JspFragment";
554             rtexprvalue = true;
555         }
556 
557         if (!rtexprvalue) {
558             // According to JSP spec, for static values (those determined at
559             // translation time) the type is fixed at java.lang.String.
560             type = "java.lang.String";
561         }
562 
563         return new TagAttributeInfo(name, required, type, rtexprvalue,
564                 isFragment);
565     }
566 
567     TagVariableInfo createVariable(TreeNode elem) {
568         String nameGiven = null;
569         String nameFromAttribute = null;
570         String className = "java.lang.String";
571         boolean declare = true;
572         int scope = VariableInfo.NESTED;
573 
574         Iterator list = elem.findChildren();
575         while (list.hasNext()) {
576             TreeNode element = (TreeNode) list.next();
577             String tname = element.getName();
578             if ("name-given".equals(tname))
579                 nameGiven = element.getBody();
580             else if ("name-from-attribute".equals(tname))
581                 nameFromAttribute = element.getBody();
582             else if ("variable-class".equals(tname))
583                 className = element.getBody();
584             else if ("declare".equals(tname)) {
585                 String s = element.getBody();
586                 if (s != null)
587                     declare = JspUtil.booleanValue(s);
588             } else if ("scope".equals(tname)) {
589                 String s = element.getBody();
590                 if (s != null) {
591                     if ("NESTED".equals(s)) {
592                         scope = VariableInfo.NESTED;
593                     } else if ("AT_BEGIN".equals(s)) {
594                         scope = VariableInfo.AT_BEGIN;
595                     } else if ("AT_END".equals(s)) {
596                         scope = VariableInfo.AT_END;
597                     }
598                 }
599             } else if ("description".equals(tname) ||    // Ignored elements
600                     false) {
601             } else {
602                 if (log.isWarnEnabled()) {
603                     log.warn(Localizer.getMessage(
604                             "jsp.warning.unknown.element.in.variable", tname));
605                 }
606             }
607         }
608         return new TagVariableInfo(nameGiven, nameFromAttribute,
609                 className, declare, scope);
610     }
611 
612     private TagLibraryValidator createValidator(TreeNode elem)
613             throws JasperException {
614 
615         String validatorClass = null;
616         Map initParams = new Hashtable();
617 
618         Iterator list = elem.findChildren();
619         while (list.hasNext()) {
620             TreeNode element = (TreeNode) list.next();
621             String tname = element.getName();
622             if ("validator-class".equals(tname))
623                 validatorClass = element.getBody();
624             else if ("init-param".equals(tname)) {
625                 String[] initParam = createInitParam(element);
626                 initParams.put(initParam[0], initParam[1]);
627             } else if ("description".equals(tname) ||    // Ignored elements
628                     false) {
629             } else {
630                 if (log.isWarnEnabled()) {
631                     log.warn(Localizer.getMessage(
632                             "jsp.warning.unknown.element.in.validator", tname));
633                 }
634             }
635         }
636 
637         TagLibraryValidator tlv = null;
638         if (validatorClass != null && !validatorClass.equals("")) {
639             try {
640                 Class tlvClass =
641                         ctxt.getClassLoader().loadClass(validatorClass);
642                 tlv = (TagLibraryValidator) tlvClass.newInstance();
643             } catch (Exception e) {
644                 err.jspError("jsp.error.tlvclass.instantiation",
645                         validatorClass, e);
646             }
647         }
648         if (tlv != null) {
649             tlv.setInitParameters(initParams);
650         }
651         return tlv;
652     }
653 
654     String[] createInitParam(TreeNode elem) {
655         String[] initParam = new String[2];
656 
657         Iterator list = elem.findChildren();
658         while (list.hasNext()) {
659             TreeNode element = (TreeNode) list.next();
660             String tname = element.getName();
661             if ("param-name".equals(tname)) {
662                 initParam[0] = element.getBody();
663             } else if ("param-value".equals(tname)) {
664                 initParam[1] = element.getBody();
665             } else if ("description".equals(tname)) {
666                 ; // Do nothing
667             } else {
668                 if (log.isWarnEnabled()) {
669                     log.warn(Localizer.getMessage(
670                             "jsp.warning.unknown.element.in.initParam", tname));
671                 }
672             }
673         }
674         return initParam;
675     }
676 
677     FunctionInfo createFunctionInfo(TreeNode elem) {
678 
679         String name = null;
680         String klass = null;
681         String signature = null;
682 
683         Iterator list = elem.findChildren();
684         while (list.hasNext()) {
685             TreeNode element = (TreeNode) list.next();
686             String tname = element.getName();
687 
688             if ("name".equals(tname)) {
689                 name = element.getBody();
690             } else if ("function-class".equals(tname)) {
691                 klass = element.getBody();
692             } else if ("function-signature".equals(tname)) {
693                 signature = element.getBody();
694             } else if ("display-name".equals(tname) ||    // Ignored elements
695                     "small-icon".equals(tname) ||
696                     "large-icon".equals(tname) ||
697                     "description".equals(tname) ||
698                     "example".equals(tname)) {
699             } else {
700                 if (log.isWarnEnabled()) {
701                     log.warn(Localizer.getMessage(
702                             "jsp.warning.unknown.element.in.function", tname));
703                 }
704             }
705         }
706 
707         return new FunctionInfo(name, klass, signature);
708     }
709 
710 
711     //**********************************************************************
712     // Until javax.servlet.jsp.tagext.TagLibraryInfo is fixed
713 
714     /**
715      * The instance (if any) for the TagLibraryValidator class.
716      *
717      * @return The TagLibraryValidator instance, if any.
718      */
719     public TagLibraryValidator getTagLibraryValidator() {
720         return tagLibraryValidator;
721     }
722 
723     /***
724      * Translation-time validation of the XML document
725      * associated with the JSP page.
726      * This is a convenience method on the associated
727      * TagLibraryValidator class.
728      *
729      * @param thePage The JSP page object
730      * @return A string indicating whether the page is valid or not.
731      */
732     public ValidationMessage[] validate(PageData thePage) {
733         TagLibraryValidator tlv = getTagLibraryValidator();
734         if (tlv == null) return null;
735 
736         String uri = getURI();
737         if (uri.startsWith("/")) {
738             uri = URN_JSPTLD + uri;
739         }
740 
741         return tlv.validate(getPrefixString(), uri, thePage);
742     }
743 
744     protected TagLibraryValidator tagLibraryValidator;
745 }