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 }