1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
|
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jasper.compiler;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletContext;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.util.ExceptionUtils;
import org.apache.jasper.xmlparser.ParserUtils;
import org.apache.jasper.xmlparser.TreeNode;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.scan.Jar;
import org.apache.tomcat.util.scan.JarFactory;
/**
* A container for all tag libraries that are defined "globally"
* for the web application.
*
* Tag Libraries can be defined globally in one of two ways:
* 1. Via <taglib> elements in web.xml:
* the uri and location of the tag-library are specified in
* the <taglib> element.
* 2. Via packaged jar files that contain .tld files
* within the META-INF directory, or some subdirectory
* of it. The taglib is 'global' if it has the <uri>
* element defined.
*
* A mapping between the taglib URI and its associated TaglibraryInfoImpl
* is maintained in this container.
* Actually, that's what we'd like to do. However, because of the
* way the classes TagLibraryInfo and TagInfo have been defined,
* it is not currently possible to share an instance of TagLibraryInfo
* across page invocations. A bug has been submitted to the spec lead.
* In the mean time, all we do is save the 'location' where the
* TLD associated with a taglib URI can be found.
*
* When a JSP page has a taglib directive, the mappings in this container
* are first searched (see method getLocation()).
* If a mapping is found, then the location of the TLD is returned.
* If no mapping is found, then the uri specified
* in the taglib directive is to be interpreted as the location for
* the TLD of this tag library.
*
* @author Pierre Delisle
* @author Jan Luehe
*/
public class TldLocationsCache {
private static final Log log = LogFactory.getLog(TldLocationsCache.class);
private static final String KEY = TldLocationsCache.class.getName();
/**
* The types of URI one may specify for a tag library
*/
public static final int ABS_URI = 0;
public static final int ROOT_REL_URI = 1;
public static final int NOROOT_REL_URI = 2;
private static final String WEB_INF = "/WEB-INF/";
private static final String WEB_INF_LIB = "/WEB-INF/lib/";
private static final String JAR_EXT = ".jar";
private static final String TLD_EXT = ".tld";
// Names of JARs that are known not to contain any TLDs
private static Set<String> noTldJars = null;
// Flag that indicates that an INFO level message has been provided that
// there are JARs that could be skipped
private static volatile boolean showTldScanWarning = true;
static {
// Set the default list of JARs to skip for TLDs
// Set the default list of JARs to skip for TLDs
StringBuilder jarList = new StringBuilder(System.getProperty(
Constants.DEFAULT_JAR_SKIP_PROP, ""));
String tldJars = System.getProperty(Constants.TLD_JAR_SKIP_PROP, "");
if (tldJars.length() > 0) {
if (jarList.length() > 0) {
jarList.append(',');
}
jarList.append(tldJars);
}
if (jarList.length() > 0) {
setNoTldJars(jarList.toString());
}
}
/**
* Sets the list of JARs that are known not to contain any TLDs.
*
* @param jarNames List of comma-separated names of JAR files that are
* known not to contain any TLDs
*/
public static synchronized void setNoTldJars(String jarNames) {
if (jarNames == null) {
noTldJars = null;
} else {
if (noTldJars == null) {
noTldJars = new HashSet<String>();
} else {
noTldJars.clear();
}
StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken().trim();
if (token.length() > 0) {
noTldJars.add(token);
}
}
}
}
/**
* The mapping of the 'global' tag library URI to the location (resource
* path) of the TLD associated with that tag library. The location is
* returned as a String array:
* [0] The location
* [1] If the location is a jar file, this is the location of the tld.
*/
private Hashtable<String, TldLocation> mappings;
private volatile boolean initialized;
private ServletContext ctxt;
/** Constructor.
*
* @param ctxt the servlet context of the web application in which Jasper
* is running
*/
public TldLocationsCache(ServletContext ctxt) {
this.ctxt = ctxt;
mappings = new Hashtable<String, TldLocation>();
initialized = false;
}
/**
* Obtains the TLD location cache for the given {@link ServletContext} and
* creates one if one does not currently exist.
*/
public static synchronized TldLocationsCache getInstance(
ServletContext ctxt) {
if (ctxt == null) {
throw new IllegalArgumentException("ServletContext was null");
}
TldLocationsCache cache = (TldLocationsCache) ctxt.getAttribute(KEY);
if (cache == null) {
cache = new TldLocationsCache(ctxt);
ctxt.setAttribute(KEY, cache);
}
return cache;
}
/**
* Gets the 'location' of the TLD associated with the given taglib 'uri'.
*
* Returns null if the uri is not associated with any tag library 'exposed'
* in the web application. A tag library is 'exposed' either explicitly in
* web.xml or implicitly via the uri tag in the TLD of a taglib deployed
* in a jar file (WEB-INF/lib).
*
* @param uri The taglib uri
*
* @return An array of two Strings: The first element denotes the real
* path to the TLD. If the path to the TLD points to a jar file, then the
* second element denotes the name of the TLD entry in the jar file.
* Returns null if the uri is not associated with any tag library 'exposed'
* in the web application.
*/
public TldLocation getLocation(String uri) throws JasperException {
if (!initialized) {
init();
}
return mappings.get(uri);
}
/**
* Returns the type of a URI:
* ABS_URI
* ROOT_REL_URI
* NOROOT_REL_URI
*/
public static int uriType(String uri) {
if (uri.indexOf(':') != -1) {
return ABS_URI;
} else if (uri.startsWith("/")) {
return ROOT_REL_URI;
} else {
return NOROOT_REL_URI;
}
}
/*
* Keep processing order in sync with o.a.c.startup.TldConfig
*
* This supports a Tomcat-specific extension to the TLD search
* order defined in the JSP spec. It allows tag libraries packaged as JAR
* files to be shared by web applications by simply dropping them in a
* location that all web applications have access to (e.g.,
* <CATALINA_HOME>/lib). It also supports some of the weird and
* wonderful arrangements present when Tomcat gets embedded.
*
*/
private synchronized void init() throws JasperException {
if (initialized) return;
try {
tldScanWebXml();
tldScanResourcePaths(WEB_INF);
JarScanner jarScanner = JarScannerFactory.getJarScanner(ctxt);
if (jarScanner != null) {
jarScanner.scan(ctxt,
Thread.currentThread().getContextClassLoader(),
new TldJarScannerCallback(), noTldJars);
}
initialized = true;
} catch (Exception ex) {
throw new JasperException(Localizer.getMessage(
"jsp.error.internal.tldinit", ex.getMessage()), ex);
}
}
private class TldJarScannerCallback implements JarScannerCallback {
@Override
public void scan(JarURLConnection urlConn) throws IOException {
tldScanJar(urlConn);
}
@Override
public void scan(File file) throws IOException {
File metaInf = new File(file, "META-INF");
if (metaInf.isDirectory()) {
tldScanDir(metaInf);
}
}
}
/*
* Populates taglib map described in web.xml.
*
* This is not kept in sync with o.a.c.startup.TldConfig as the Jasper only
* needs the URI to TLD mappings from scan web.xml whereas TldConfig needs
* to scan the actual TLD files.
*/
private void tldScanWebXml() throws Exception {
WebXml webXml = null;
try {
webXml = new WebXml(ctxt);
if (webXml.getInputSource() == null) {
return;
}
boolean validate = Boolean.parseBoolean(
ctxt.getInitParameter(
Constants.XML_VALIDATION_INIT_PARAM));
String blockExternalString = ctxt.getInitParameter(
Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
boolean blockExternal;
if (blockExternalString == null) {
blockExternal = true;
} else {
blockExternal = Boolean.parseBoolean(blockExternalString);
}
// Parse the web application deployment descriptor
ParserUtils pu = new ParserUtils(validate, blockExternal);
TreeNode webtld = null;
webtld = pu.parseXMLDocument(webXml.getSystemId(),
webXml.getInputSource());
// Allow taglib to be an element of the root or jsp-config (JSP2.0)
TreeNode jspConfig = webtld.findChild("jsp-config");
if (jspConfig != null) {
webtld = jspConfig;
}
Iterator<TreeNode> taglibs = webtld.findChildren("taglib");
while (taglibs.hasNext()) {
// Parse the next <taglib> element
TreeNode taglib = taglibs.next();
String tagUri = null;
String tagLoc = null;
TreeNode child = taglib.findChild("taglib-uri");
if (child != null)
tagUri = child.getBody();
child = taglib.findChild("taglib-location");
if (child != null)
tagLoc = child.getBody();
// Save this location if appropriate
if (tagLoc == null)
continue;
if (uriType(tagLoc) == NOROOT_REL_URI)
tagLoc = "/WEB-INF/" + tagLoc;
TldLocation location;
if (tagLoc.endsWith(JAR_EXT)) {
location = new TldLocation("META-INF/taglib.tld", ctxt.getResource(tagLoc).toString());
} else {
location = new TldLocation(tagLoc);
}
mappings.put(tagUri, location);
}
} finally {
if (webXml != null) {
webXml.close();
}
}
}
/*
* Scans the web application's sub-directory identified by startPath,
* along with its sub-directories, for TLDs and adds an implicit map entry
* to the taglib map for any TLD that has a <uri> element.
*
* Initially, rootPath equals /WEB-INF/. The /WEB-INF/classes and
* /WEB-INF/lib sub-directories are excluded from the search, as per the
* JSP 2.0 spec.
*
* Keep code in sync with o.a.c.startup.TldConfig
*/
private void tldScanResourcePaths(String startPath)
throws Exception {
Set<String> dirList = ctxt.getResourcePaths(startPath);
if (dirList != null) {
Iterator<String> it = dirList.iterator();
while (it.hasNext()) {
String path = it.next();
if (!path.endsWith(TLD_EXT)
&& (path.startsWith(WEB_INF_LIB)
|| path.startsWith("/WEB-INF/classes/"))) {
continue;
}
if (path.endsWith(TLD_EXT)) {
if (path.startsWith("/WEB-INF/tags/") &&
!path.endsWith("implicit.tld")) {
continue;
}
InputStream stream = ctxt.getResourceAsStream(path);
try {
tldScanStream(path, null, stream);
} finally {
if (stream != null) {
try {
stream.close();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
}
}
} else {
tldScanResourcePaths(path);
}
}
}
}
/*
* Scans the directory identified by startPath, along with its
* sub-directories, for TLDs.
*
* Keep in sync with o.a.c.startup.TldConfig
*/
private void tldScanDir(File start) throws IOException {
File[] fileList = start.listFiles();
if (fileList != null) {
for (int i = 0; i < fileList.length; i++) {
// Scan recursively
if (fileList[i].isDirectory()) {
tldScanDir(fileList[i]);
} else if (fileList[i].getAbsolutePath().endsWith(TLD_EXT)) {
InputStream stream = null;
try {
stream = new FileInputStream(fileList[i]);
tldScanStream(
fileList[i].toURI().toString(), null, stream);
} finally {
if (stream != null) {
try {
stream.close();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
}
}
}
}
}
}
/*
* Scans the given JarURLConnection for TLD files located in META-INF
* (or a subdirectory of it), adding an implicit map entry to the taglib
* map for any TLD that has a <uri> element.
*
* @param jarConn The JarURLConnection to the JAR file to scan
*
* Keep in sync with o.a.c.startup.TldConfig
*/
private void tldScanJar(JarURLConnection jarConn) throws IOException {
Jar jar = null;
InputStream is;
boolean foundTld = false;
URL resourceURL = jarConn.getJarFileURL();
String resourcePath = resourceURL.toString();
try {
jar = JarFactory.newInstance(jarConn.getURL());
jar.nextEntry();
String entryName = jar.getEntryName();
while (entryName != null) {
if (entryName.startsWith("META-INF/") &&
entryName.endsWith(".tld")) {
is = null;
try {
is = jar.getEntryInputStream();
foundTld = true;
tldScanStream(resourcePath, entryName, is);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ioe) {
// Ignore
}
}
}
}
jar.nextEntry();
entryName = jar.getEntryName();
}
} finally {
if (jar != null) {
jar.close();
}
}
if (!foundTld) {
if (log.isDebugEnabled()) {
log.debug(Localizer.getMessage("jsp.tldCache.noTldInJar",
resourcePath));
} else if (showTldScanWarning) {
// Not entirely thread-safe but a few duplicate log messages are
// not a huge issue
showTldScanWarning = false;
log.info(Localizer.getMessage("jsp.tldCache.noTldSummary"));
}
}
}
/*
* Scan the TLD contents in the specified input stream and add any new URIs
* to the map.
*
* @param resourcePath Path of the resource
* @param entryName If the resource is a JAR file, the name of the entry
* in the JAR file
* @param stream The input stream for the resource
* @throws IOException
*/
private void tldScanStream(String resourcePath, String entryName,
InputStream stream) throws IOException {
try {
// Parse the tag library descriptor at the specified resource path
String uri = null;
boolean validate = Boolean.parseBoolean(
ctxt.getInitParameter(
Constants.XML_VALIDATION_TLD_INIT_PARAM));
String blockExternalString = ctxt.getInitParameter(
Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
boolean blockExternal;
if (blockExternalString == null) {
blockExternal = true;
} else {
blockExternal = Boolean.parseBoolean(blockExternalString);
}
ParserUtils pu = new ParserUtils(validate, blockExternal);
TreeNode tld = pu.parseXMLDocument(resourcePath, stream);
TreeNode uriNode = tld.findChild("uri");
if (uriNode != null) {
String body = uriNode.getBody();
if (body != null)
uri = body;
}
// Add implicit map entry only if its uri is not already
// present in the map
if (uri != null && mappings.get(uri) == null) {
TldLocation location;
if (entryName == null) {
location = new TldLocation(resourcePath);
} else {
location = new TldLocation(entryName, resourcePath);
}
mappings.put(uri, location);
}
} catch (JasperException e) {
// Hack - makes exception handling simpler
throw new IOException(e);
}
}
}
|