/* | |
* 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.struts2.jasper.compiler; | |
import org.apache.commons.io.FileUtils; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.apache.struts2.jasper.Constants; | |
import org.apache.struts2.jasper.JasperException; | |
import org.apache.struts2.jasper.xmlparser.ParserUtils; | |
import org.apache.struts2.jasper.xmlparser.TreeNode; | |
import org.xml.sax.InputSource; | |
import javax.servlet.ServletContext; | |
import java.io.File; | |
import java.io.InputStream; | |
import java.net.*; | |
import java.util.*; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
/** | |
* 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 { | |
// Logger | |
private Log log = LogFactory.getLog(TldLocationsCache.class); | |
/** | |
* 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_XML = "/WEB-INF/web.xml"; | |
private static final String FILE_PROTOCOL = "file:"; | |
private static final String JAR_FILE_SUFFIX = ".jar"; | |
// Names of JARs that are known not to contain any TLDs | |
private static HashSet noTldJars; | |
/** | |
* 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 mappings; | |
private boolean initialized; | |
private ServletContext ctxt; | |
private boolean redeployMode; | |
//********************************************************************* | |
// Constructor and Initilizations | |
/* | |
* Initializes the set of JARs that are known not to contain any TLDs | |
*/ | |
static { | |
noTldJars = new HashSet(); | |
noTldJars.add("ant.jar"); | |
noTldJars.add("catalina.jar"); | |
noTldJars.add("catalina-ant.jar"); | |
noTldJars.add("catalina-cluster.jar"); | |
noTldJars.add("catalina-optional.jar"); | |
noTldJars.add("catalina-i18n-fr.jar"); | |
noTldJars.add("catalina-i18n-ja.jar"); | |
noTldJars.add("catalina-i18n-es.jar"); | |
noTldJars.add("commons-dbcp.jar"); | |
noTldJars.add("commons-modeler.jar"); | |
noTldJars.add("commons-logging-api.jar"); | |
noTldJars.add("commons-beanutils.jar"); | |
noTldJars.add("commons-fileupload-1.0.jar"); | |
noTldJars.add("commons-pool.jar"); | |
noTldJars.add("commons-digester.jar"); | |
noTldJars.add("commons-logging.jar"); | |
noTldJars.add("commons-collections.jar"); | |
noTldJars.add("commons-el.jar"); | |
noTldJars.add("jakarta-regexp-1.2.jar"); | |
noTldJars.add("jasper-compiler.jar"); | |
noTldJars.add("jasper-runtime.jar"); | |
noTldJars.add("jmx.jar"); | |
noTldJars.add("jmx-tools.jar"); | |
noTldJars.add("jsp-api.jar"); | |
noTldJars.add("jkshm.jar"); | |
noTldJars.add("jkconfig.jar"); | |
noTldJars.add("naming-common.jar"); | |
noTldJars.add("naming-resources.jar"); | |
noTldJars.add("naming-factory.jar"); | |
noTldJars.add("naming-java.jar"); | |
noTldJars.add("servlet-api.jar"); | |
noTldJars.add("servlets-default.jar"); | |
noTldJars.add("servlets-invoker.jar"); | |
noTldJars.add("servlets-common.jar"); | |
noTldJars.add("servlets-webdav.jar"); | |
noTldJars.add("tomcat-util.jar"); | |
noTldJars.add("tomcat-http11.jar"); | |
noTldJars.add("tomcat-jni.jar"); | |
noTldJars.add("tomcat-jk.jar"); | |
noTldJars.add("tomcat-jk2.jar"); | |
noTldJars.add("tomcat-coyote.jar"); | |
noTldJars.add("xercesImpl.jar"); | |
noTldJars.add("xmlParserAPIs.jar"); | |
noTldJars.add("xml-apis.jar"); | |
// JARs from J2SE runtime | |
noTldJars.add("sunjce_provider.jar"); | |
noTldJars.add("ldapsec.jar"); | |
noTldJars.add("localedata.jar"); | |
noTldJars.add("dnsns.jar"); | |
} | |
public TldLocationsCache(ServletContext ctxt) { | |
this(ctxt, true); | |
} | |
/** Constructor. | |
* | |
* @param ctxt the servlet context of the web application in which Jasper | |
* is running | |
* @param redeployMode if true, then the compiler will allow redeploying | |
* a tag library from the same jar, at the expense of slowing down the | |
* server a bit. Note that this may only work on JDK 1.3.1_01a and later, | |
* because of JDK bug 4211817 fixed in this release. | |
* If redeployMode is false, a faster but less capable mode will be used. | |
*/ | |
public TldLocationsCache(ServletContext ctxt, boolean redeployMode) { | |
this.ctxt = ctxt; | |
this.redeployMode = redeployMode; | |
mappings = new Hashtable(); | |
initialized = false; | |
} | |
/** | |
* 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 void setNoTldJars(String jarNames) { | |
if (jarNames != null) { | |
noTldJars.clear(); | |
StringTokenizer tokenizer = new StringTokenizer(jarNames, ","); | |
while (tokenizer.hasMoreElements()) { | |
noTldJars.add(tokenizer.nextToken()); | |
} | |
} | |
} | |
/** | |
* 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 String[] getLocation(String uri) throws JasperException { | |
if (!initialized) { | |
init(); | |
} | |
return (String[]) mappings.get(uri); | |
} | |
/** | |
* Returns a list of absolute paths of the locations in the cache | |
*/ | |
public Set<String> getAbsolutePathsOfLocations() { | |
Set<String> paths = new HashSet<String>(mappings.size()); | |
for (Object value : mappings.values()) { | |
String[] location = (String[]) value; | |
try { | |
File file = FileUtils.toFile(new URL(location[0])); | |
paths.add(file.getAbsolutePath()); | |
} catch (Exception e) { | |
//ignore | |
} | |
} | |
return paths; | |
} | |
/** | |
* 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; | |
} | |
} | |
private void init() throws JasperException { | |
if (initialized) return; | |
try { | |
processWebDotXml(); | |
scanJars(); | |
processTldsInFileSystem("/WEB-INF/"); | |
initialized = true; | |
} catch (Exception ex) { | |
throw new JasperException(Localizer.getMessage( | |
"jsp.error.internal.tldinit", ex.getMessage())); | |
} | |
} | |
/* | |
* Populates taglib map described in web.xml. | |
*/ | |
private void processWebDotXml() throws Exception { | |
InputStream is = null; | |
try { | |
// Acquire input stream to web application deployment descriptor | |
String altDDName = (String)ctxt.getAttribute( | |
Constants.ALT_DD_ATTR); | |
URL uri = null; | |
if (altDDName != null) { | |
try { | |
uri = new URL(FILE_PROTOCOL+altDDName.replace('\\', '/')); | |
} catch (MalformedURLException e) { | |
if (log.isWarnEnabled()) { | |
log.warn(Localizer.getMessage( | |
"jsp.error.internal.filenotfound", | |
altDDName)); | |
} | |
} | |
} else { | |
uri = ctxt.getResource(WEB_XML); | |
if (uri == null && log.isWarnEnabled()) { | |
log.warn(Localizer.getMessage( | |
"jsp.error.internal.filenotfound", | |
WEB_XML)); | |
} | |
} | |
if (uri == null) { | |
return; | |
} | |
is = uri.openStream(); | |
InputSource ip = new InputSource(is); | |
ip.setSystemId(uri.toExternalForm()); | |
// Parse the web application deployment descriptor | |
TreeNode webtld = null; | |
// altDDName is the absolute path of the DD | |
if (altDDName != null) { | |
webtld = new ParserUtils().parseXMLDocument(altDDName, ip); | |
} else { | |
webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip); | |
} | |
// 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 taglibs = webtld.findChildren("taglib"); | |
while (taglibs.hasNext()) { | |
// Parse the next <taglib> element | |
TreeNode taglib = (TreeNode) 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; | |
String tagLoc2 = null; | |
if (tagLoc.endsWith(JAR_FILE_SUFFIX)) { | |
tagLoc = ctxt.getResource(tagLoc).toString(); | |
tagLoc2 = "META-INF/taglib.tld"; | |
} | |
mappings.put(tagUri, new String[] { tagLoc, tagLoc2 }); | |
} | |
} finally { | |
if (is != null) { | |
try { | |
is.close(); | |
} catch (Throwable 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 conn The JarURLConnection to the JAR file to scan | |
* @param ignore true if any exceptions raised when processing the given | |
* JAR should be ignored, false otherwise | |
*/ | |
private void scanJar(JarURLConnection conn, boolean ignore) | |
throws JasperException { | |
JarFile jarFile = null; | |
String resourcePath = conn.getJarFileURL().toString(); | |
try { | |
if (redeployMode) { | |
conn.setUseCaches(false); | |
} | |
jarFile = conn.getJarFile(); | |
Enumeration entries = jarFile.entries(); | |
while (entries.hasMoreElements()) { | |
JarEntry entry = (JarEntry) entries.nextElement(); | |
String name = entry.getName(); | |
if (!name.startsWith("META-INF/")) continue; | |
if (!name.endsWith(".tld")) continue; | |
InputStream stream = jarFile.getInputStream(entry); | |
try { | |
String uri = getUriFromTld(resourcePath, stream); | |
// Add implicit map entry only if its uri is not already | |
// present in the map | |
if (uri != null && mappings.get(uri) == null) { | |
mappings.put(uri, new String[]{ resourcePath, name }); | |
} | |
} finally { | |
if (stream != null) { | |
try { | |
stream.close(); | |
} catch (Throwable t) { | |
// do nothing | |
} | |
} | |
} | |
} | |
} catch (Exception ex) { | |
if (!redeployMode) { | |
// if not in redeploy mode, close the jar in case of an error | |
if (jarFile != null) { | |
try { | |
jarFile.close(); | |
} catch (Throwable t) { | |
// ignore | |
} | |
} | |
} | |
if (!ignore) { | |
throw new JasperException(ex); | |
} | |
} finally { | |
if (redeployMode) { | |
// if in redeploy mode, always close the jar | |
if (jarFile != null) { | |
try { | |
jarFile.close(); | |
} catch (Throwable t) { | |
// ignore | |
} | |
} | |
} | |
} | |
} | |
/* | |
* Searches the filesystem under /WEB-INF for any TLD files, and adds | |
* an implicit map entry to the taglib map for any TLD that has a <uri> | |
* element. | |
*/ | |
private void processTldsInFileSystem(String startPath) | |
throws Exception { | |
Set dirList = ctxt.getResourcePaths(startPath); | |
if (dirList != null) { | |
Iterator it = dirList.iterator(); | |
while (it.hasNext()) { | |
String path = (String) it.next(); | |
if (path.endsWith("/")) { | |
processTldsInFileSystem(path); | |
} | |
if (!path.endsWith(".tld")) { | |
continue; | |
} | |
InputStream stream = ctxt.getResourceAsStream(path); | |
String uri = null; | |
try { | |
uri = getUriFromTld(path, stream); | |
} finally { | |
if (stream != null) { | |
try { | |
stream.close(); | |
} catch (Throwable t) { | |
// do nothing | |
} | |
} | |
} | |
// Add implicit map entry only if its uri is not already | |
// present in the map | |
if (uri != null && mappings.get(uri) == null) { | |
mappings.put(uri, new String[] { path, null }); | |
} | |
} | |
} | |
} | |
/* | |
* Returns the value of the uri element of the given TLD, or null if the | |
* given TLD does not contain any such element. | |
*/ | |
private String getUriFromTld(String resourcePath, InputStream in) | |
throws JasperException | |
{ | |
// Parse the tag library descriptor at the specified resource path | |
TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in); | |
TreeNode uri = tld.findChild("uri"); | |
if (uri != null) { | |
String body = uri.getBody(); | |
if (body != null) | |
return body; | |
} | |
return null; | |
} | |
/* | |
* Scans all JARs accessible to the webapp's classloader and its | |
* parent classloaders for TLDs. | |
* | |
* The list of JARs always includes the JARs under WEB-INF/lib, as well as | |
* all shared JARs in the classloader delegation chain of the webapp's | |
* classloader. | |
* | |
* Considering JARs in the classloader delegation chain constitutes 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>/common/lib). | |
* | |
* The set of shared JARs to be scanned for TLDs is narrowed down by | |
* the <tt>noTldJars</tt> class variable, which contains the names of JARs | |
* that are known not to contain any TLDs. | |
*/ | |
private void scanJars() throws Exception { | |
ClassLoader webappLoader | |
= Thread.currentThread().getContextClassLoader(); | |
ClassLoader loader = webappLoader; | |
while (loader != null) { | |
if (loader instanceof URLClassLoader) { | |
URL[] urls = ((URLClassLoader) loader).getURLs(); | |
for (int i=0; i<urls.length; i++) { | |
URLConnection conn = urls[i].openConnection(); | |
if (conn instanceof JarURLConnection) { | |
if (needScanJar(loader, webappLoader, | |
((JarURLConnection) conn).getJarFile().getName())) { | |
scanJar((JarURLConnection) conn, true); | |
} | |
} else { | |
String urlStr = urls[i].toString(); | |
if (urlStr.startsWith(FILE_PROTOCOL) | |
&& urlStr.endsWith(JAR_FILE_SUFFIX) | |
&& needScanJar(loader, webappLoader, urlStr)) { | |
URL jarURL = new URL("jar:" + urlStr + "!/"); | |
scanJar((JarURLConnection) jarURL.openConnection(), | |
true); | |
} | |
} | |
} | |
} | |
loader = loader.getParent(); | |
} | |
} | |
/* | |
* Determines if the JAR file with the given <tt>jarPath</tt> needs to be | |
* scanned for TLDs. | |
* | |
* @param loader The current classloader in the parent chain | |
* @param webappLoader The webapp classloader | |
* @param jarPath The JAR file path | |
* | |
* @return TRUE if the JAR file identified by <tt>jarPath</tt> needs to be | |
* scanned for TLDs, FALSE otherwise | |
*/ | |
private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader, | |
String jarPath) { | |
if (loader == webappLoader) { | |
// JARs under WEB-INF/lib must be scanned unconditionally according | |
// to the spec. | |
return true; | |
} else { | |
String jarName = jarPath; | |
int slash = jarPath.lastIndexOf('/'); | |
if (slash >= 0) { | |
jarName = jarPath.substring(slash + 1); | |
} | |
return (!noTldJars.contains(jarName)); | |
} | |
} | |
} |