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