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