415 lines
16 KiB
Java
415 lines
16 KiB
Java
/*
|
|
* 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.servlet;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.nio.file.FileVisitResult;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.SimpleFileVisitor;
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import javax.servlet.ServletContext;
|
|
import javax.servlet.descriptor.JspConfigDescriptor;
|
|
import javax.servlet.descriptor.TaglibDescriptor;
|
|
|
|
import org.apache.jasper.compiler.JarScannerFactory;
|
|
import org.apache.jasper.compiler.Localizer;
|
|
import org.apache.juli.logging.Log;
|
|
import org.apache.juli.logging.LogFactory;
|
|
import org.apache.tomcat.Jar;
|
|
import org.apache.tomcat.JarScanType;
|
|
import org.apache.tomcat.JarScanner;
|
|
import org.apache.tomcat.JarScannerCallback;
|
|
import org.apache.tomcat.util.descriptor.tld.TaglibXml;
|
|
import org.apache.tomcat.util.descriptor.tld.TldParser;
|
|
import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
|
|
import org.xml.sax.SAXException;
|
|
|
|
/**
|
|
* Scans for and loads Tag Library Descriptors contained in a web application.
|
|
*/
|
|
public class TldScanner {
|
|
private final Log log = LogFactory.getLog(TldScanner.class); // must not be static
|
|
private static final String MSG = "org.apache.jasper.servlet.TldScanner";
|
|
private static final String TLD_EXT = ".tld";
|
|
private static final String WEB_INF = "/WEB-INF/";
|
|
private final ServletContext context;
|
|
private final TldParser tldParser;
|
|
private final Map<String, TldResourcePath> uriTldResourcePathMap = new HashMap<>();
|
|
private final Map<TldResourcePath, TaglibXml> tldResourcePathTaglibXmlMap = new HashMap<>();
|
|
private final List<String> listeners = new ArrayList<>();
|
|
|
|
/**
|
|
* Initialise with the application's ServletContext.
|
|
*
|
|
* @param context the application's servletContext
|
|
* @param namespaceAware should the XML parser used to parse TLD files be
|
|
* configured to be name space aware
|
|
* @param validation should the XML parser used to parse TLD files be
|
|
* configured to use validation
|
|
* @param blockExternal should the XML parser used to parse TLD files be
|
|
* configured to be block references to external
|
|
* entities
|
|
*/
|
|
public TldScanner(ServletContext context,
|
|
boolean namespaceAware,
|
|
boolean validation,
|
|
boolean blockExternal) {
|
|
this.context = context;
|
|
|
|
this.tldParser = new TldParser(namespaceAware, validation, blockExternal);
|
|
}
|
|
|
|
/**
|
|
* Scan for TLDs in all places defined by the specification:
|
|
* <ol>
|
|
* <li>Tag libraries defined by the platform</li>
|
|
* <li>Entries from <jsp-config> in web.xml</li>
|
|
* <li>A resources under /WEB-INF</li>
|
|
* <li>In jar files from /WEB-INF/lib</li>
|
|
* <li>Additional entries from the container</li>
|
|
* </ol>
|
|
*
|
|
* @throws IOException if there was a problem scanning for or loading a TLD
|
|
* @throws SAXException if there was a problem parsing a TLD
|
|
*/
|
|
public void scan() throws IOException, SAXException {
|
|
scanPlatform();
|
|
scanJspConfig();
|
|
scanResourcePaths(WEB_INF);
|
|
scanJars();
|
|
}
|
|
|
|
/**
|
|
* Returns the map of URI to TldResourcePath built by this scanner.
|
|
*
|
|
* @return the map of URI to TldResourcePath
|
|
*/
|
|
public Map<String, TldResourcePath> getUriTldResourcePathMap() {
|
|
return uriTldResourcePathMap;
|
|
}
|
|
|
|
/**
|
|
* Returns the map of TldResourcePath to parsed XML files built by this
|
|
* scanner.
|
|
*
|
|
* @return the map of TldResourcePath to parsed XML files
|
|
*/
|
|
public Map<TldResourcePath,TaglibXml> getTldResourcePathTaglibXmlMap() {
|
|
return tldResourcePathTaglibXmlMap;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all listeners declared by scanned TLDs.
|
|
*
|
|
* @return a list of listener class names
|
|
*/
|
|
public List<String> getListeners() {
|
|
return listeners;
|
|
}
|
|
|
|
/**
|
|
* Set the class loader used by the digester to create objects as a result
|
|
* of this scan. Normally this only needs to be set when using JspC.
|
|
*
|
|
* @param classLoader Class loader to use when creating new objects while
|
|
* parsing TLDs
|
|
*/
|
|
public void setClassLoader(ClassLoader classLoader) {
|
|
tldParser.setClassLoader(classLoader);
|
|
}
|
|
|
|
/**
|
|
* Scan for TLDs required by the platform specification.
|
|
*/
|
|
protected void scanPlatform() {
|
|
}
|
|
|
|
/**
|
|
* Scan for TLDs defined in <jsp-config>.
|
|
* @throws IOException Error reading resources
|
|
* @throws SAXException XML parsing error
|
|
*/
|
|
protected void scanJspConfig() throws IOException, SAXException {
|
|
JspConfigDescriptor jspConfigDescriptor = context.getJspConfigDescriptor();
|
|
if (jspConfigDescriptor == null) {
|
|
return;
|
|
}
|
|
|
|
Collection<TaglibDescriptor> descriptors = jspConfigDescriptor.getTaglibs();
|
|
for (TaglibDescriptor descriptor : descriptors) {
|
|
String taglibURI = descriptor.getTaglibURI();
|
|
String resourcePath = descriptor.getTaglibLocation();
|
|
// Note: Whilst the Servlet 2.4 DTD implies that the location must
|
|
// be a context-relative path starting with '/', JSP.7.3.6.1 states
|
|
// explicitly how paths that do not start with '/' should be
|
|
// handled.
|
|
if (!resourcePath.startsWith("/")) {
|
|
resourcePath = WEB_INF + resourcePath;
|
|
}
|
|
if (uriTldResourcePathMap.containsKey(taglibURI)) {
|
|
log.warn(Localizer.getMessage(MSG + ".webxmlSkip",
|
|
resourcePath,
|
|
taglibURI));
|
|
continue;
|
|
}
|
|
|
|
if (log.isTraceEnabled()) {
|
|
log.trace(Localizer.getMessage(MSG + ".webxmlAdd",
|
|
resourcePath,
|
|
taglibURI));
|
|
}
|
|
|
|
URL url = context.getResource(resourcePath);
|
|
if (url != null) {
|
|
TldResourcePath tldResourcePath;
|
|
if (resourcePath.endsWith(".jar")) {
|
|
// if the path points to a jar file, the TLD is presumed to be
|
|
// inside at META-INF/taglib.tld
|
|
tldResourcePath = new TldResourcePath(url, resourcePath, "META-INF/taglib.tld");
|
|
} else {
|
|
tldResourcePath = new TldResourcePath(url, resourcePath);
|
|
}
|
|
// parse TLD but store using the URI supplied in the descriptor
|
|
TaglibXml tld = tldParser.parse(tldResourcePath);
|
|
uriTldResourcePathMap.put(taglibURI, tldResourcePath);
|
|
tldResourcePathTaglibXmlMap.put(tldResourcePath, tld);
|
|
if (tld.getListeners() != null) {
|
|
listeners.addAll(tld.getListeners());
|
|
}
|
|
} else {
|
|
log.warn(Localizer.getMessage(MSG + ".webxmlFailPathDoesNotExist",
|
|
resourcePath,
|
|
taglibURI));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan web application resources for TLDs, recursively.
|
|
*
|
|
* @param startPath the directory resource to scan
|
|
* @throws IOException if there was a problem scanning for or loading a TLD
|
|
* @throws SAXException if there was a problem parsing a TLD
|
|
*/
|
|
protected void scanResourcePaths(String startPath)
|
|
throws IOException, SAXException {
|
|
|
|
boolean found = false;
|
|
Set<String> dirList = context.getResourcePaths(startPath);
|
|
if (dirList != null) {
|
|
for (String path : dirList) {
|
|
if (path.startsWith("/WEB-INF/classes/")) {
|
|
// Skip: JSP.7.3.1
|
|
} else if (path.startsWith("/WEB-INF/lib/")) {
|
|
// Skip: JSP.7.3.1
|
|
} else if (path.endsWith("/")) {
|
|
scanResourcePaths(path);
|
|
} else if (path.startsWith("/WEB-INF/tags/")) {
|
|
// JSP 7.3.1: in /WEB-INF/tags only consider implicit.tld
|
|
if (path.endsWith("/implicit.tld")) {
|
|
found = true;
|
|
parseTld(path);
|
|
}
|
|
} else if (path.endsWith(TLD_EXT)) {
|
|
found = true;
|
|
parseTld(path);
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage("jsp.tldCache.tldInResourcePath", startPath));
|
|
}
|
|
} else {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage("jsp.tldCache.noTldInResourcePath", startPath));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan for TLDs in JARs in /WEB-INF/lib.
|
|
*/
|
|
public void scanJars() {
|
|
JarScanner scanner = JarScannerFactory.getJarScanner(context);
|
|
TldScannerCallback callback = new TldScannerCallback();
|
|
scanner.scan(JarScanType.TLD, context, callback);
|
|
if (callback.scanFoundNoTLDs()) {
|
|
log.info(Localizer.getMessage("jsp.tldCache.noTldSummary"));
|
|
}
|
|
}
|
|
|
|
protected void parseTld(String resourcePath) throws IOException, SAXException {
|
|
TldResourcePath tldResourcePath =
|
|
new TldResourcePath(context.getResource(resourcePath), resourcePath);
|
|
parseTld(tldResourcePath);
|
|
}
|
|
|
|
protected void parseTld(TldResourcePath path) throws IOException, SAXException {
|
|
if (tldResourcePathTaglibXmlMap.containsKey(path)) {
|
|
// TLD has already been parsed as a result of processing web.xml
|
|
return;
|
|
}
|
|
TaglibXml tld = tldParser.parse(path);
|
|
String uri = tld.getUri();
|
|
if (uri != null) {
|
|
if (!uriTldResourcePathMap.containsKey(uri)) {
|
|
uriTldResourcePathMap.put(uri, path);
|
|
}
|
|
}
|
|
tldResourcePathTaglibXmlMap.put(path, tld);
|
|
if (tld.getListeners() != null) {
|
|
listeners.addAll(tld.getListeners());
|
|
}
|
|
}
|
|
|
|
class TldScannerCallback implements JarScannerCallback {
|
|
private boolean foundJarWithoutTld = false;
|
|
private boolean foundFileWithoutTld = false;
|
|
|
|
|
|
@Override
|
|
public void scan(Jar jar, String webappPath, boolean isWebapp) throws IOException {
|
|
boolean found = false;
|
|
URL jarFileUrl = jar.getJarFileURL();
|
|
jar.nextEntry();
|
|
for (String entryName = jar.getEntryName();
|
|
entryName != null;
|
|
jar.nextEntry(), entryName = jar.getEntryName()) {
|
|
if (!(entryName.startsWith("META-INF/") &&
|
|
entryName.endsWith(TLD_EXT))) {
|
|
continue;
|
|
}
|
|
found = true;
|
|
TldResourcePath tldResourcePath =
|
|
new TldResourcePath(jarFileUrl, webappPath, entryName);
|
|
try {
|
|
parseTld(tldResourcePath);
|
|
} catch (SAXException e) {
|
|
throw new IOException(e);
|
|
}
|
|
}
|
|
if (found) {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage("jsp.tldCache.tldInJar", jarFileUrl.toString()));
|
|
}
|
|
} else {
|
|
foundJarWithoutTld = true;
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage(
|
|
"jsp.tldCache.noTldInJar", jarFileUrl.toString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void scan(File file, final String webappPath, boolean isWebapp)
|
|
throws IOException {
|
|
File metaInf = new File(file, "META-INF");
|
|
if (!metaInf.isDirectory()) {
|
|
return;
|
|
}
|
|
foundFileWithoutTld = false;
|
|
final Path filePath = file.toPath();
|
|
Files.walkFileTree(metaInf.toPath(), new SimpleFileVisitor<Path>() {
|
|
@Override
|
|
public FileVisitResult visitFile(Path file,
|
|
BasicFileAttributes attrs)
|
|
throws IOException {
|
|
Path fileName = file.getFileName();
|
|
if (fileName == null || !fileName.toString().toLowerCase(
|
|
Locale.ENGLISH).endsWith(TLD_EXT)) {
|
|
return FileVisitResult.CONTINUE;
|
|
}
|
|
|
|
foundFileWithoutTld = true;
|
|
String resourcePath;
|
|
if (webappPath == null) {
|
|
resourcePath = null;
|
|
} else {
|
|
String subPath = file.subpath(
|
|
filePath.getNameCount(), file.getNameCount()).toString();
|
|
if ('/' != File.separatorChar) {
|
|
subPath = subPath.replace(File.separatorChar, '/');
|
|
}
|
|
resourcePath = webappPath + "/" + subPath;
|
|
}
|
|
|
|
try {
|
|
URL url = file.toUri().toURL();
|
|
TldResourcePath path = new TldResourcePath(url, resourcePath);
|
|
parseTld(path);
|
|
} catch (SAXException e) {
|
|
throw new IOException(e);
|
|
}
|
|
return FileVisitResult.CONTINUE;
|
|
}
|
|
});
|
|
if (foundFileWithoutTld) {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage("jsp.tldCache.tldInDir",
|
|
file.getAbsolutePath()));
|
|
}
|
|
} else {
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(Localizer.getMessage("jsp.tldCache.noTldInDir",
|
|
file.getAbsolutePath()));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void scanWebInfClasses() throws IOException {
|
|
// This is used when scanAllDirectories is enabled and one or more
|
|
// JARs have been unpacked into WEB-INF/classes as happens with some
|
|
// IDEs.
|
|
|
|
Set<String> paths = context.getResourcePaths(WEB_INF + "classes/META-INF");
|
|
if (paths == null) {
|
|
return;
|
|
}
|
|
|
|
for (String path : paths) {
|
|
if (path.endsWith(TLD_EXT)) {
|
|
try {
|
|
parseTld(path);
|
|
} catch (SAXException e) {
|
|
throw new IOException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
boolean scanFoundNoTLDs() {
|
|
return foundJarWithoutTld;
|
|
}
|
|
}
|
|
}
|