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