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.jasper.compiler;
19
20 import java.io.InputStream;
21 import java.net.JarURLConnection;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.net.URLClassLoader;
25 import java.net.URLConnection;
26 import java.util.Enumeration;
27 import java.util.Hashtable;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32 import java.util.jar.JarEntry;
33 import java.util.jar.JarFile;
34 import org.xml.sax.InputSource;
35
36 import javax.servlet.ServletContext;
37
38 import org.apache.jasper.Constants;
39 import org.apache.jasper.JasperException;
40 import org.apache.jasper.xmlparser.ParserUtils;
41 import org.apache.jasper.xmlparser.TreeNode;
42 import org.apache.juli.logging.Log;
43 import org.apache.juli.logging.LogFactory;
44
45 /**
46 * A container for all tag libraries that are defined "globally"
47 * for the web application.
48 *
49 * Tag Libraries can be defined globally in one of two ways:
50 * 1. Via <taglib> elements in web.xml:
51 * the uri and location of the tag-library are specified in
52 * the <taglib> element.
53 * 2. Via packaged jar files that contain .tld files
54 * within the META-INF directory, or some subdirectory
55 * of it. The taglib is 'global' if it has the <uri>
56 * element defined.
57 *
58 * A mapping between the taglib URI and its associated TaglibraryInfoImpl
59 * is maintained in this container.
60 * Actually, that's what we'd like to do. However, because of the
61 * way the classes TagLibraryInfo and TagInfo have been defined,
62 * it is not currently possible to share an instance of TagLibraryInfo
63 * across page invocations. A bug has been submitted to the spec lead.
64 * In the mean time, all we do is save the 'location' where the
65 * TLD associated with a taglib URI can be found.
66 *
67 * When a JSP page has a taglib directive, the mappings in this container
68 * are first searched (see method getLocation()).
69 * If a mapping is found, then the location of the TLD is returned.
70 * If no mapping is found, then the uri specified
71 * in the taglib directive is to be interpreted as the location for
72 * the TLD of this tag library.
73 *
74 * @author Pierre Delisle
75 * @author Jan Luehe
76 */
77
78 public class TldLocationsCache {
79
80 // Logger
81 private Log log = LogFactory.getLog(TldLocationsCache.class);
82
83 /**
84 * The types of URI one may specify for a tag library
85 */
86 public static final int ABS_URI = 0;
87 public static final int ROOT_REL_URI = 1;
88 public static final int NOROOT_REL_URI = 2;
89
90 private static final String WEB_XML = "/WEB-INF/web.xml";
91 private static final String FILE_PROTOCOL = "file:";
92 private static final String JAR_FILE_SUFFIX = ".jar";
93
94 // Names of JARs that are known not to contain any TLDs
95 private static HashSet<String> noTldJars;
96
97 /**
98 * The mapping of the 'global' tag library URI to the location (resource
99 * path) of the TLD associated with that tag library. The location is
100 * returned as a String array:
101 * [0] The location
102 * [1] If the location is a jar file, this is the location of the tld.
103 */
104 private Hashtable mappings;
105
106 private boolean initialized;
107 private ServletContext ctxt;
108 private boolean redeployMode;
109
110 //*********************************************************************
111 // Constructor and Initilizations
112
113 /*
114 * Initializes the set of JARs that are known not to contain any TLDs
115 */
116 static {
117 noTldJars = new HashSet<String>();
118 // Bootstrap JARs
119 noTldJars.add("bootstrap.jar");
120 noTldJars.add("commons-daemon.jar");
121 noTldJars.add("tomcat-juli.jar");
122 // Main JARs
123 noTldJars.add("annotations-api.jar");
124 noTldJars.add("catalina.jar");
125 noTldJars.add("catalina-ant.jar");
126 noTldJars.add("catalina-ha.jar");
127 noTldJars.add("catalina-tribes.jar");
128 noTldJars.add("el-api.jar");
129 noTldJars.add("jasper.jar");
130 noTldJars.add("jasper-el.jar");
131 noTldJars.add("jasper-jdt.jar");
132 noTldJars.add("jsp-api.jar");
133 noTldJars.add("servlet-api.jar");
134 noTldJars.add("tomcat-coyote.jar");
135 noTldJars.add("tomcat-dbcp.jar");
136 // i18n JARs
137 noTldJars.add("tomcat-i18n-en.jar");
138 noTldJars.add("tomcat-i18n-es.jar");
139 noTldJars.add("tomcat-i18n-fr.jar");
140 noTldJars.add("tomcat-i18n-ja.jar");
141 // Misc JARs not included with Tomcat
142 noTldJars.add("ant.jar");
143 noTldJars.add("commons-dbcp.jar");
144 noTldJars.add("commons-beanutils.jar");
145 noTldJars.add("commons-fileupload-1.0.jar");
146 noTldJars.add("commons-pool.jar");
147 noTldJars.add("commons-digester.jar");
148 noTldJars.add("commons-logging.jar");
149 noTldJars.add("commons-collections.jar");
150 noTldJars.add("jmx.jar");
151 noTldJars.add("jmx-tools.jar");
152 noTldJars.add("xercesImpl.jar");
153 noTldJars.add("xmlParserAPIs.jar");
154 noTldJars.add("xml-apis.jar");
155 // JARs from J2SE runtime
156 noTldJars.add("sunjce_provider.jar");
157 noTldJars.add("ldapsec.jar");
158 noTldJars.add("localedata.jar");
159 noTldJars.add("dnsns.jar");
160 noTldJars.add("tools.jar");
161 noTldJars.add("sunpkcs11.jar");
162 }
163
164 public TldLocationsCache(ServletContext ctxt) {
165 this(ctxt, true);
166 }
167
168 /** Constructor.
169 *
170 * @param ctxt the servlet context of the web application in which Jasper
171 * is running
172 * @param redeployMode if true, then the compiler will allow redeploying
173 * a tag library from the same jar, at the expense of slowing down the
174 * server a bit. Note that this may only work on JDK 1.3.1_01a and later,
175 * because of JDK bug 4211817 fixed in this release.
176 * If redeployMode is false, a faster but less capable mode will be used.
177 */
178 public TldLocationsCache(ServletContext ctxt, boolean redeployMode) {
179 this.ctxt = ctxt;
180 this.redeployMode = redeployMode;
181 mappings = new Hashtable();
182 initialized = false;
183 }
184
185 /**
186 * Sets the list of JARs that are known not to contain any TLDs.
187 *
188 * @param jarNames List of comma-separated names of JAR files that are
189 * known not to contain any TLDs
190 */
191 public static void setNoTldJars(String jarNames) {
192 if (jarNames != null) {
193 noTldJars.clear();
194 StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
195 while (tokenizer.hasMoreElements()) {
196 noTldJars.add(tokenizer.nextToken());
197 }
198 }
199 }
200
201 /**
202 * Gets the 'location' of the TLD associated with the given taglib 'uri'.
203 *
204 * Returns null if the uri is not associated with any tag library 'exposed'
205 * in the web application. A tag library is 'exposed' either explicitly in
206 * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
207 * in a jar file (WEB-INF/lib).
208 *
209 * @param uri The taglib uri
210 *
211 * @return An array of two Strings: The first element denotes the real
212 * path to the TLD. If the path to the TLD points to a jar file, then the
213 * second element denotes the name of the TLD entry in the jar file.
214 * Returns null if the uri is not associated with any tag library 'exposed'
215 * in the web application.
216 */
217 public String[] getLocation(String uri) throws JasperException {
218 if (!initialized) {
219 init();
220 }
221 return (String[]) mappings.get(uri);
222 }
223
224 /**
225 * Returns the type of a URI:
226 * ABS_URI
227 * ROOT_REL_URI
228 * NOROOT_REL_URI
229 */
230 public static int uriType(String uri) {
231 if (uri.indexOf(':') != -1) {
232 return ABS_URI;
233 } else if (uri.startsWith("/")) {
234 return ROOT_REL_URI;
235 } else {
236 return NOROOT_REL_URI;
237 }
238 }
239
240 private void init() throws JasperException {
241 if (initialized) return;
242 try {
243 processWebDotXml();
244 scanJars();
245 processTldsInFileSystem("/WEB-INF/");
246 initialized = true;
247 } catch (Exception ex) {
248 throw new JasperException(Localizer.getMessage(
249 "jsp.error.internal.tldinit", ex.getMessage()));
250 }
251 }
252
253 /*
254 * Populates taglib map described in web.xml.
255 */
256 private void processWebDotXml() throws Exception {
257
258 InputStream is = null;
259
260 try {
261 // Acquire input stream to web application deployment descriptor
262 String altDDName = (String)ctxt.getAttribute(
263 Constants.ALT_DD_ATTR);
264 URL uri = null;
265 if (altDDName != null) {
266 try {
267 uri = new URL(FILE_PROTOCOL+altDDName.replace('\\', '/'));
268 } catch (MalformedURLException e) {
269 if (log.isWarnEnabled()) {
270 log.warn(Localizer.getMessage(
271 "jsp.error.internal.filenotfound",
272 altDDName));
273 }
274 }
275 } else {
276 uri = ctxt.getResource(WEB_XML);
277 if (uri == null && log.isWarnEnabled()) {
278 log.warn(Localizer.getMessage(
279 "jsp.error.internal.filenotfound",
280 WEB_XML));
281 }
282 }
283
284 if (uri == null) {
285 return;
286 }
287 is = uri.openStream();
288 InputSource ip = new InputSource(is);
289 ip.setSystemId(uri.toExternalForm());
290
291 // Parse the web application deployment descriptor
292 TreeNode webtld = null;
293 // altDDName is the absolute path of the DD
294 if (altDDName != null) {
295 webtld = new ParserUtils().parseXMLDocument(altDDName, ip);
296 } else {
297 webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip);
298 }
299
300 // Allow taglib to be an element of the root or jsp-config (JSP2.0)
301 TreeNode jspConfig = webtld.findChild("jsp-config");
302 if (jspConfig != null) {
303 webtld = jspConfig;
304 }
305 Iterator taglibs = webtld.findChildren("taglib");
306 while (taglibs.hasNext()) {
307
308 // Parse the next <taglib> element
309 TreeNode taglib = (TreeNode) taglibs.next();
310 String tagUri = null;
311 String tagLoc = null;
312 TreeNode child = taglib.findChild("taglib-uri");
313 if (child != null)
314 tagUri = child.getBody();
315 child = taglib.findChild("taglib-location");
316 if (child != null)
317 tagLoc = child.getBody();
318
319 // Save this location if appropriate
320 if (tagLoc == null)
321 continue;
322 if (uriType(tagLoc) == NOROOT_REL_URI)
323 tagLoc = "/WEB-INF/" + tagLoc;
324 String tagLoc2 = null;
325 if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
326 tagLoc = ctxt.getResource(tagLoc).toString();
327 tagLoc2 = "META-INF/taglib.tld";
328 }
329 mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
330 }
331 } finally {
332 if (is != null) {
333 try {
334 is.close();
335 } catch (Throwable t) {}
336 }
337 }
338 }
339
340 /**
341 * Scans the given JarURLConnection for TLD files located in META-INF
342 * (or a subdirectory of it), adding an implicit map entry to the taglib
343 * map for any TLD that has a <uri> element.
344 *
345 * @param conn The JarURLConnection to the JAR file to scan
346 * @param ignore true if any exceptions raised when processing the given
347 * JAR should be ignored, false otherwise
348 */
349 private void scanJar(JarURLConnection conn, boolean ignore)
350 throws JasperException {
351
352 JarFile jarFile = null;
353 String resourcePath = conn.getJarFileURL().toString();
354 try {
355 if (redeployMode) {
356 conn.setUseCaches(false);
357 }
358 jarFile = conn.getJarFile();
359 Enumeration entries = jarFile.entries();
360 while (entries.hasMoreElements()) {
361 JarEntry entry = (JarEntry) entries.nextElement();
362 String name = entry.getName();
363 if (!name.startsWith("META-INF/")) continue;
364 if (!name.endsWith(".tld")) continue;
365 InputStream stream = jarFile.getInputStream(entry);
366 try {
367 String uri = getUriFromTld(resourcePath, stream);
368 // Add implicit map entry only if its uri is not already
369 // present in the map
370 if (uri != null && mappings.get(uri) == null) {
371 mappings.put(uri, new String[]{ resourcePath, name });
372 }
373 } finally {
374 if (stream != null) {
375 try {
376 stream.close();
377 } catch (Throwable t) {
378 // do nothing
379 }
380 }
381 }
382 }
383 } catch (Exception ex) {
384 if (!redeployMode) {
385 // if not in redeploy mode, close the jar in case of an error
386 if (jarFile != null) {
387 try {
388 jarFile.close();
389 } catch (Throwable t) {
390 // ignore
391 }
392 }
393 }
394 if (!ignore) {
395 throw new JasperException(ex);
396 }
397 } finally {
398 if (redeployMode) {
399 // if in redeploy mode, always close the jar
400 if (jarFile != null) {
401 try {
402 jarFile.close();
403 } catch (Throwable t) {
404 // ignore
405 }
406 }
407 }
408 }
409 }
410
411 /*
412 * Searches the filesystem under /WEB-INF for any TLD files, and adds
413 * an implicit map entry to the taglib map for any TLD that has a <uri>
414 * element.
415 */
416 private void processTldsInFileSystem(String startPath)
417 throws Exception {
418
419 Set dirList = ctxt.getResourcePaths(startPath);
420 if (dirList != null) {
421 Iterator it = dirList.iterator();
422 while (it.hasNext()) {
423 String path = (String) it.next();
424 if (path.endsWith("/")) {
425 processTldsInFileSystem(path);
426 }
427 if (!path.endsWith(".tld")) {
428 continue;
429 }
430 InputStream stream = ctxt.getResourceAsStream(path);
431 String uri = null;
432 try {
433 uri = getUriFromTld(path, stream);
434 } finally {
435 if (stream != null) {
436 try {
437 stream.close();
438 } catch (Throwable t) {
439 // do nothing
440 }
441 }
442 }
443 // Add implicit map entry only if its uri is not already
444 // present in the map
445 if (uri != null && mappings.get(uri) == null) {
446 mappings.put(uri, new String[] { path, null });
447 }
448 }
449 }
450 }
451
452 /*
453 * Returns the value of the uri element of the given TLD, or null if the
454 * given TLD does not contain any such element.
455 */
456 private String getUriFromTld(String resourcePath, InputStream in)
457 throws JasperException
458 {
459 // Parse the tag library descriptor at the specified resource path
460 TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in);
461 TreeNode uri = tld.findChild("uri");
462 if (uri != null) {
463 String body = uri.getBody();
464 if (body != null)
465 return body;
466 }
467
468 return null;
469 }
470
471 /*
472 * Scans all JARs accessible to the webapp's classloader and its
473 * parent classloaders for TLDs.
474 *
475 * The list of JARs always includes the JARs under WEB-INF/lib, as well as
476 * all shared JARs in the classloader delegation chain of the webapp's
477 * classloader.
478 *
479 * Considering JARs in the classloader delegation chain constitutes a
480 * Tomcat-specific extension to the TLD search
481 * order defined in the JSP spec. It allows tag libraries packaged as JAR
482 * files to be shared by web applications by simply dropping them in a
483 * location that all web applications have access to (e.g.,
484 * <CATALINA_HOME>/common/lib).
485 *
486 * The set of shared JARs to be scanned for TLDs is narrowed down by
487 * the <tt>noTldJars</tt> class variable, which contains the names of JARs
488 * that are known not to contain any TLDs.
489 */
490 private void scanJars() throws Exception {
491
492 ClassLoader webappLoader
493 = Thread.currentThread().getContextClassLoader();
494 ClassLoader loader = webappLoader;
495
496 while (loader != null) {
497 if (loader instanceof URLClassLoader) {
498 URL[] urls = ((URLClassLoader) loader).getURLs();
499 for (int i=0; i<urls.length; i++) {
500 URLConnection conn = urls[i].openConnection();
501 if (conn instanceof JarURLConnection) {
502 if (needScanJar(loader, webappLoader,
503 ((JarURLConnection) conn).getJarFile().getName())) {
504 scanJar((JarURLConnection) conn, true);
505 }
506 } else {
507 String urlStr = urls[i].toString();
508 if (urlStr.startsWith(FILE_PROTOCOL)
509 && urlStr.endsWith(JAR_FILE_SUFFIX)
510 && needScanJar(loader, webappLoader, urlStr)) {
511 URL jarURL = new URL("jar:" + urlStr + "!/");
512 scanJar((JarURLConnection) jarURL.openConnection(),
513 true);
514 }
515 }
516 }
517 }
518
519 loader = loader.getParent();
520 }
521 }
522
523 /*
524 * Determines if the JAR file with the given <tt>jarPath</tt> needs to be
525 * scanned for TLDs.
526 *
527 * @param loader The current classloader in the parent chain
528 * @param webappLoader The webapp classloader
529 * @param jarPath The JAR file path
530 *
531 * @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be
532 * scanned for TLDs, FALSE otherwise
533 */
534 private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader,
535 String jarPath) {
536 if (loader == webappLoader) {
537 // JARs under WEB-INF/lib must be scanned unconditionally according
538 // to the spec.
539 return true;
540 } else {
541 String jarName = jarPath;
542 int slash = jarPath.lastIndexOf('/');
543 if (slash >= 0) {
544 jarName = jarPath.substring(slash + 1);
545 }
546 return (!noTldJars.contains(jarName));
547 }
548 }
549 }