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.getLog(GeronimoTldLocationsCache.class);
090
091 /**
092 * The types of URI one may specify for a tag library
093 */
094 public static final int ABS_URI = 0;
095 public static final int ROOT_REL_URI = 1;
096 public static final int NOROOT_REL_URI = 2;
097
098 private static final String WEB_XML = "/WEB-INF/web.xml";
099 private static final String FILE_PROTOCOL = "file:";
100 private static final String JAR_FILE_SUFFIX = ".jar";
101
102 // Names of JARs that are known not to contain any TLDs
103 private static HashSet<String> noTldJars;
104
105 // Names of JARs that have already been scanned
106 ArrayList<String> scannedJars;
107
108 /**
109 * The mapping of the 'global' tag library URI to the location (resource
110 * path) of the TLD associated with that tag library. The location is
111 * returned as a String array:
112 * [0] The location
113 * [1] If the location is a jar file, this is the location of the tld.
114 */
115 private Hashtable<String,String[]> mappings;
116
117 private boolean initialized;
118 private ServletContext ctxt;
119 private boolean redeployMode;
120
121 //*********************************************************************
122 // Constructor and Initilizations
123
124 /*
125 * Initializes the set of JARs that are known not to contain any TLDs
126 */
127 static {
128 noTldJars = new HashSet<String>();
129 // Misc JARs not included with Tomcat
130 noTldJars.add("ant.jar");
131 noTldJars.add("commons-dbcp.jar");
132 noTldJars.add("commons-beanutils.jar");
133 noTldJars.add("commons-fileupload-1.0.jar");
134 noTldJars.add("commons-pool.jar");
135 noTldJars.add("commons-digester.jar");
136 noTldJars.add("commons-logging.jar");
137 noTldJars.add("commons-collections.jar");
138 noTldJars.add("jmx.jar");
139 noTldJars.add("jmx-tools.jar");
140 noTldJars.add("xercesImpl.jar");
141 noTldJars.add("xmlParserAPIs.jar");
142 noTldJars.add("xml-apis.jar");
143 // JARs from J2SE runtime
144 noTldJars.add("sunjce_provider.jar");
145 noTldJars.add("ldapsec.jar");
146 noTldJars.add("localedata.jar");
147 noTldJars.add("dnsns.jar");
148 noTldJars.add("tools.jar");
149 noTldJars.add("sunpkcs11.jar");
150 }
151
152 public GeronimoTldLocationsCache(ServletContext ctxt) {
153 this(ctxt, true);
154 }
155
156 /** Constructor.
157 *
158 * @param ctxt the servlet context of the web application in which Jasper
159 * is running
160 * @param redeployMode if true, then the compiler will allow redeploying
161 * a tag library from the same jar, at the expense of slowing down the
162 * server a bit. Note that this may only work on JDK 1.3.1_01a and later,
163 * because of JDK bug 4211817 fixed in this release.
164 * If redeployMode is false, a faster but less capable mode will be used.
165 */
166 public GeronimoTldLocationsCache(ServletContext ctxt, boolean redeployMode) {
167 super(ctxt,redeployMode);
168 scannedJars = new ArrayList<String>();
169 this.ctxt = ctxt;
170 this.redeployMode = redeployMode;
171 mappings = new Hashtable<String,String[]>();
172 initialized = false;
173 }
174
175 /**
176 * Sets the list of JARs that are known not to contain any TLDs.
177 *
178 * @param jarNames List of comma-separated names of JAR files that are
179 * known not to contain any TLDs
180 */
181 public static void setNoTldJars(String jarNames) {
182 if (jarNames != null) {
183 noTldJars.clear();
184 StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
185 while (tokenizer.hasMoreElements()) {
186 noTldJars.add(tokenizer.nextToken());
187 }
188 }
189 }
190
191 /**
192 * Gets the 'location' of the TLD associated with the given taglib 'uri'.
193 *
194 * Returns null if the uri is not associated with any tag library 'exposed'
195 * in the web application. A tag library is 'exposed' either explicitly in
196 * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
197 * in a jar file (WEB-INF/lib).
198 *
199 * @param uri The taglib uri
200 *
201 * @return An array of two Strings: The first element denotes the real
202 * path to the TLD. If the path to the TLD points to a jar file, then the
203 * second element denotes the name of the TLD entry in the jar file.
204 * Returns null if the uri is not associated with any tag library 'exposed'
205 * in the web application.
206 */
207 public String[] getLocation(String uri) throws JasperException {
208 if (!initialized) {
209 init();
210 }
211 return (String[]) mappings.get(uri);
212 }
213
214 private void init() throws JasperException {
215 if (initialized) return;
216 try {
217 processWebDotXml();
218 scanJars(Thread.currentThread().getContextClassLoader());
219 processTldsInFileSystem("/WEB-INF/");
220 initialized = true;
221 } catch (Exception ex) {
222 throw new JasperException(ex);
223 }
224 }
225
226 /*
227 * Populates taglib map described in web.xml.
228 */
229 private void processWebDotXml() throws Exception {
230
231 InputStream is = null;
232
233 try {
234 // Acquire input stream to web application deployment descriptor
235 String altDDName = (String)ctxt.getAttribute(
236 Constants.ALT_DD_ATTR);
237 URL uri = null;
238 if (altDDName != null) {
239 try {
240 uri = new URL(FILE_PROTOCOL+altDDName.replace('\\', '/'));
241 } catch (MalformedURLException e) {
242 if (log.isWarnEnabled()) {
243 log.warn("file not found: " + altDDName);
244 }
245 }
246 } else {
247 uri = ctxt.getResource(WEB_XML);
248 if (uri == null && log.isWarnEnabled()) {
249 log.warn("file not found: " + WEB_XML);
250 }
251 }
252
253 if (uri == null) {
254 return;
255 }
256 is = uri.openStream();
257 InputSource ip = new InputSource(is);
258 ip.setSystemId(uri.toExternalForm());
259
260 // Parse the web application deployment descriptor
261 TreeNode webtld = null;
262 // altDDName is the absolute path of the DD
263 if (altDDName != null) {
264 webtld = new ParserUtils().parseXMLDocument(altDDName, ip);
265 } else {
266 webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip);
267 }
268
269 // Allow taglib to be an element of the root or jsp-config (JSP2.0)
270 TreeNode jspConfig = webtld.findChild("jsp-config");
271 if (jspConfig != null) {
272 webtld = jspConfig;
273 }
274 Iterator taglibs = webtld.findChildren("taglib");
275 while (taglibs.hasNext()) {
276
277 // Parse the next <taglib> element
278 TreeNode taglib = (TreeNode) taglibs.next();
279 String tagUri = null;
280 String tagLoc = null;
281 TreeNode child = taglib.findChild("taglib-uri");
282 if (child != null)
283 tagUri = child.getBody();
284 child = taglib.findChild("taglib-location");
285 if (child != null)
286 tagLoc = child.getBody();
287
288 // Save this location if appropriate
289 if (tagLoc == null)
290 continue;
291 if (uriType(tagLoc) == NOROOT_REL_URI)
292 tagLoc = "/WEB-INF/" + tagLoc;
293 String tagLoc2 = null;
294 if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
295 tagLoc = ctxt.getResource(tagLoc).toString();
296 tagLoc2 = "META-INF/taglib.tld";
297 }
298 mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
299 }
300 } finally {
301 if (is != null) {
302 try {
303 is.close();
304 } catch (Throwable t) {}
305 }
306 }
307 }
308
309 /**
310 * Scans the given JarURLConnection for TLD files located in META-INF
311 * (or a subdirectory of it), adding an implicit map entry to the taglib
312 * map for any TLD that has a <uri> element.
313 *
314 * @param conn The JarURLConnection to the JAR file to scan
315 * @param ignore true if any exceptions raised when processing the given
316 * JAR should be ignored, false otherwise
317 */
318 private void scanJar(JarURLConnection conn, boolean ignore)
319 throws JasperException {
320
321 JarFile jarFile = null;
322 String resourcePath = conn.getJarFileURL().toString();
323 try {
324 if (redeployMode) {
325 conn.setUseCaches(false);
326 }
327 jarFile = conn.getJarFile();
328 Enumeration entries = jarFile.entries();
329 while (entries.hasMoreElements()) {
330 JarEntry entry = (JarEntry) entries.nextElement();
331 String name = entry.getName();
332 if (!name.startsWith("META-INF/")) continue;
333 if (!name.endsWith(".tld")) continue;
334 InputStream stream = jarFile.getInputStream(entry);
335 try {
336 String uri = getUriFromTld(resourcePath, stream);
337 // Add implicit map entry only if its uri is not already
338 // present in the map
339 if (uri != null && mappings.get(uri) == null) {
340 mappings.put(uri, new String[]{ resourcePath, name });
341 }
342 } finally {
343 if (stream != null) {
344 try {
345 stream.close();
346 } catch (Throwable t) {
347 // do nothing
348 }
349 }
350 }
351 }
352 } catch (Exception ex) {
353 if (!redeployMode) {
354 // if not in redeploy mode, close the jar in case of an error
355 if (jarFile != null) {
356 try {
357 jarFile.close();
358 } catch (Throwable t) {
359 // ignore
360 }
361 }
362 }
363 if (!ignore) {
364 throw new JasperException(ex);
365 }
366 } finally {
367 if (redeployMode) {
368 // if in redeploy mode, always close the jar
369 if (jarFile != null) {
370 try {
371 jarFile.close();
372 } catch (Throwable t) {
373 // ignore
374 }
375 }
376 }
377 }
378 }
379
380 /*
381 * Searches the filesystem under /WEB-INF for any TLD files, and adds
382 * an implicit map entry to the taglib map for any TLD that has a <uri>
383 * element.
384 */
385 private void processTldsInFileSystem(String startPath)
386 throws Exception {
387
388 Set dirList = ctxt.getResourcePaths(startPath);
389 if (dirList != null) {
390 Iterator it = dirList.iterator();
391 while (it.hasNext()) {
392 String path = (String) it.next();
393 if (path.endsWith("/")) {
394 processTldsInFileSystem(path);
395 }
396 if (!path.endsWith(".tld")) {
397 continue;
398 }
399 InputStream stream = ctxt.getResourceAsStream(path);
400 String uri = null;
401 try {
402 uri = getUriFromTld(path, stream);
403 } finally {
404 if (stream != null) {
405 try {
406 stream.close();
407 } catch (Throwable t) {
408 // do nothing
409 }
410 }
411 }
412 // Add implicit map entry only if its uri is not already
413 // present in the map
414 if (uri != null && mappings.get(uri) == null) {
415 mappings.put(uri, new String[] { path, null });
416 }
417 }
418 }
419 }
420
421 /*
422 * Returns the value of the uri element of the given TLD, or null if the
423 * given TLD does not contain any such element.
424 */
425 private String getUriFromTld(String resourcePath, InputStream in)
426 throws JasperException
427 {
428 // Parse the tag library descriptor at the specified resource path
429 TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in);
430 TreeNode uri = tld.findChild("uri");
431 if (uri != null) {
432 String body = uri.getBody();
433 if (body != null)
434 return body;
435 }
436
437 return null;
438 }
439
440 /*
441 * Scans all JARs accessible to the webapp's classloader and its
442 * parent classloaders for TLDs.
443 *
444 * The list of JARs always includes the JARs under WEB-INF/lib, as well as
445 * all shared JARs in the classloader delegation chain of the webapp's
446 * classloader.
447 *
448 * The set of shared JARs to be scanned for TLDs is narrowed down by
449 * the <tt>noTldJars</tt> class variable, which contains the names of JARs
450 * that are known not to contain any TLDs.
451 */
452 private void scanJars(ClassLoader loader) throws Exception {
453
454 if (loader instanceof MultiParentClassLoader) {
455 MultiParentClassLoader mutliLoader = (MultiParentClassLoader) loader;
456 for (ClassLoader parent : mutliLoader.getParents()) {
457 scanJars(parent);
458 }
459 }
460
461 if (loader instanceof URLClassLoader) {
462 URL[] urls = ((URLClassLoader) loader).getURLs();
463 for (int i=0; i<urls.length; i++) {
464 URLConnection conn = urls[i].openConnection();
465 if (conn instanceof JarURLConnection) {
466 if (needScanJar(loader,
467 ((JarURLConnection) conn).getJarFile().getName())) {
468 scanJar((JarURLConnection) conn, true);
469 scannedJars.add(((JarURLConnection) conn).getJarFile().getName());
470 }
471 } else {
472 String urlStr = urls[i].toString();
473 if (urlStr.startsWith(FILE_PROTOCOL)
474 && urlStr.endsWith(JAR_FILE_SUFFIX)
475 && needScanJar(loader, urlStr)) {
476 URL jarURL = new URL("jar:" + urlStr + "!/");
477 scanJar((JarURLConnection) jarURL.openConnection(),
478 true);
479 scannedJars.add(urlStr);
480 }
481 }
482 }
483 }
484 }
485
486 /*
487 * Determines if the JAR file with the given <tt>jarPath</tt> needs to be
488 * scanned for TLDs.
489 *
490 * @param loader The current classloader in the parent chain
491 * @param webappLoader The webapp classloader
492 * @param jarPath The JAR file path
493 *
494 * @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be
495 * scanned for TLDs, FALSE otherwise
496 */
497 private boolean needScanJar(ClassLoader loader,
498 String jarPath) {
499 if (scannedJars.contains(jarPath)) {
500 return false;
501 }
502 else if (loader == Thread.currentThread().getContextClassLoader()) {
503 // JARs under WEB-INF/lib must be scanned unconditionally according
504 // to the spec.
505 return true;
506 } else {
507 String jarName = jarPath;
508 int slash = jarPath.lastIndexOf('/');
509 if (slash >= 0) {
510 jarName = jarPath.substring(slash + 1);
511 }
512 return (!noTldJars.contains(jarName));
513 }
514 }
515 }