| /* |
| * 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.runtime.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); |
| 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); |
| } |
| } |
| |
| } |