org.apache.jasper.runtime.TldScanner Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.runtime;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Logger;
import java.util.logging.Level;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletException;
import jakarta.servlet.descriptor.TaglibDescriptor;
import jakarta.servlet.descriptor.JspConfigDescriptor;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.xmlparser.ParserUtils;
import org.apache.jasper.xmlparser.TreeNode;
import org.apache.jasper.compiler.Localizer;
/**
* 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 elements in web.xml:
* the uri and location of the tag-library are specified in
* the 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
* 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
* @author Kin-man Chung servlet 3.0 JSP plugin, tld cache etc
*/
public class TldScanner implements ServletContainerInitializer {
// Logger
private static Logger log =
Logger.getLogger(TldScanner.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_XML = "/WEB-INF/web.xml";
private static final String FILE_PROTOCOL = "file:";
private static final String JAR_FILE_SUFFIX = ".jar";
// Names of system Uri's that are ignored if referred in WEB-INF/web.xml
private static HashSet systemUris = new HashSet();
private static HashSet systemUrisJsf = new HashSet();
// A Cache is used for system jar files.
// The key is the name of the jar file, the value is an array of
// TldInfo, one for each of the TLD in the jar file
private static Map jarTldCache =
new ConcurrentHashMap();
private static final String EAR_LIB_CLASSLOADER =
"org.glassfish.javaee.full.deployment.EarLibClassLoader";
private static final String IS_STANDALONE_ATTRIBUTE_NAME =
"org.glassfish.jsp.isStandaloneWebapp";
/**
* The mapping of the 'global' tag library URI (as defined in the tld) to
* the location (resource path) of the TLD associated with that tag library.
* The location is returned as a String array:
* [0] The location of the tld file or the jar file that contains the tld
* [1] If the location is a jar file, this is the location of the tld.
*/
private HashMap mappings;
/**
* A local cache for keeping track which jars have been scanned.
*/
private Map jarTldCacheLocal =
new HashMap();
private ServletContext ctxt;
private boolean isValidationEnabled;
private boolean useMyFaces = false;
private boolean scanListeners; // true if scan tlds for listeners
private boolean doneScanning; // true if all tld scanning done
private boolean blockExternal; // Don't allow external entities
//*********************************************************************
// Constructor and Initilizations
/*
* Initializes the set of JARs that are known not to contain any TLDs
*/
static {
systemUrisJsf.add("http://java.sun.com/jsf/core");
systemUrisJsf.add("http://java.sun.com/jsf/html");
systemUris.add("http://java.sun.com/jsp/jstl/core");
}
/**
* Default Constructor.
* This is only used for implementing ServletContainerInitializer.
* ServletContext will be supplied in the method onStartUp;
*/
public TldScanner() {
}
/**
* Constructor used in Jasper
*/
public TldScanner(ServletContext ctxt, boolean isValidationEnabled) {
this.ctxt = ctxt;
this.isValidationEnabled = isValidationEnabled;
Boolean b = (Boolean) ctxt.getAttribute("com.sun.faces.useMyFaces");
if (b != null) {
useMyFaces = b.booleanValue();
}
blockExternal = Boolean.parseBoolean(ctxt.getInitParameter(
Constants.XML_BLOCK_EXTERNAL_INIT_PARAM));
}
public void onStartup(java.util.Set> c,
ServletContext ctxt) throws ServletException {
this.ctxt = ctxt;
Boolean b = (Boolean) ctxt.getAttribute("com.sun.faces.useMyFaces");
if (b != null) {
useMyFaces = b.booleanValue();
}
ServletRegistration reg = ctxt.getServletRegistration("jsp");
if (reg == null) {
return;
}
String validating = reg.getInitParameter("validating");
isValidationEnabled = "true".equals(validating);
scanListeners = true;
scanTlds();
ctxt.setAttribute(Constants.JSP_TLD_URI_TO_LOCATION_MAP, mappings);
}
/**
* 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.
*
* This method may be called when the scanning is in one of states:
* 1. Called from jspc script, then a full tld scan is required.
* 2. The is the first call after servlet initialization, then system jars
* that are knwon to have tlds but not listeners need to be scanned.
* 3. Sebsequent calls, no need to scans.
*/
@SuppressWarnings("unchecked")
public String[] getLocation(String uri) throws JasperException {
if (mappings == null) {
// Recovering the map done in onStart.
mappings = (HashMap) ctxt.getAttribute(
Constants.JSP_TLD_URI_TO_LOCATION_MAP);
}
if (mappings != null && mappings.get(uri) != null) {
// if the uri is in, return that, and dont bother to do full scan
return mappings.get(uri);
}
if (! doneScanning) {
scanListeners = false;
scanTlds();
doneScanning = true;
}
if (mappings == null) {
// Should never happend
return null;
}
return mappings.get(uri);
}
@SuppressWarnings("unchecked")
Map> getTldMap() {
/*
* System jars with tlds may be passed as a special
* ServletContext attribute
* Map key: a JarURI
* Map value: list of tlds in the jar file
*/
return (Map>)
ctxt.getAttribute("com.sun.appserv.tld.map");
}
@SuppressWarnings("unchecked")
Map> getTldListenerMap() {
/*
* System jars with tlds that are known to contain a listener, and
* may be passed as a special ServletContext attribute
* Map key: a JarURI
* Map value: list of tlds in the jar file
*/
return (Map>)
ctxt.getAttribute("com.sun.appserv.tldlistener.map");
}
/**
* 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;
}
}
/**
* Scan the all the tlds accessible in the web app.
* For performance reasons, this is done in two stages. At servlet
* initialization time, we only scan the jar files for listeners. The
* container passes a list of system jar files that are known to contain
* tlds with listeners. The rest of the jar files will be scanned when
* a JSP page with a tld referenced is compiled.
*/
private void scanTlds() throws JasperException {
mappings = new HashMap();
// Make a local copy of the system jar cache
jarTldCacheLocal.putAll(jarTldCache);
try {
processWebDotXml();
scanJars();
processTldsInFileSystem("/WEB-INF/");
} catch (JasperException ex) {
throw ex;
} catch (Exception ex) {
throw new JasperException(
Localizer.getMessage("jsp.error.internal.tldinit"),
ex);
}
}
/*
* Populates taglib map described in web.xml.
*/
private void processWebDotXml() throws Exception {
// Skip if we are only looking for listeners
if (scanListeners) {
return;
}
JspConfigDescriptor jspConfig = ctxt.getJspConfigDescriptor();
if (jspConfig == null) {
return;
}
for (TaglibDescriptor taglib: jspConfig.getTaglibs()) {
if (taglib == null) {
continue;
}
String tagUri = taglib.getTaglibURI();
String tagLoc = taglib.getTaglibLocation();
if (tagUri == null || tagLoc == null) {
continue;
}
// Ignore system tlds in web.xml, for backward compatibility
if (systemUris.contains(tagUri)
|| (!useMyFaces && systemUrisJsf.contains(tagUri))) {
continue;
}
// Save this location if appropriate
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";
}
if (log.isLoggable(Level.FINE)) {
log.fine( "Add tld map from web.xml: " + tagUri + "=>" + tagLoc+ "," + tagLoc2);
}
mappings.put(tagUri, new String[] { tagLoc, tagLoc2 });
}
}
/**
* Scans the given JarURLConnection for TLD files located in META-INF
* (or a subdirectory of it). If the scanning in is done as part of the
* ServletContextInitializer, the listeners in the tlds in this jar file
* are added to the servlet context, and for any TLD that has a
* element, an implicit map entry is added to the taglib map.
*
* @param conn The JarURLConnection to the JAR file to scan
* @param tldNames the list of tld element to scan. The null value
* indicates all the tlds in this case.
* @param isLocal True if the jar file is under WEB-INF
* false otherwise
*/
private void scanJar(JarURLConnection conn, List tldNames,
boolean isLocal)
throws JasperException {
String resourcePath = conn.getJarFileURL().toString();
TldInfo[] tldInfos = jarTldCacheLocal.get(resourcePath);
// Optimize for most common cases: jars known to NOT have tlds
if (tldInfos != null && tldInfos.length == 0) {
try {
conn.getJarFile().close();
} catch (IOException ex) {
//ignored
}
return;
}
// scan the tld if the jar has not been cached.
if (tldInfos == null) {
JarFile jarFile = null;
ArrayList tldInfoA = new ArrayList();
try {
jarFile = conn.getJarFile();
if (tldNames != null) {
for (String tldName : tldNames) {
JarEntry entry = jarFile.getJarEntry(tldName);
InputStream stream = jarFile.getInputStream(entry);
tldInfoA.add(scanTld(resourcePath, tldName, stream));
}
} else {
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (!name.startsWith("META-INF/")) continue;
if (!name.endsWith(".tld")) continue;
InputStream stream = jarFile.getInputStream(entry);
tldInfoA.add(scanTld(resourcePath, name, stream));
}
}
} catch (IOException ex) {
if (resourcePath.startsWith(FILE_PROTOCOL) &&
!((new File(resourcePath)).exists())) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING,
Localizer.getMessage("jsp.warn.nojar",
resourcePath),
ex);
}
} else {
throw new JasperException(
Localizer.getMessage("jsp.error.jar.io", resourcePath),
ex);
}
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (Throwable t) {
// ignore
}
}
}
// Update the jar TLD cache
tldInfos = tldInfoA.toArray(new TldInfo[tldInfoA.size()]);
jarTldCacheLocal.put(resourcePath, tldInfos);
if (!isLocal) {
// Also update the global cache;
jarTldCache.put(resourcePath, tldInfos);
}
}
// Iterate over tldinfos to add listeners or to map tldlocations
for (TldInfo tldInfo: tldInfos) {
if (scanListeners) {
addListener(tldInfo, isLocal);
}
mapTldLocation(resourcePath, tldInfo, isLocal);
}
}
private void addListener(TldInfo tldInfo, boolean isLocal) {
String uri = tldInfo.getUri();
if (!systemUrisJsf.contains(uri)
|| (isLocal && useMyFaces)
|| (!isLocal && !useMyFaces)) {
for (String listenerClassName: tldInfo.getListeners()) {
if (log.isLoggable(Level.FINE)) {
log.fine( "Add tld listener " + listenerClassName);
}
ctxt.addListener(listenerClassName);
}
}
}
private void mapTldLocation(String resourcePath, TldInfo tldInfo,
boolean isLocal) {
String uri = tldInfo.getUri();
if (uri == null) {
return;
}
if ((isLocal
// Local tld files override the tlds in the jar files,
// unless it is in a system jar (except when using myfaces)
&& mappings.get(uri) == null
&& !systemUris.contains(uri)
&& (!systemUrisJsf.contains(uri) || useMyFaces)
) ||
(!isLocal
// Jars are scanned bottom up, so jars in WEB-INF override
// thos in the system (except when using myfaces)
&& (mappings.get(uri) == null
|| systemUris.contains(uri)
|| (systemUrisJsf.contains(uri) && !useMyFaces)
)
)
) {
String entryName = tldInfo.getEntryName();
if (log.isLoggable(Level.FINE)) {
log.fine("Add tld map from tld in " +
(isLocal? "WEB-INF": "jar: ") + uri + "=>" +
resourcePath + "," + entryName);
}
mappings.put(uri, new String[] {resourcePath, entryName});
}
}
/*
* Searches the filesystem under /WEB-INF for any TLD files, and scans
* them for and elements.
*/
private void processTldsInFileSystem(String startPath)
throws JasperException {
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;
}
if (path.startsWith("/WEB-INF/tags/")
&& !path.endsWith("implicit.tld")) {
throw new JasperException(
Localizer.getMessage(
"jsp.error.tldinit.tldInWebInfTags",
path));
}
InputStream stream = ctxt.getResourceAsStream(path);
TldInfo tldInfo = scanTld(path, null, stream);
// Add listeners or to map tldlocations for this TLD
if (scanListeners) {
addListener(tldInfo, true);
}
mapTldLocation(path, tldInfo, true);
}
}
}
/**
* Scan the given TLD for uri and listeners elements.
*
* @param resourcePath the resource path for the jar file or the tld file.
* @param entryName If the resource path is a jar file, then the name of
* the tld file in the jar, else should be null.
* @param stream The input stream for the tld
* @return The TldInfo for this tld
*/
private TldInfo scanTld(String resourcePath, String entryName,
InputStream stream)
throws JasperException {
try {
// Parse the tag library descriptor at the specified resource path
TreeNode tld = new ParserUtils(blockExternal).parseXMLDocument(
resourcePath, stream, isValidationEnabled);
String uri = null;
TreeNode uriNode = tld.findChild("uri");
if (uriNode != null) {
uri = uriNode.getBody();
}
ArrayList listeners = new ArrayList();
IteratorlistenerNodes = tld.findChildren("listener");
while (listenerNodes.hasNext()) {
TreeNode listener = listenerNodes.next();
TreeNode listenerClass = listener.findChild("listener-class");
if (listenerClass != null) {
String listenerClassName = listenerClass.getBody();
if (listenerClassName != null) {
listeners.add(listenerClassName);
}
}
}
return new TldInfo(uri, entryName,
listeners.toArray(new String[listeners.size()]));
} finally {
if (stream != null) {
try {
stream.close();
} catch (Throwable t) {
// do nothing
}
}
}
}
/*
* 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.,
* /common/lib).
*/
private void scanJars() throws Exception {
ClassLoader webappLoader =
Thread.currentThread().getContextClassLoader();
ClassLoader loader = webappLoader;
Map> tldMap;
if (scanListeners) {
tldMap = getTldListenerMap();
} else {
tldMap= getTldMap();
}
Boolean isStandalone = (Boolean)
ctxt.getAttribute(IS_STANDALONE_ATTRIBUTE_NAME);
while (loader != null) {
if (loader instanceof URLClassLoader) {
boolean isLocal = (loader == webappLoader);
URL[] urls = ((URLClassLoader) loader).getURLs();
List extraJars = new ArrayList();
for (int i=0; i 0) {
List newJars;
do {
newJars = new ArrayList();
for (String jar: extraJars) {
URL jarURL = new URL("jar:" + jar + "!/");
JarURLConnection jconn =
(JarURLConnection) jarURL.openConnection();
jconn.setUseCaches(false);
if (addManifestClassPath(extraJars,newJars,jconn)){
scanJar(jconn, null, true);
}
}
extraJars.addAll(newJars);
} while (newJars.size() != 0);
}
}
if (tldMap != null && isStandalone != null) {
if (isStandalone.booleanValue()) {
break;
} else {
if (EAR_LIB_CLASSLOADER.equals(
loader.getClass().getName())) {
// Do not walk up classloader delegation chain beyond
// EarLibClassLoader
break;
}
}
}
loader = loader.getParent();
}
if (tldMap != null) {
for (URI uri : tldMap.keySet()) {
URL jarURL = new URL("jar:" + uri.toString() + "!/");
scanJar((JarURLConnection)jarURL.openConnection(),
tldMap.get(uri), false);
}
}
}
/*
* Add the jars in the manifest Class-Path to the list "jars"
* @param scannedJars List of jars that has been previously scanned
* @param newJars List of jars from Manifest Class-Path
* @return true is the jar file exists
*/
private boolean addManifestClassPath(List scannedJars,
List newJars,
JarURLConnection jconn){
Manifest manifest;
try {
manifest = jconn.getManifest();
} catch (IOException ex) {
// Maybe non existing jar, ignored
return false;
}
String file = jconn.getJarFileURL().toString();
if (! file.contains("WEB-INF")) {
// Only jar in WEB-INF is considered here
return true;
}
if (manifest == null)
return true;
java.util.jar.Attributes attrs = manifest.getMainAttributes();
String cp = (String) attrs.getValue("Class-Path");
if (cp == null)
return true;
String[] paths = cp.split(" ");
int lastIndex = file.lastIndexOf('/');
if (lastIndex < 0) {
lastIndex = file.lastIndexOf('\\');
}
String baseDir = "";
if (lastIndex > 0) {
baseDir = file.substring(0, lastIndex+1);
}
for (String path: paths) {
String p;
if (path.startsWith("/") || path.startsWith("\\")){
p = "file:" + path;
} else {
p = baseDir + path;
}
if ((scannedJars == null || !scannedJars.contains(p)) &&
!newJars.contains(p) ){
newJars.add(p);
}
}
return true;
}
static class TldInfo {
private String entryName; // The name of the tld file
private String uri; // The uri name for the tld
private String[] listeners; // The listeners in the tld
public TldInfo(String uri, String entryName, String[] listeners) {
this.uri = uri;
this.entryName = entryName;
this.listeners = listeners;
}
public String getEntryName() {
return entryName;
}
public String getUri() {
return uri;
}
public String[] getListeners() {
return listeners;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy