001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jasper.compiler;
018:
019: import java.io.InputStream;
020: import java.net.JarURLConnection;
021: import java.net.URL;
022: import java.net.URLClassLoader;
023: import java.net.URLConnection;
024: import java.util.Enumeration;
025: import java.util.Hashtable;
026: import java.util.HashSet;
027: import java.util.Iterator;
028: import java.util.Set;
029: import java.util.StringTokenizer;
030: import java.util.jar.JarEntry;
031: import java.util.jar.JarFile;
032:
033: import javax.servlet.ServletContext;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.apache.jasper.JasperException;
038: import org.apache.jasper.xmlparser.ParserUtils;
039: import org.apache.jasper.xmlparser.TreeNode;
040:
041: /**
042: * A container for all tag libraries that are defined "globally"
043: * for the web application.
044: *
045: * Tag Libraries can be defined globally in one of two ways:
046: * 1. Via <taglib> elements in web.xml:
047: * the uri and location of the tag-library are specified in
048: * the <taglib> element.
049: * 2. Via packaged jar files that contain .tld files
050: * within the META-INF directory, or some subdirectory
051: * of it. The taglib is 'global' if it has the <uri>
052: * element defined.
053: *
054: * A mapping between the taglib URI and its associated TaglibraryInfoImpl
055: * is maintained in this container.
056: * Actually, that's what we'd like to do. However, because of the
057: * way the classes TagLibraryInfo and TagInfo have been defined,
058: * it is not currently possible to share an instance of TagLibraryInfo
059: * across page invocations. A bug has been submitted to the spec lead.
060: * In the mean time, all we do is save the 'location' where the
061: * TLD associated with a taglib URI can be found.
062: *
063: * When a JSP page has a taglib directive, the mappings in this container
064: * are first searched (see method getLocation()).
065: * If a mapping is found, then the location of the TLD is returned.
066: * If no mapping is found, then the uri specified
067: * in the taglib directive is to be interpreted as the location for
068: * the TLD of this tag library.
069: *
070: * @author Pierre Delisle
071: * @author Jan Luehe
072: */
073:
074: public class TldLocationsCache {
075:
076: // Logger
077: private static Log log = LogFactory.getLog(TldLocationsCache.class);
078:
079: /**
080: * The types of URI one may specify for a tag library
081: */
082: public static final int ABS_URI = 0;
083: public static final int ROOT_REL_URI = 1;
084: public static final int NOROOT_REL_URI = 2;
085:
086: private static final String WEB_XML = "/WEB-INF/web.xml";
087: private static final String FILE_PROTOCOL = "file:";
088: private static final String JAR_FILE_SUFFIX = ".jar";
089:
090: // Names of JARs that are known not to contain any TLDs
091: private static HashSet noTldJars;
092:
093: /**
094: * The mapping of the 'global' tag library URI to the location (resource
095: * path) of the TLD associated with that tag library. The location is
096: * returned as a String array:
097: * [0] The location
098: * [1] If the location is a jar file, this is the location of the tld.
099: */
100: private Hashtable mappings;
101:
102: private boolean initialized;
103: private ServletContext ctxt;
104: private boolean redeployMode;
105:
106: //*********************************************************************
107: // Constructor and Initilizations
108:
109: /*
110: * Initializes the set of JARs that are known not to contain any TLDs
111: */
112: static {
113: noTldJars = new HashSet();
114: noTldJars.add("ant.jar");
115: noTldJars.add("catalina.jar");
116: noTldJars.add("catalina-ant.jar");
117: noTldJars.add("catalina-cluster.jar");
118: noTldJars.add("catalina-optional.jar");
119: noTldJars.add("catalina-i18n-fr.jar");
120: noTldJars.add("catalina-i18n-ja.jar");
121: noTldJars.add("catalina-i18n-es.jar");
122: noTldJars.add("commons-dbcp.jar");
123: noTldJars.add("commons-modeler.jar");
124: noTldJars.add("commons-logging-api.jar");
125: noTldJars.add("commons-beanutils.jar");
126: noTldJars.add("commons-fileupload-1.0.jar");
127: noTldJars.add("commons-pool.jar");
128: noTldJars.add("commons-digester.jar");
129: noTldJars.add("commons-logging.jar");
130: noTldJars.add("commons-collections.jar");
131: noTldJars.add("commons-el.jar");
132: noTldJars.add("jakarta-regexp-1.2.jar");
133: noTldJars.add("jasper-compiler.jar");
134: noTldJars.add("jasper-runtime.jar");
135: noTldJars.add("jmx.jar");
136: noTldJars.add("jmx-tools.jar");
137: noTldJars.add("jsp-api.jar");
138: noTldJars.add("jkshm.jar");
139: noTldJars.add("jkconfig.jar");
140: noTldJars.add("naming-common.jar");
141: noTldJars.add("naming-resources.jar");
142: noTldJars.add("naming-factory.jar");
143: noTldJars.add("naming-java.jar");
144: noTldJars.add("servlet-api.jar");
145: noTldJars.add("servlets-default.jar");
146: noTldJars.add("servlets-invoker.jar");
147: noTldJars.add("servlets-common.jar");
148: noTldJars.add("servlets-webdav.jar");
149: noTldJars.add("tomcat-util.jar");
150: noTldJars.add("tomcat-http11.jar");
151: noTldJars.add("tomcat-jni.jar");
152: noTldJars.add("tomcat-jk.jar");
153: noTldJars.add("tomcat-jk2.jar");
154: noTldJars.add("tomcat-coyote.jar");
155: noTldJars.add("xercesImpl.jar");
156: noTldJars.add("xmlParserAPIs.jar");
157: noTldJars.add("xml-apis.jar");
158: // JARs from J2SE runtime
159: noTldJars.add("sunjce_provider.jar");
160: noTldJars.add("ldapsec.jar");
161: noTldJars.add("localedata.jar");
162: noTldJars.add("dnsns.jar");
163: }
164:
165: public TldLocationsCache(ServletContext ctxt) {
166: this (ctxt, true);
167: }
168:
169: /** Constructor.
170: *
171: * @param ctxt the servlet context of the web application in which Jasper
172: * is running
173: * @param redeployMode if true, then the compiler will allow redeploying
174: * a tag library from the same jar, at the expense of slowing down the
175: * server a bit. Note that this may only work on JDK 1.3.1_01a and later,
176: * because of JDK bug 4211817 fixed in this release.
177: * If redeployMode is false, a faster but less capable mode will be used.
178: */
179: public TldLocationsCache(ServletContext ctxt, boolean redeployMode) {
180: this .ctxt = ctxt;
181: this .redeployMode = redeployMode;
182: mappings = new Hashtable();
183: initialized = false;
184: }
185:
186: /**
187: * Sets the list of JARs that are known not to contain any TLDs.
188: *
189: * @param jarNames List of comma-separated names of JAR files that are
190: * known not to contain any TLDs
191: */
192: public static void setNoTldJars(String jarNames) {
193: if (jarNames != null) {
194: noTldJars.clear();
195: StringTokenizer tokenizer = new StringTokenizer(jarNames,
196: ",");
197: while (tokenizer.hasMoreElements()) {
198: noTldJars.add(tokenizer.nextToken());
199: }
200: }
201: }
202:
203: /**
204: * Gets the 'location' of the TLD associated with the given taglib 'uri'.
205: *
206: * Returns null if the uri is not associated with any tag library 'exposed'
207: * in the web application. A tag library is 'exposed' either explicitly in
208: * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
209: * in a jar file (WEB-INF/lib).
210: *
211: * @param uri The taglib uri
212: *
213: * @return An array of two Strings: The first element denotes the real
214: * path to the TLD. If the path to the TLD points to a jar file, then the
215: * second element denotes the name of the TLD entry in the jar file.
216: * Returns null if the uri is not associated with any tag library 'exposed'
217: * in the web application.
218: */
219: public String[] getLocation(String uri) throws JasperException {
220: if (!initialized) {
221: init();
222: }
223: return (String[]) mappings.get(uri);
224: }
225:
226: /**
227: * Returns the type of a URI:
228: * ABS_URI
229: * ROOT_REL_URI
230: * NOROOT_REL_URI
231: */
232: public static int uriType(String uri) {
233: if (uri.indexOf(':') != -1) {
234: return ABS_URI;
235: } else if (uri.startsWith("/")) {
236: return ROOT_REL_URI;
237: } else {
238: return NOROOT_REL_URI;
239: }
240: }
241:
242: private void init() throws JasperException {
243: if (initialized)
244: return;
245: try {
246: processWebDotXml();
247: scanJars();
248: processTldsInFileSystem("/WEB-INF/");
249: initialized = true;
250: } catch (Exception ex) {
251: throw new JasperException(Localizer.getMessage(
252: "jsp.error.internal.tldinit", ex.getMessage()));
253: }
254: }
255:
256: /*
257: * Populates taglib map described in web.xml.
258: */
259: private void processWebDotXml() throws Exception {
260:
261: InputStream is = null;
262:
263: try {
264: // Acquire input stream to web application deployment descriptor
265: is = ctxt.getResourceAsStream(WEB_XML);
266: if (is == null) {
267: if (log.isWarnEnabled()) {
268: log
269: .warn(Localizer.getMessage(
270: "jsp.error.internal.filenotfound",
271: WEB_XML));
272: }
273: return;
274: }
275:
276: // Parse the web application deployment descriptor
277: TreeNode webtld = new ParserUtils().parseXMLDocument(
278: WEB_XML, is);
279:
280: // Allow taglib to be an element of the root or jsp-config (JSP2.0)
281: TreeNode jspConfig = webtld.findChild("jsp-config");
282: if (jspConfig != null) {
283: webtld = jspConfig;
284: }
285: Iterator taglibs = webtld.findChildren("taglib");
286: while (taglibs.hasNext()) {
287:
288: // Parse the next <taglib> element
289: TreeNode taglib = (TreeNode) taglibs.next();
290: String tagUri = null;
291: String tagLoc = null;
292: TreeNode child = taglib.findChild("taglib-uri");
293: if (child != null)
294: tagUri = child.getBody();
295: child = taglib.findChild("taglib-location");
296: if (child != null)
297: tagLoc = child.getBody();
298:
299: // Save this location if appropriate
300: if (tagLoc == null)
301: continue;
302: if (uriType(tagLoc) == NOROOT_REL_URI)
303: tagLoc = "/WEB-INF/" + tagLoc;
304: String tagLoc2 = null;
305: if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
306: tagLoc = ctxt.getResource(tagLoc).toString();
307: tagLoc2 = "META-INF/taglib.tld";
308: }
309: mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
310: }
311: } finally {
312: if (is != null) {
313: try {
314: is.close();
315: } catch (Throwable t) {
316: }
317: }
318: }
319: }
320:
321: /**
322: * Scans the given JarURLConnection for TLD files located in META-INF
323: * (or a subdirectory of it), adding an implicit map entry to the taglib
324: * map for any TLD that has a <uri> element.
325: *
326: * @param conn The JarURLConnection to the JAR file to scan
327: * @param ignore true if any exceptions raised when processing the given
328: * JAR should be ignored, false otherwise
329: */
330: private void scanJar(JarURLConnection conn, boolean ignore)
331: throws JasperException {
332:
333: JarFile jarFile = null;
334: String resourcePath = conn.getJarFileURL().toString();
335: try {
336: if (redeployMode) {
337: conn.setUseCaches(false);
338: }
339: jarFile = conn.getJarFile();
340: Enumeration entries = jarFile.entries();
341: while (entries.hasMoreElements()) {
342: JarEntry entry = (JarEntry) entries.nextElement();
343: String name = entry.getName();
344: if (!name.startsWith("META-INF/"))
345: continue;
346: if (!name.endsWith(".tld"))
347: continue;
348: InputStream stream = jarFile.getInputStream(entry);
349: try {
350: String uri = getUriFromTld(resourcePath, stream);
351: // Add implicit map entry only if its uri is not already
352: // present in the map
353: if (uri != null && mappings.get(uri) == null) {
354: mappings.put(uri, new String[] { resourcePath,
355: name });
356: }
357: } finally {
358: if (stream != null) {
359: try {
360: stream.close();
361: } catch (Throwable t) {
362: // do nothing
363: }
364: }
365: }
366: }
367: } catch (Exception ex) {
368: if (!redeployMode) {
369: // if not in redeploy mode, close the jar in case of an error
370: if (jarFile != null) {
371: try {
372: jarFile.close();
373: } catch (Throwable t) {
374: // ignore
375: }
376: }
377: }
378: if (!ignore) {
379: throw new JasperException(ex);
380: }
381: } finally {
382: if (redeployMode) {
383: // if in redeploy mode, always close the jar
384: if (jarFile != null) {
385: try {
386: jarFile.close();
387: } catch (Throwable t) {
388: // ignore
389: }
390: }
391: }
392: }
393: }
394:
395: /*
396: * Searches the filesystem under /WEB-INF for any TLD files, and adds
397: * an implicit map entry to the taglib map for any TLD that has a <uri>
398: * element.
399: */
400: private void processTldsInFileSystem(String startPath)
401: throws Exception {
402:
403: Set dirList = ctxt.getResourcePaths(startPath);
404: if (dirList != null) {
405: Iterator it = dirList.iterator();
406: while (it.hasNext()) {
407: String path = (String) it.next();
408: if (path.endsWith("/")) {
409: processTldsInFileSystem(path);
410: }
411: if (!path.endsWith(".tld")) {
412: continue;
413: }
414: InputStream stream = ctxt.getResourceAsStream(path);
415: String uri = null;
416: try {
417: uri = getUriFromTld(path, stream);
418: } finally {
419: if (stream != null) {
420: try {
421: stream.close();
422: } catch (Throwable t) {
423: // do nothing
424: }
425: }
426: }
427: // Add implicit map entry only if its uri is not already
428: // present in the map
429: if (uri != null && mappings.get(uri) == null) {
430: mappings.put(uri, new String[] { path, null });
431: }
432: }
433: }
434: }
435:
436: /*
437: * Returns the value of the uri element of the given TLD, or null if the
438: * given TLD does not contain any such element.
439: */
440: private String getUriFromTld(String resourcePath, InputStream in)
441: throws JasperException {
442: // Parse the tag library descriptor at the specified resource path
443: TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath,
444: in);
445: TreeNode uri = tld.findChild("uri");
446: if (uri != null) {
447: String body = uri.getBody();
448: if (body != null)
449: return body;
450: }
451:
452: return null;
453: }
454:
455: /*
456: * Scans all JARs accessible to the webapp's classloader and its
457: * parent classloaders for TLDs.
458: *
459: * The list of JARs always includes the JARs under WEB-INF/lib, as well as
460: * all shared JARs in the classloader delegation chain of the webapp's
461: * classloader.
462: *
463: * Considering JARs in the classloader delegation chain constitutes a
464: * Tomcat-specific extension to the TLD search
465: * order defined in the JSP spec. It allows tag libraries packaged as JAR
466: * files to be shared by web applications by simply dropping them in a
467: * location that all web applications have access to (e.g.,
468: * <CATALINA_HOME>/common/lib).
469: *
470: * The set of shared JARs to be scanned for TLDs is narrowed down by
471: * the <tt>noTldJars</tt> class variable, which contains the names of JARs
472: * that are known not to contain any TLDs.
473: */
474: private void scanJars() throws Exception {
475:
476: ClassLoader webappLoader = Thread.currentThread()
477: .getContextClassLoader();
478: ClassLoader loader = webappLoader;
479:
480: while (loader != null) {
481: if (loader instanceof URLClassLoader) {
482: URL[] urls = ((URLClassLoader) loader).getURLs();
483: for (int i = 0; i < urls.length; i++) {
484: URLConnection conn = urls[i].openConnection();
485: if (conn instanceof JarURLConnection) {
486: if (needScanJar(loader, webappLoader,
487: ((JarURLConnection) conn).getJarFile()
488: .getName())) {
489: scanJar((JarURLConnection) conn, true);
490: }
491: } else {
492: String urlStr = urls[i].toString();
493: if (urlStr.startsWith(FILE_PROTOCOL)
494: && urlStr.endsWith(JAR_FILE_SUFFIX)
495: && needScanJar(loader, webappLoader,
496: urlStr)) {
497: URL jarURL = new URL("jar:" + urlStr + "!/");
498: scanJar((JarURLConnection) jarURL
499: .openConnection(), true);
500: }
501: }
502: }
503: }
504:
505: loader = loader.getParent();
506: }
507: }
508:
509: /*
510: * Determines if the JAR file with the given <tt>jarPath</tt> needs to be
511: * scanned for TLDs.
512: *
513: * @param loader The current classloader in the parent chain
514: * @param webappLoader The webapp classloader
515: * @param jarPath The JAR file path
516: *
517: * @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be
518: * scanned for TLDs, FALSE otherwise
519: */
520: private boolean needScanJar(ClassLoader loader,
521: ClassLoader webappLoader, String jarPath) {
522: if (loader == webappLoader) {
523: // JARs under WEB-INF/lib must be scanned unconditionally according
524: // to the spec.
525: return true;
526: } else {
527: String jarName = jarPath;
528: int slash = jarPath.lastIndexOf('/');
529: if (slash >= 0) {
530: jarName = jarPath.substring(slash + 1);
531: }
532: return (!noTldJars.contains(jarName));
533: }
534: }
535: }
|