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