http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/freemarker/ext/jsp/TaglibFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/TaglibFactory.java
b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
deleted file mode 100644
index 1da76c2..0000000
--- a/src/main/java/freemarker/ext/jsp/TaglibFactory.java
+++ /dev/null
@@ -1,2020 +0,0 @@
-/*
- * 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 freemarker.ext.jsp;
-
-import java.beans.IntrospectionException;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FilenameFilter;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.net.JarURLConnection;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-import java.util.TreeSet;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.tagext.Tag;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.EntityResolver;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-
-import freemarker.core.BugException;
-import freemarker.core.Environment;
-import freemarker.ext.beans.BeansWrapper;
-import freemarker.ext.servlet.FreemarkerServlet;
-import freemarker.ext.servlet.HttpRequestHashModel;
-import freemarker.template.DefaultObjectWrapper;
-import freemarker.template.ObjectWrapper;
-import freemarker.template.TemplateHashModel;
-import freemarker.template.TemplateMethodModelEx;
-import freemarker.template.TemplateModel;
-import freemarker.template.TemplateModelException;
-import freemarker.template.TemplateTransformModel;
-import freemarker.template.utility.ClassUtil;
-import freemarker.template.utility.NullArgumentException;
-import freemarker.template.utility.SecurityUtilities;
-import freemarker.template.utility.StringUtil;
-
-/**
- * A hash model associated with a servlet context that can load JSP tag
libraries associated with that servlet context.
- * An instance of this class is made available in the root data model of
templates executed by
- * {@link freemarker.ext.servlet.FreemarkerServlet} under key {@code
JspTaglibs}. It can be added to custom servlets as
- * well to enable JSP taglib integration in them as well.
- */
-public class TaglibFactory implements TemplateHashModel {
-
- /**
- * The default of {@link #getClasspathTlds()}; an empty list.
- *
- * @since 2.3.22
- */
- public static final List DEFAULT_CLASSPATH_TLDS = Collections.EMPTY_LIST;
-
- /**
- * The default of {@link #getMetaInfTldSources()}; a list that contains
- * {@link WebInfPerLibJarMetaInfTldSource#INSTANCE}, which gives the
behavior described in the JSP 2.2
- * specification.
- *
- * @since 2.3.22
- */
- public static final List/*<? extends MetaInfTldSource>*/
DEFAULT_META_INF_TLD_SOURCES
- =
Collections.singletonList(WebInfPerLibJarMetaInfTldSource.INSTANCE);
-
- private static final Logger LOG =
LoggerFactory.getLogger("freemarker.jsp");
-
- private static final int URL_TYPE_FULL = 0;
- private static final int URL_TYPE_ABSOLUTE = 1;
- private static final int URL_TYPE_RELATIVE = 2;
-
- private static final String META_INF_REL_PATH = "META-INF/";
- private static final String META_INF_ABS_PATH = "/META-INF/";
- private static final String DEFAULT_TLD_RESOURCE_PATH = META_INF_ABS_PATH
+ "taglib.tld";
- private static final String JAR_URL_ENTRY_PATH_START = "!/";
-
- private static final String PLATFORM_FILE_ENCODING =
SecurityUtilities.getSystemProperty("file.encoding", "utf-8");
-
- private final ServletContext servletContext;
-
- private ObjectWrapper objectWrapper;
- private List/*<MetaInfTldSource>*/ metaInfTldSources =
DEFAULT_META_INF_TLD_SOURCES;
- private List/*<String>*/ classpathTlds = DEFAULT_CLASSPATH_TLDS;
-
- boolean test_emulateNoUrlToFileConversions = false;
- boolean test_emulateNoJarURLConnections = false;
- boolean test_emulateJarEntryUrlOpenStreamFails = false;
-
- private final Object lock = new Object();
- private final Map taglibs = new HashMap();
- private final Map tldLocations = new HashMap();
- private List/*<String>*/ failedTldLocations = new ArrayList();
- private int nextTldLocationLookupPhase = 0;
-
- /**
- /**
- * Creates a new JSP taglib factory that will be used to load JSP tag
libraries and functions for the web
- * application represented by the passed in {@link ServletContext}.
- * You should at least call {@link #setObjectWrapper(ObjectWrapper)}
before start using this object.
- *
- * <p>This object is only thread-safe after you have stopped calling its
setter methods (and it was properly
- * published to the other threads; see JSR 133 (Java Memory Model)).
- *
- * @param ctx
- * The servlet context whose JSP tag libraries this factory
will load.
- */
- public TaglibFactory(ServletContext ctx) {
- this.servletContext = ctx;
- }
-
- /**
- * Retrieves a JSP tag library identified by an URI. The matching of the
URI to a JSP taglib is done as described in
- * the JSP 1.2 FCS specification.
- *
- * @param taglibUri
- * The URI used in templates to refer to the taglib (like
{@code <%@ taglib uri="..." ... %>} in
- * JSP). It can be any of the three forms allowed by the JSP
specification: absolute URI (like
- * {@code http://example.com/foo}), root relative URI (like
{@code /bar/foo.tld}) and non-root relative
- * URI (like {@code bar/foo.tld}). Note that if a non-root
relative URI is used it's resolved relative to
- * the URL of the current request. In this case, the current
request is obtained by looking up a
- * {@link HttpRequestHashModel} object named <tt>Request</tt>
in the root data model.
- * {@link FreemarkerServlet} provides this object under the
expected name, and custom servlets that want
- * to integrate JSP taglib support should do the same.
- *
- * @return a {@link TemplateHashModel} representing the JSP taglib. Each
element of this hash represents a single
- * custom tag or EL function from the library, implemented as a
{@link TemplateTransformModel} or
- * {@link TemplateMethodModelEx}, respectively.
- */
- @Override
- public TemplateModel get(final String taglibUri) throws
TemplateModelException {
- synchronized (lock) {
- {
- final Taglib taglib = (Taglib) taglibs.get(taglibUri);
- if (taglib != null) {
- return taglib;
- }
- }
-
- boolean failedTldListAlreadyIncluded = false;
- final TldLocation tldLocation;
- final String normalizedTaglibUri;
- try {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Locating TLD for taglib URI " +
StringUtil.jQuoteNoXSS(taglibUri) + ".");
- }
-
- TldLocation explicitlyMappedTldLocation =
getExplicitlyMappedTldLocation(taglibUri);
- if (explicitlyMappedTldLocation != null) {
- tldLocation = explicitlyMappedTldLocation;
- normalizedTaglibUri = taglibUri;
- } else {
- // Taglib URI must be directly the path (no mapping).
-
- final int urlType;
- try {
- urlType = getUriType(taglibUri);
- } catch (MalformedURLException e) {
- throw new TaglibGettingException("Malformed taglib
URI: " + StringUtil.jQuote(taglibUri), e);
- }
- if (urlType == URL_TYPE_RELATIVE) {
- normalizedTaglibUri = resolveRelativeUri(taglibUri);
- } else if (urlType == URL_TYPE_ABSOLUTE) {
- normalizedTaglibUri = taglibUri;
- } else if (urlType == URL_TYPE_FULL) {
- // Per spec., full URI-s can only be resolved through
explicit mapping
- String failedTLDsList = getFailedTLDsList();
- failedTldListAlreadyIncluded = true;
- throw new TaglibGettingException("No TLD was found for
the "
- + StringUtil.jQuoteNoXSS(taglibUri) + " JSP
taglib URI. (TLD-s are searched according "
- + "the JSP 2.2 specification. In development-
and embedded-servlet-container "
- + "setups you may also need the "
- + "\"" +
FreemarkerServlet.INIT_PARAM_META_INF_TLD_LOCATIONS + "\" and "
- + "\"" +
FreemarkerServlet.INIT_PARAM_CLASSPATH_TLDS + "\" "
- + FreemarkerServlet.class.getName() + "
init-params or the similar system "
- + "properites."
- + (failedTLDsList == null
- ? ""
- : " Also note these TLD-s were skipped
earlier due to errors; "
- + "see error in the log: " +
failedTLDsList
- ) + ")");
- } else {
- throw new BugException();
- }
-
- if (!normalizedTaglibUri.equals(taglibUri)) {
- final Taglib taglib = (Taglib)
taglibs.get(normalizedTaglibUri);
- if (taglib != null) {
- return taglib;
- }
- }
-
- tldLocation = isJarPath(normalizedTaglibUri)
- ? (TldLocation) new
ServletContextJarEntryTldLocation(
- normalizedTaglibUri,
DEFAULT_TLD_RESOURCE_PATH)
- : (TldLocation) new
ServletContextTldLocation(normalizedTaglibUri);
- }
- } catch (Exception e) {
- String failedTLDsList = failedTldListAlreadyIncluded ? null :
getFailedTLDsList();
- throw new TemplateModelException(
- "Error while looking for TLD file for " +
StringUtil.jQuoteNoXSS(taglibUri)
- + "; see cause exception."
- + (failedTLDsList == null
- ? ""
- : " (Note: These TLD-s were skipped earlier
due to errors; "
- + "see errors in the log: " + failedTLDsList +
")"),
- e);
- }
-
- try {
- return loadTaglib(tldLocation, normalizedTaglibUri);
- } catch (Exception e) {
- throw new TemplateModelException("Error while loading tag
library for URI "
- + StringUtil.jQuoteNoXSS(normalizedTaglibUri) + " from
TLD location "
- + StringUtil.jQuoteNoXSS(tldLocation) + "; see cause
exception.",
- e);
- }
- }
- }
-
- /**
- * Returns the joined list of failed TLD-s, or {@code null} if there was
none.
- */
- private String getFailedTLDsList() {
- synchronized (failedTldLocations) {
- if (failedTldLocations.isEmpty()) {
- return null;
- }
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < failedTldLocations.size(); i++) {
- if (i != 0) {
- sb.append(", ");
- }
- sb.append(StringUtil.jQuote(failedTldLocations.get(i)));
- }
- return sb.toString();
- }
- }
-
- /**
- * Returns false.
- */
- @Override
- public boolean isEmpty() {
- return false;
- }
-
- /**
- * See {@link #setObjectWrapper(ObjectWrapper)}.
- *
- * @since 2.3.22
- */
- public ObjectWrapper getObjectWrapper() {
- return objectWrapper;
- }
-
- /**
- * Sets the {@link ObjectWrapper} used when building the JSP tag library
{@link TemplateHashModel}-s from the TLD-s.
- * Usually, it should be the same {@link ObjectWrapper} that will be used
inside the templates. {@code null} value
- * is only supported for backward compatibility. For custom EL functions
to be exposed, it must be non-{@code null}
- * and an {@code intanceof} {@link BeansWrapper} (like typically, a {@link
DefaultObjectWrapper}).
- *
- * @since 2.3.22
- */
- public void setObjectWrapper(ObjectWrapper objectWrapper) {
- checkNotStarted();
- this.objectWrapper = objectWrapper;
- }
-
- /**
- * See {@link #setMetaInfTldSources(List)}.
- *
- * @since 2.3.22
- */
- public List/*<Pattern>*/ getMetaInfTldSources() {
- return metaInfTldSources;
- }
-
- /**
- * Sets the list of places where we will look for {@code
META-INF/**}{@code /*.tld} files. By default this is a list
- * that only contains {@link WebInfPerLibJarMetaInfTldSource#INSTANCE}.
This corresponds to the behavior that the
- * JSP specification describes. See the {@link MetaInfTldSource}
subclasses for the possible values and their
- * meanings.
- *
- * <p>
- * This is usually set via the init-params of {@link FreemarkerServlet}.
- *
- * @param metaInfTldSources
- * The list of {@link MetaInfTldSource} subclass instances.
Their order matters if multiple TLD-s define
- * a taglib with the same {@code taglib-uri}. In that case, the
one found by the earlier
- * {@link MetaInfTldSource} wins.
- *
- * @see #setClasspathTlds(List)
- *
- * @since 2.3.22
- */
- public void setMetaInfTldSources(List/*<? extends MetaInfTldSource>*/
metaInfTldSources) {
- checkNotStarted();
- NullArgumentException.check("metaInfTldSources", metaInfTldSources);
- this.metaInfTldSources = metaInfTldSources;
- }
-
- /**
- * See {@link #setClasspathTlds(List)}.
- *
- * @since 2.3.22
- */
- public List/*<String>*/ getClasspathTlds() {
- return classpathTlds;
- }
-
- /**
- * Sets the class-loader resource paths of the TLD-s that aren't inside
the locations covered by
- * {@link #setMetaInfTldSources(List)}, yet you want them to be
discovered. They will be loaded with the class
- * loader provided by the servlet container.
- *
- * <p>
- * This is usually set via the init-params of {@link FreemarkerServlet}.
- *
- * @param classpathTlds
- * List of {@code String}-s, maybe {@code null}. Each item is a
resource path, like
- * {@code "/META-INF/my.tld"}. (Relative resource paths will be
interpreted as root-relative.)
- *
- * @see #setMetaInfTldSources(List)
- *
- * @since 2.3.22
- */
- public void setClasspathTlds(List/*<String>*/ classpathTlds) {
- checkNotStarted();
- NullArgumentException.check("classpathTlds", classpathTlds);
- this.classpathTlds = classpathTlds;
- }
-
- private void checkNotStarted() {
- synchronized (lock) {
- if (nextTldLocationLookupPhase != 0) {
- throw new IllegalStateException(TaglibFactory.class.getName()
+ " object was already in use.");
- }
- }
- }
-
- private TldLocation getExplicitlyMappedTldLocation(final String uri)
throws SAXException, IOException,
- TaglibGettingException {
- while (true) {
- final TldLocation tldLocation = (TldLocation)
tldLocations.get(uri);
- if (tldLocation != null) {
- return tldLocation;
- }
-
- switch (nextTldLocationLookupPhase) {
- case 0:
- // Not in JSP spec.
- addTldLocationsFromClasspathTlds();
- break;
- case 1:
- // JSP 2.2 spec / JSP.7.3.3 (also JSP.3.2)
- addTldLocationsFromWebXml();
- break;
- case 2:
- // JSP 2.2 spec / JSP.7.3.4, FM-specific TLD processing order
#1
- addTldLocationsFromWebInfTlds();
- break;
- case 3:
- // JSP 2.2 spec / JSP.7.3.4, FM-specific TLD processing order
#2
- addTldLocationsFromMetaInfTlds();
- break;
- case 4:
- return null;
- default:
- throw new BugException();
- }
- nextTldLocationLookupPhase++;
- }
- }
-
- private void addTldLocationsFromWebXml() throws SAXException, IOException {
- LOG.debug("Looking for TLD locations in
servletContext:/WEB-INF/web.xml");
-
- WebXmlParser webXmlParser = new WebXmlParser();
- InputStream in =
servletContext.getResourceAsStream("/WEB-INF/web.xml");
- if (in == null) {
- LOG.debug("No web.xml was found in servlet context");
- return;
- }
- try {
- parseXml(in,
servletContext.getResource("/WEB-INF/web.xml").toExternalForm(), webXmlParser);
- } finally {
- in.close();
- }
- }
-
- private void addTldLocationsFromWebInfTlds()
- throws IOException, SAXException {
- LOG.debug("Looking for TLD locations in
servletContext:/WEB-INF/**/*.tld");
- addTldLocationsFromServletContextResourceTlds("/WEB-INF");
- }
-
- private void addTldLocationsFromServletContextResourceTlds(String basePath)
- throws IOException, SAXException {
- Set unsortedResourcePaths = servletContext.getResourcePaths(basePath);
- if (unsortedResourcePaths != null) {
- List/*<String>*/ resourcePaths = new
ArrayList/*<String>*/(unsortedResourcePaths);
- Collections.sort(resourcePaths);
- // First process the files...
- for (Iterator it = resourcePaths.iterator(); it.hasNext(); ) {
- String resourcePath = (String) it.next();
- if (resourcePath.endsWith(".tld")) {
- addTldLocationFromTld(new
ServletContextTldLocation(resourcePath));
- }
- }
- // ... only later the directories
- for (Iterator it = resourcePaths.iterator(); it.hasNext(); ) {
- String resourcePath = (String) it.next();
- if (resourcePath.endsWith("/")) {
-
addTldLocationsFromServletContextResourceTlds(resourcePath);
- }
- }
- }
- }
-
- private void addTldLocationsFromMetaInfTlds() throws IOException,
SAXException {
- if (metaInfTldSources == null || metaInfTldSources.isEmpty()) {
- return;
- }
-
- Set/*<URLWithExternalForm>*/ cpMetaInfDirUrlsWithEF = null;
-
- // Skip past the last "clear":
- int srcIdxStart = 0;
- for (int i = metaInfTldSources.size() - 1; i >= 0; i--) {
- if (metaInfTldSources.get(i) instanceof ClearMetaInfTldSource) {
- srcIdxStart = i + 1;
- break;
- }
- }
-
- for (int srcIdx = srcIdxStart; srcIdx < metaInfTldSources.size();
srcIdx++) {
- MetaInfTldSource miTldSource = (MetaInfTldSource)
metaInfTldSources.get(srcIdx);
-
- if (miTldSource == WebInfPerLibJarMetaInfTldSource.INSTANCE) {
- addTldLocationsFromWebInfPerLibJarMetaInfTlds();
- } else if (miTldSource instanceof ClasspathMetaInfTldSource) {
- ClasspathMetaInfTldSource cpMiTldLocation =
(ClasspathMetaInfTldSource) miTldSource;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Looking for TLD-s in "
- + "classpathRoots[" +
cpMiTldLocation.getRootContainerPattern() + "]"
- + META_INF_ABS_PATH + "**/*.tld");
- }
-
- if (cpMetaInfDirUrlsWithEF == null) {
- cpMetaInfDirUrlsWithEF =
collectMetaInfUrlsFromClassLoaders();
- }
-
- for (Iterator iterator = cpMetaInfDirUrlsWithEF.iterator();
iterator.hasNext(); ) {
- URLWithExternalForm urlWithEF = (URLWithExternalForm)
iterator.next();
- final URL url = urlWithEF.getUrl();
- final boolean isJarUrl = isJarUrl(url);
- final String urlEF = urlWithEF.externalForm;
-
- final String rootContainerUrl;
- if (isJarUrl) {
- int sep = urlEF.indexOf(JAR_URL_ENTRY_PATH_START);
- rootContainerUrl = sep != -1 ? urlEF.substring(0, sep)
: urlEF;
- } else {
- rootContainerUrl = urlEF.endsWith(META_INF_ABS_PATH)
- ? urlEF.substring(0, urlEF.length() -
META_INF_REL_PATH.length())
- : urlEF;
- }
-
- if
(cpMiTldLocation.getRootContainerPattern().matcher(rootContainerUrl).matches())
{
- final File urlAsFile = urlToFileOrNull(url);
- if (urlAsFile != null) {
- addTldLocationsFromFileDirectory(urlAsFile);
- } else if (isJarUrl) {
- addTldLocationsFromJarDirectoryEntryURL(url);
- } else {
- LOG.debug("Can't list entries under this URL;
TLD-s won't be discovered here: {}",
- urlWithEF.getExternalForm());
- }
- }
- }
- } else {
- throw new BugException();
- }
- }
- }
-
- private void addTldLocationsFromWebInfPerLibJarMetaInfTlds() throws
IOException, SAXException {
- LOG.debug("Looking for TLD locations in
servletContext:/WEB-INF/lib/*.{jar,zip}{}*.tld", META_INF_ABS_PATH);
-
- Set libEntPaths = servletContext.getResourcePaths("/WEB-INF/lib");
- if (libEntPaths != null) {
- for (Iterator iter = libEntPaths.iterator(); iter.hasNext(); ) {
- final String libEntryPath = (String) iter.next();
- if (isJarPath(libEntryPath)) {
- addTldLocationsFromServletContextJar(libEntryPath);
- }
- }
- }
- }
-
- private void addTldLocationsFromClasspathTlds() throws SAXException,
IOException, TaglibGettingException {
- if (classpathTlds == null || classpathTlds.size() == 0) {
- return;
- }
-
- LOG.debug("Looking for TLD locations in TLD-s specified in
cfg.classpathTlds");
-
- for (Iterator it = classpathTlds.iterator(); it.hasNext(); ) {
- String tldResourcePath = (String) it.next();
- if (tldResourcePath.trim().length() == 0) {
- throw new TaglibGettingException("classpathTlds can't contain
empty item");
- }
-
- if (!tldResourcePath.startsWith("/")) {
- tldResourcePath = "/" + tldResourcePath;
- }
- if (tldResourcePath.endsWith("/")) {
- throw new TaglibGettingException("classpathTlds can't specify
a directory: " + tldResourcePath);
- }
-
- ClasspathTldLocation tldLocation = new
ClasspathTldLocation(tldResourcePath);
- InputStream in;
- try {
- in = tldLocation.getInputStream();
- } catch (IOException e) {
- if (LOG.isWarnEnabled()) {
- LOG.warn("Ignored classpath TLD location " +
StringUtil.jQuoteNoXSS(tldResourcePath)
- + " because of error", e);
- }
- in = null;
- }
- if (in != null) {
- try {
- addTldLocationFromTld(in, tldLocation);
- } finally {
- in.close();
- }
- }
- }
- }
-
- /**
- * Finds and processes *.tld inside a jar in the servet context.
- */
- private void addTldLocationsFromServletContextJar(
- final String jarResourcePath)
- throws IOException, MalformedURLException, SAXException {
- final String metaInfEntryPath =
normalizeJarEntryPath(META_INF_ABS_PATH, true);
-
- // Null for non-random-access backing resource:
- final JarFile jarFile =
servletContextResourceToFileOrNull(jarResourcePath);
- if (jarFile != null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Scanning for " + META_INF_ABS_PATH + "*.tld-s in
JarFile: servletContext:"
- + jarResourcePath);
- }
- for (Enumeration/*<JarEntry>*/ entries = jarFile.entries();
entries.hasMoreElements(); ) {
- final JarEntry curEntry = (JarEntry) entries.nextElement();
- final String curEntryPath =
normalizeJarEntryPath(curEntry.getName(), false);
- if (curEntryPath.startsWith(metaInfEntryPath) &&
curEntryPath.endsWith(".tld")) {
- addTldLocationFromTld(new
ServletContextJarEntryTldLocation(jarResourcePath, curEntryPath));
- }
- }
- } else { // jarFile == null => fall back to streamed access
- if (LOG.isDebugEnabled()) {
- LOG.debug("Scanning for " + META_INF_ABS_PATH
- + "*.tld-s in ZipInputStream (slow): servletContext:"
+ jarResourcePath);
- }
-
- final InputStream in =
servletContext.getResourceAsStream(jarResourcePath);
- if (in == null) {
- throw new IOException("ServletContext resource not found: " +
jarResourcePath);
- }
- try {
- ZipInputStream zipIn = new ZipInputStream(in);
- try {
- while (true) {
- ZipEntry curEntry = zipIn.getNextEntry();
- if (curEntry == null) break;
-
- String curEntryPath =
normalizeJarEntryPath(curEntry.getName(), false);
- if (curEntryPath.startsWith(metaInfEntryPath) &&
curEntryPath.endsWith(".tld")) {
- addTldLocationFromTld(zipIn,
- new
ServletContextJarEntryTldLocation(jarResourcePath, curEntryPath));
- }
- }
- } finally {
- zipIn.close();
- }
- } finally {
- in.close();
- }
- }
- }
-
- /**
- * Finds and processes *.tld inside a directory in a jar.
- *
- * @param jarBaseEntryUrl
- * Something like "jar:file:/C:/foo%20bar/baaz.jar!/META-INF/".
If this is not a jar(-like) URL, the
- * behavior is undefined.
- */
- private void addTldLocationsFromJarDirectoryEntryURL(final URL
jarBaseEntryUrl)
- throws IOException, MalformedURLException, SAXException {
- // Null for non-random-access backing resource:
- final JarFile jarFile;
- // Not null; the path of the directory *inside* the JAR where we will
search
- // (like "/META-INF/" in "jar:file:/C:/foo%20bar/baaz.jar!/META-INF/"):
- final String baseEntryPath;
- // Null when URLConnection is used
- // (like "file:/C:/foo%20bar/baaz.jar" in
"jar:file:/C:/foo%20bar/baaz.jar!/META-INF/"):
- final String rawJarContentUrlEF;
- {
- final URLConnection urlCon = jarBaseEntryUrl.openConnection();
- if (!test_emulateNoJarURLConnections && urlCon instanceof
JarURLConnection) {
- final JarURLConnection jarCon = (JarURLConnection) urlCon;
- jarFile = jarCon.getJarFile();
- rawJarContentUrlEF = null; // Not used as we have a
JarURLConnection
- baseEntryPath = normalizeJarEntryPath(jarCon.getEntryName(),
true);
- if (baseEntryPath == null) {
- throw
newFailedToExtractEntryPathException(jarBaseEntryUrl);
- }
- } else {
- final String jarBaseEntryUrlEF =
jarBaseEntryUrl.toExternalForm();
- final int jarEntrySepIdx =
jarBaseEntryUrlEF.indexOf(JAR_URL_ENTRY_PATH_START);
- if (jarEntrySepIdx == -1) {
- throw
newFailedToExtractEntryPathException(jarBaseEntryUrl);
- }
- rawJarContentUrlEF =
jarBaseEntryUrlEF.substring(jarBaseEntryUrlEF.indexOf(':') + 1, jarEntrySepIdx);
- baseEntryPath = normalizeJarEntryPath(
- jarBaseEntryUrlEF.substring(jarEntrySepIdx +
JAR_URL_ENTRY_PATH_START.length()), true);
-
- File rawJarContentAsFile = urlToFileOrNull(new
URL(rawJarContentUrlEF));
- jarFile = rawJarContentAsFile != null ? new
JarFile(rawJarContentAsFile) : null;
- }
- }
- if (jarFile != null) { // jarFile == null => fall back to streamed
access
- if (LOG.isDebugEnabled()) {
- LOG.debug("Scanning for " + META_INF_ABS_PATH + "**/*.tld-s in
random access mode: "
- + jarBaseEntryUrl);
- }
- for (Enumeration/*<JarEntry>*/ entries = jarFile.entries();
entries.hasMoreElements(); ) {
- final JarEntry curEntry = (JarEntry) entries.nextElement();
- final String curEntryPath =
normalizeJarEntryPath(curEntry.getName(), false);
- if (curEntryPath.startsWith(baseEntryPath) &&
curEntryPath.endsWith(".tld")) {
- final String curEntryBaseRelativePath =
curEntryPath.substring(baseEntryPath.length());
- final URL tldUrl = createJarEntryUrl(jarBaseEntryUrl,
curEntryBaseRelativePath);
- addTldLocationFromTld(new JarEntryUrlTldLocation(tldUrl,
null));
- }
- }
- } else {
- // Not a random-access file, so we fall back to the slower
ZipInputStream approach.
- if (LOG.isDebugEnabled()) {
- LOG.debug("Scanning for " + META_INF_ABS_PATH + "**/*.tld-s in
stream mode (slow): "
- + rawJarContentUrlEF);
- }
-
- final InputStream in = new URL(rawJarContentUrlEF).openStream();
- try {
- ZipInputStream zipIn = new ZipInputStream(in);
- try {
- while (true) {
- ZipEntry curEntry = zipIn.getNextEntry();
- if (curEntry == null) break;
-
- String curEntryPath =
normalizeJarEntryPath(curEntry.getName(), false);
- if (curEntryPath.startsWith(baseEntryPath) &&
curEntryPath.endsWith(".tld")) {
- final String curEntryBaseRelativePath =
curEntryPath.substring(baseEntryPath.length());
- final URL tldUrl =
createJarEntryUrl(jarBaseEntryUrl, curEntryBaseRelativePath);
- addTldLocationFromTld(zipIn, new
JarEntryUrlTldLocation(tldUrl, null));
- }
- }
- } finally {
- zipIn.close();
- }
- } catch (ZipException e) {
- // ZipException messages miss the zip URL
- IOException ioe = new IOException("Error reading ZIP (see
cause excepetion) from: "
- + rawJarContentUrlEF);
- try {
- ioe.initCause(e);
- } catch (Exception e2) {
- throw e;
- }
- throw ioe;
- } finally {
- in.close();
- }
- }
- }
-
- private void addTldLocationsFromFileDirectory(final File dir) throws
IOException, SAXException {
- if (dir.isDirectory()) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Scanning for *.tld-s in File directory: " +
StringUtil.jQuoteNoXSS(dir));
- }
- File[] tldFiles = dir.listFiles(new FilenameFilter() {
-
- @Override
- public boolean accept(File urlAsFile, String name) {
- return isTldFileNameIgnoreCase(name);
- }
-
- });
- if (tldFiles == null) {
- throw new IOException("Can't list this directory for some
reason: " + dir);
- }
- for (int i = 0; i < tldFiles.length; i++) {
- final File file = tldFiles[i];
- addTldLocationFromTld(new FileTldLocation(file));
- }
- } else {
- LOG.warn("Skipped scanning for *.tld for non-existent directory: "
+ StringUtil.jQuoteNoXSS(dir));
- }
- }
-
- /**
- * Adds the TLD location mapping from the TLD itself.
- */
- private void addTldLocationFromTld(TldLocation tldLocation) throws
IOException, SAXException {
- InputStream in = tldLocation.getInputStream();
- try {
- addTldLocationFromTld(in, tldLocation);
- } finally {
- in.close();
- }
- }
-
- /**
- * Use this overload only if you already have the {@link InputStream} for
some reason, otherwise use
- * {@link #addTldLocationFromTld(TldLocation)}.
- *
- * @param reusedIn
- * The stream that we already had (so we don't have to open a
new one from the {@code tldLocation}).
- */
- private void addTldLocationFromTld(InputStream reusedIn, TldLocation
tldLocation) throws SAXException,
- IOException {
- String taglibUri;
- try {
- taglibUri = getTaglibUriFromTld(reusedIn,
tldLocation.getXmlSystemId());
- } catch (SAXException e) {
- LOG.error("Error while parsing TLD; skipping: {}", tldLocation, e);
- synchronized (failedTldLocations) {
- failedTldLocations.add(tldLocation.toString());
- }
- taglibUri = null;
- }
- if (taglibUri != null) {
- addTldLocation(tldLocation, taglibUri);
- }
- }
-
- private void addTldLocation(TldLocation tldLocation, String taglibUri) {
- if (tldLocations.containsKey(taglibUri)) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Ignored duplicate mapping of taglib URI " +
StringUtil.jQuoteNoXSS(taglibUri)
- + " to TLD location " +
StringUtil.jQuoteNoXSS(tldLocation));
- }
- } else {
- tldLocations.put(taglibUri, tldLocation);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Mapped taglib URI " +
StringUtil.jQuoteNoXSS(taglibUri)
- + " to TLD location " +
StringUtil.jQuoteNoXSS(tldLocation));
- }
- }
- }
-
- private static Set/*<URLWithExternalForm>*/
collectMetaInfUrlsFromClassLoaders() throws IOException {
- final Set/*<URLWithExternalForm>*/ metainfDirUrls = new TreeSet();
-
- final ClassLoader tccl = tryGetThreadContextClassLoader();
- if (tccl != null) {
- collectMetaInfUrlsFromClassLoader(tccl, metainfDirUrls);
- }
-
- final ClassLoader cccl = TaglibFactory.class.getClassLoader();
- if (!isDescendantOfOrSameAs(tccl, cccl)) {
- collectMetaInfUrlsFromClassLoader(cccl, metainfDirUrls);
- }
- return metainfDirUrls;
- }
-
- private static void collectMetaInfUrlsFromClassLoader(ClassLoader cl,
Set/* <URLWithExternalForm> */metainfDirUrls)
- throws IOException {
- Enumeration/*<URL>*/ urls = cl.getResources(META_INF_REL_PATH);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- metainfDirUrls.add(new URLWithExternalForm((URL)
urls.nextElement()));
- }
- }
- }
-
- private String getTaglibUriFromTld(InputStream tldFileIn, String
tldFileXmlSystemId) throws SAXException, IOException {
- TldParserForTaglibUriExtraction tldParser = new
TldParserForTaglibUriExtraction();
- parseXml(tldFileIn, tldFileXmlSystemId, tldParser);
- return tldParser.getTaglibUri();
- }
-
- /**
- * @param tldLocation
- * The physical location of the TLD file
- * @param taglibUri
- * The URI used in templates to refer to the taglib (like
{@code <%@ taglib uri="..." ... %>} in JSP).
- */
- private TemplateHashModel loadTaglib(TldLocation tldLocation, String
taglibUri) throws IOException, SAXException {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Loading taglib for URI " +
StringUtil.jQuoteNoXSS(taglibUri)
- + " from TLD location " +
StringUtil.jQuoteNoXSS(tldLocation));
- }
- final Taglib taglib = new Taglib(servletContext, tldLocation,
objectWrapper);
- taglibs.put(taglibUri, taglib);
- tldLocations.remove(taglibUri);
- return taglib;
- }
-
- private static void parseXml(InputStream in, String systemId,
DefaultHandler handler)
- throws SAXException, IOException {
- InputSource inSrc = new InputSource();
- inSrc.setSystemId(systemId);
- inSrc.setByteStream(toCloseIgnoring(in));
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- factory.setNamespaceAware(false);
- factory.setValidating(false); // Especially as we use dummy empty DTD-s
- XMLReader reader;
- try {
- reader = factory.newSAXParser().getXMLReader();
- } catch (ParserConfigurationException e) {
- // Not expected
- throw new RuntimeException("XML parser setup failed", e);
- }
- reader.setEntityResolver(new EmptyContentEntityResolver()); // To deal
with referred DTD-s
- reader.setContentHandler(handler);
- reader.setErrorHandler(handler);
-
- reader.parse(inSrc);
- }
-
- private static String resolveRelativeUri(String uri) throws
TaglibGettingException {
- TemplateModel reqHash;
- try {
- reqHash = Environment.getCurrentEnvironment().getVariable(
- FreemarkerServlet.KEY_REQUEST_PRIVATE);
- } catch (TemplateModelException e) {
- throw new TaglibGettingException("Failed to get FreemarkerServlet
request information", e);
- }
- if (reqHash instanceof HttpRequestHashModel) {
- HttpServletRequest req =
- ((HttpRequestHashModel) reqHash).getRequest();
- String pi = req.getPathInfo();
- String reqPath = req.getServletPath();
- if (reqPath == null) {
- reqPath = "";
- }
- reqPath += (pi == null ? "" : pi);
- // We don't care about paths with ".." in them. If the container
- // wishes to resolve them on its own, let it be.
- int lastSlash = reqPath.lastIndexOf('/');
- if (lastSlash != -1) {
- return reqPath.substring(0, lastSlash + 1) + uri;
- } else {
- return '/' + uri;
- }
- }
- throw new TaglibGettingException(
- "Can't resolve relative URI " + uri + " as request URL
information is unavailable.");
- }
-
- /**
- * Ignores attempts to close the stream.
- */
- private static FilterInputStream toCloseIgnoring(InputStream in) {
- return new FilterInputStream(in) {
- @Override
- public void close() {
- // Do nothing
- }
- };
- }
-
- private static int getUriType(String uri) throws MalformedURLException {
- if (uri == null) {
- throw new IllegalArgumentException("null is not a valid URI");
- }
- if (uri.length() == 0) {
- throw new MalformedURLException("empty string is not a valid URI");
- }
- final char c0 = uri.charAt(0);
- if (c0 == '/') {
- return URL_TYPE_ABSOLUTE;
- }
- // Check if it conforms to RFC 3986 3.1 in order to qualify as ABS_URI
- if (c0 < 'a' || c0 > 'z') { // First char of scheme must be alpha
- return URL_TYPE_RELATIVE;
- }
- final int colon = uri.indexOf(':');
- if (colon == -1) { // Must have a colon
- return URL_TYPE_RELATIVE;
- }
- // Subsequent chars must be [a-z,0-9,+,-,.]
- for (int i = 1; i < colon; ++i) {
- final char c = uri.charAt(i);
- if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '+' && c
!= '-' && c != '.') {
- return URL_TYPE_RELATIVE;
- }
- }
- return URL_TYPE_FULL;
- }
-
- private static boolean isJarPath(final String uriPath) {
- return uriPath.endsWith(".jar") || uriPath.endsWith(".zip");
- }
-
- private static boolean isJarUrl(URL url) {
- final String scheme = url.getProtocol();
- return "jar".equals(scheme) || "zip".equals(scheme)
- || "vfszip".equals(scheme) // JBoss AS
- || "wsjar".equals(scheme); // WebSphere
- }
-
- private static URL createJarEntryUrl(final URL jarBaseEntryUrl, String
relativeEntryPath)
- throws MalformedURLException {
- if (relativeEntryPath.startsWith("/")) {
- relativeEntryPath = relativeEntryPath.substring(1);
- }
- try {
- return new URL(jarBaseEntryUrl,
StringUtil.URLPathEnc(relativeEntryPath, PLATFORM_FILE_ENCODING));
- } catch (UnsupportedEncodingException e) {
- throw new BugException();
- }
- }
-
- /**
- * Trying to hide any JarFile implementation inconsistencies.
- */
- private static String normalizeJarEntryPath(String jarEntryDirPath,
boolean directory) {
- // Not know to be a problem, but to be in the safe side:
- if (!jarEntryDirPath.startsWith("/")) {
- jarEntryDirPath = "/" + jarEntryDirPath;
- }
-
- // Known to be a problem:
- if (directory && !jarEntryDirPath.endsWith("/")) {
- jarEntryDirPath = jarEntryDirPath + "/";
- }
-
- return jarEntryDirPath;
- }
-
- private static MalformedURLException
newFailedToExtractEntryPathException(final URL url) {
- return new MalformedURLException("Failed to extract jar entry path
from: " + url);
- }
-
- /**
- * Converts an URL to a {@code File} object, if the URL format (scheme)
makes is possible.
- */
- private File urlToFileOrNull(URL url) {
- if (test_emulateNoUrlToFileConversions) {
- return null;
- }
-
- if (!"file".equals(url.getProtocol())) {
- return null;
- }
-
- String filePath;
- try {
- // Using URI instead of URL, so we get an URL-decoded path.
- filePath = url.toURI().getSchemeSpecificPart();
- } catch (URISyntaxException e) { // Can happen, as URI-s are stricter
than legacy URL-s.
- // URL.getFile() doesn't decode %XX-s (used for spaces and
non-US-ASCII letters usually), so we do.
- // As it was originally created for a file somewhere, we hope that
it uses the platform default encoding.
- try {
- filePath = URLDecoder.decode(url.getFile(),
PLATFORM_FILE_ENCODING);
- } catch (UnsupportedEncodingException e2) {
- throw new BugException(e2);
- }
- }
- return new File(filePath);
- }
-
- /**
- * Gets a servlet context resource as a {@link JarFile} if possible,
return {@code null} otherwise.
- * For BC only, we try to get over errors during URL/JarFile construction,
so then the caller can fall back to the
- * legacy ZipInputStream-based approach.
- */
- private JarFile servletContextResourceToFileOrNull(final String
jarResourcePath) throws MalformedURLException,
- IOException {
- URL jarResourceUrl = servletContext.getResource(jarResourcePath);
- if (jarResourceUrl == null) {
- LOG.error("ServletContext resource URL was null (missing
resource?): {}", jarResourcePath);
- return null;
- }
-
- File jarResourceAsFile = urlToFileOrNull(jarResourceUrl);
- if (jarResourceAsFile == null) {
- // Expected - it's just not File
- return null;
- }
-
- if (!jarResourceAsFile.isFile()) {
- LOG.error("Jar file doesn't exist - falling back to stream mode:
{}", jarResourceAsFile);
- return null;
- }
-
- return new JarFile(jarResourceAsFile);
- }
-
- private static URL tryCreateServletContextJarEntryUrl(
- ServletContext servletContext, final String
servletContextJarFilePath, final String entryPath) {
- try {
- final URL jarFileUrl =
servletContext.getResource(servletContextJarFilePath);
- if (jarFileUrl == null) {
- throw new IOException("Servlet context resource not found: " +
servletContextJarFilePath);
- }
- return new URL(
- "jar:"
- + jarFileUrl.toURI()
- + JAR_URL_ENTRY_PATH_START
- + URLEncoder.encode(
- entryPath.startsWith("/") ? entryPath.substring(1)
: entryPath,
- PLATFORM_FILE_ENCODING));
- } catch (Exception e) {
- LOG.error("Couldn't get URL for serlvetContext resource "
- + StringUtil.jQuoteNoXSS(servletContextJarFilePath)
- + " / jar entry " + StringUtil.jQuoteNoXSS(entryPath),
- e);
- return null;
- }
- }
-
- private static boolean isTldFileNameIgnoreCase(String name) {
- final int dotIdx = name.lastIndexOf('.');
- if (dotIdx < 0) return false;
- final String extension = name.substring(dotIdx + 1).toLowerCase();
- return extension.equalsIgnoreCase("tld");
- }
-
- private static ClassLoader tryGetThreadContextClassLoader() {
- ClassLoader tccl;
- try {
- tccl = Thread.currentThread().getContextClassLoader();
- } catch (SecurityException e) {
- // Suppress
- tccl = null;
- LOG.warn("Can't access Thread Context ClassLoader", e);
- }
- return tccl;
- }
-
- private static boolean isDescendantOfOrSameAs(ClassLoader descendant,
ClassLoader parent) {
- while (true) {
- if (descendant == null) {
- return false;
- }
- if (descendant == parent) {
- return true;
- }
- descendant = descendant.getParent();
- }
- }
-
- /**
- * A location within which we will look for {@code META-INF/**}{@code
/*.tld}-s. Used in the parameter to
- * {@link #setMetaInfTldSources}. See concrete subclasses for more.
- *
- * @since 2.3.22
- */
- public static abstract class MetaInfTldSource {
- private MetaInfTldSource() { }
- }
-
- /**
- * To search TLD-s under
<tt>sevletContext:/WEB-INF/lib/*.{jar,zip}/META-INF/**</tt><tt>/*.tld</tt>, as
requested by
- * the JSP specification. Note that these also used to be in the
classpath, so it's redundant to use this together
- * with a sufficiently permissive {@link ClasspathMetaInfTldSource}.
- *
- * @since 2.3.22
- */
- public static final class WebInfPerLibJarMetaInfTldSource extends
MetaInfTldSource {
- public final static WebInfPerLibJarMetaInfTldSource INSTANCE = new
WebInfPerLibJarMetaInfTldSource();
- private WebInfPerLibJarMetaInfTldSource() { };
- }
-
- /**
- * To search TLD-s under {@code META-INF/**}{@code /*.tld} inside
classpath root containers, that is, in directories
- * and jar-s that are in the classpath (or are visible for the class
loader otherwise). It will only search inside
- * those roots whose URL matches the pattern specified in the constructor.
It correctly handles when multiple roots
- * contain a TLD with the same name (typically, {@code
META-INF/taglib.tld}), that is, those TLD-s won't shadow each
- * other, all of them will be loaded independently.
- *
- * <p>
- * Note that this TLD discovery mechanism is not part of the JSP
specification.
- *
- * @since 2.3.22
- */
- public static final class ClasspathMetaInfTldSource extends
MetaInfTldSource {
-
- private final Pattern rootContainerPattern;
-
- /**
- * @param rootContainerPattern
- * The pattern against which the classpath root container
URL-s will be matched. For example, to only
- * search in jar-s whose name contains "taglib", the patter
should be {@code ".*taglib\.jar$"}. To
- * search everywhere, the pattern should be {@code ".*"}.
The pattern need to match the whole URL,
- * not just part of it.
- */
- public ClasspathMetaInfTldSource(Pattern rootContainerPattern) {
- this.rootContainerPattern = rootContainerPattern;
- }
-
- /**
- * See constructor argument: {@link
#ClasspathMetaInfTldSource(Pattern)}.
- */
- public Pattern getRootContainerPattern() {
- return rootContainerPattern;
- };
-
- }
-
- /**
- * When it occurs in the {@link MetaInfTldSource} list, all {@link
MetaInfTldSource}-s before it will be disabled.
- * This is useful when the list is assembled from multiple sources, and
some want to re-start it, rather than append
- * to the end of it.
- *
- * @see FreemarkerServlet#SYSTEM_PROPERTY_META_INF_TLD_SOURCES
- * @see TaglibFactory#setMetaInfTldSources(List)
- */
- public static final class ClearMetaInfTldSource extends MetaInfTldSource {
- public final static ClearMetaInfTldSource INSTANCE = new
ClearMetaInfTldSource();
- private ClearMetaInfTldSource() { };
- }
-
- private interface TldLocation {
-
- /**
- * Reads the TLD file.
- * @return Not {@code null}
- */
- public abstract InputStream getInputStream() throws IOException;
-
- /**
- * The absolute URL of the TLD file.
- * @return Not {@code null}
- */
- public abstract String getXmlSystemId() throws IOException;
- }
-
- private interface InputStreamFactory {
- InputStream getInputStream();
-
- }
-
- private class ServletContextTldLocation implements TldLocation {
-
- private final String fileResourcePath;
-
- public ServletContextTldLocation(String fileResourcePath) {
- this.fileResourcePath = fileResourcePath;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- final InputStream in =
servletContext.getResourceAsStream(fileResourcePath);
- if (in == null) {
- throw newResourceNotFoundException();
- }
- return in;
- }
-
- @Override
- public String getXmlSystemId() throws IOException {
- final URL url = servletContext.getResource(fileResourcePath);
- return url != null ? url.toExternalForm() : null;
- }
-
- private IOException newResourceNotFoundException() {
- return new IOException("Resource not found: servletContext:" +
fileResourcePath);
- }
-
- @Override
- public final String toString() {
- return "servletContext:" + fileResourcePath;
- }
-
- }
-
-
- /**
- * Points to plain class loader resource (regardless of if in what
classpath root container it's in).
- */
- private static class ClasspathTldLocation implements TldLocation {
-
- private final String resourcePath;
-
- public ClasspathTldLocation(String resourcePath) {
- if (!resourcePath.startsWith("/")) {
- throw new IllegalArgumentException("\"resourcePath\" must
start with /");
- }
- this.resourcePath = resourcePath;
- }
-
- @Override
- public String toString() {
- return "classpath:" + resourcePath;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- ClassLoader tccl = tryGetThreadContextClassLoader();
- if (tccl != null) {
- final InputStream in =
getClass().getResourceAsStream(resourcePath);
- if (in != null) {
- return in;
- }
- }
-
- final InputStream in =
getClass().getResourceAsStream(resourcePath);
- if (in == null) {
- throw newResourceNotFoundException();
- }
- return in;
- }
-
- @Override
- public String getXmlSystemId() throws IOException {
- ClassLoader tccl = tryGetThreadContextClassLoader();
- if (tccl != null) {
- final URL url = getClass().getResource(resourcePath);
- if (url != null) {
- return url.toExternalForm();
- }
- }
-
- final URL url = getClass().getResource(resourcePath);
- return url == null ? null : url.toExternalForm();
- }
-
- private IOException newResourceNotFoundException() {
- return new IOException("Resource not found: classpath:" +
resourcePath);
- }
-
- }
-
- private abstract class JarEntryTldLocation implements TldLocation {
-
- /**
- * Can be {@code null} if there was some technical problem, but then
- * {@link #fallbackRawJarContentInputStreamFactory} and {@link
#entryPath} will be non-{@code null}
- */
- private final URL entryUrl;
- private final InputStreamFactory
fallbackRawJarContentInputStreamFactory;
- private final String entryPath;
-
- public JarEntryTldLocation(URL entryUrl, InputStreamFactory
fallbackRawJarContentInputStreamFactory,
- String entryPath) {
- if (entryUrl == null) {
-
NullArgumentException.check(fallbackRawJarContentInputStreamFactory);
- NullArgumentException.check(entryPath);
- }
-
- this.entryUrl = entryUrl;
- this.fallbackRawJarContentInputStreamFactory =
fallbackRawJarContentInputStreamFactory;
- this.entryPath = entryPath != null ?
normalizeJarEntryPath(entryPath, false) : null;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- if (entryUrl != null) {
- try {
- if (test_emulateJarEntryUrlOpenStreamFails) {
- throw new RuntimeException("Test only");
- }
- return entryUrl.openStream();
- } catch (Exception e) {
- if (fallbackRawJarContentInputStreamFactory == null) {
- // Java 7 (Java 6?): We could just re-throw `e`
- if (e instanceof IOException) {
- throw (IOException) e;
- }
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
- throw new RuntimeException(e);
- }
- LOG.error("Failed to open InputStream for URL (will try
fallback stream): {}", entryUrl);
- }
- // Retry with the fallbackRawJarContentInputStreamFactory
comes.
- }
-
- final String entryPath;
- if (this.entryPath != null) {
- entryPath = this.entryPath;
- } else {
- if (entryUrl == null) {
- throw new IOException("Nothing to deduce jar entry path
from.");
- }
- String urlEF = entryUrl.toExternalForm();
- int sepIdx = urlEF.indexOf(JAR_URL_ENTRY_PATH_START);
- if (sepIdx == -1) {
- throw new IOException("Couldn't extract jar entry path
from: " + urlEF);
- }
- entryPath = normalizeJarEntryPath(
- URLDecoder.decode(
- urlEF.substring(sepIdx +
JAR_URL_ENTRY_PATH_START.length()),
- PLATFORM_FILE_ENCODING),
- false);
- }
-
- InputStream rawIn = null;
- ZipInputStream zipIn = null;
- boolean returnedZipIn = false;
- try {
- rawIn =
fallbackRawJarContentInputStreamFactory.getInputStream();
- if (rawIn == null) {
- throw new IOException("Jar's InputStreamFactory (" +
fallbackRawJarContentInputStreamFactory
- + ") says the resource doesn't exist.");
- }
- zipIn = new ZipInputStream(rawIn);
- while (true) {
- final ZipEntry macthedJarEntry = zipIn.getNextEntry();
- if (macthedJarEntry == null) {
- throw new IOException("Could not find JAR entry " +
StringUtil.jQuoteNoXSS(entryPath) + ".");
- }
- if
(entryPath.equals(normalizeJarEntryPath(macthedJarEntry.getName(), false))) {
- returnedZipIn = true;
- return zipIn;
- }
- }
- } finally {
- if (!returnedZipIn) {
- if (zipIn != null) {
- zipIn.close();
- }
- if (rawIn != null) {
- rawIn.close();
- }
- }
- }
- }
-
- @Override
- public String getXmlSystemId() {
- return entryUrl != null ? entryUrl.toExternalForm() : null;
- }
-
- @Override
- public String toString() {
- return entryUrl != null
- ? entryUrl.toExternalForm()
- : "jar:{" + fallbackRawJarContentInputStreamFactory + "}!"
+ entryPath;
- }
-
- }
-
- private class JarEntryUrlTldLocation extends JarEntryTldLocation {
-
- private JarEntryUrlTldLocation(URL entryUrl, InputStreamFactory
fallbackRawJarContentInputStreamFactory) {
- super(entryUrl, fallbackRawJarContentInputStreamFactory, null);
- }
-
- }
-
- /**
- * Points to a file entry inside a jar, with optional {@link
ZipInputStream} fallback.
- */
- private class ServletContextJarEntryTldLocation extends
JarEntryTldLocation {
-
- /**
- * For creating instance based on the servlet context resource path of
a jar.
- * While it tries to construct and use an URL that points directly to
the target entry inside the jar, it will
- * operate even if these URL-related operations fail.
- */
- private ServletContextJarEntryTldLocation(final String
servletContextJarFilePath, final String entryPath) {
- super(
- tryCreateServletContextJarEntryUrl(servletContext,
servletContextJarFilePath, entryPath),
- new InputStreamFactory() {
- @Override
- public InputStream getInputStream() {
- return
servletContext.getResourceAsStream(servletContextJarFilePath);
- }
-
- @Override
- public String toString() {
- return "servletContext:" +
servletContextJarFilePath;
- }
- },
- entryPath);
- }
-
- }
-
- private static class FileTldLocation implements TldLocation {
-
- private final File file;
-
- public FileTldLocation(File file) {
- this.file = file;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- return new FileInputStream(file);
- }
-
- @Override
- public String getXmlSystemId() throws IOException {
- return file.toURI().toURL().toExternalForm();
- }
-
- @Override
- public String toString() {
- return file.toString();
- }
-
- }
-
- private static final class Taglib implements TemplateHashModel {
- private final Map tagsAndFunctions;
-
- Taglib(ServletContext ctx, TldLocation tldPath, ObjectWrapper wrapper)
throws IOException, SAXException {
- tagsAndFunctions = parseToTagsAndFunctions(ctx, tldPath, wrapper);
- }
-
- @Override
- public TemplateModel get(String key) {
- return (TemplateModel) tagsAndFunctions.get(key);
- }
-
- @Override
- public boolean isEmpty() {
- return tagsAndFunctions.isEmpty();
- }
-
- private static final Map parseToTagsAndFunctions(
- ServletContext ctx, TldLocation tldLocation, ObjectWrapper
objectWrapper) throws IOException, SAXException {
- final TldParserForTaglibBuilding tldParser = new
TldParserForTaglibBuilding(objectWrapper);
-
- InputStream in = tldLocation.getInputStream();
- try {
- parseXml(in, tldLocation.getXmlSystemId(), tldParser);
- } finally {
- in.close();
- }
-
- EventForwarding eventForwarding = EventForwarding.getInstance(ctx);
- if (eventForwarding != null) {
- eventForwarding.addListeners(tldParser.getListeners());
- } else if (tldParser.getListeners().size() > 0) {
- throw new TldParsingSAXException(
- "Event listeners specified in the TLD could not be " +
- " registered since the web application doesn't
have a" +
- " listener of class " +
EventForwarding.class.getName() +
- ". To remedy this, add this element to
web.xml:\n" +
- "| <listener>\n" +
- "| <listener-class>" +
EventForwarding.class.getName() + "</listener-class>\n" +
- "| </listener>", null);
- }
- return tldParser.getTagsAndFunctions();
- }
- }
-
- private class WebXmlParser extends DefaultHandler {
- private static final String E_TAGLIB = "taglib";
- private static final String E_TAGLIB_LOCATION = "taglib-location";
- private static final String E_TAGLIB_URI = "taglib-uri";
-
- private StringBuilder cDataCollector;
- private String taglibUriCData;
- private String taglibLocationCData;
- private Locator locator;
-
- @Override
- public void setDocumentLocator(Locator locator) {
- this.locator = locator;
- }
-
- @Override
- public void startElement(
- String nsuri,
- String localName,
- String qName,
- Attributes atts) {
- if (E_TAGLIB_URI.equals(qName) || E_TAGLIB_LOCATION.equals(qName))
{
- cDataCollector = new StringBuilder();
- }
- }
-
- @Override
- public void characters(char[] chars, int off, int len) {
- if (cDataCollector != null) {
- cDataCollector.append(chars, off, len);
- }
- }
-
- @Override
- public void endElement(String nsUri, String localName, String qName)
throws TldParsingSAXException {
- if (E_TAGLIB_URI.equals(qName)) {
- taglibUriCData = cDataCollector.toString().trim();
- cDataCollector = null;
- } else if (E_TAGLIB_LOCATION.equals(qName)) {
- taglibLocationCData = cDataCollector.toString().trim();
- if (taglibLocationCData.length() == 0) {
- throw new TldParsingSAXException("Required \"" +
E_TAGLIB_URI + "\" element was missing or empty",
- locator);
- }
- try {
- if (getUriType(taglibLocationCData) == URL_TYPE_RELATIVE) {
- taglibLocationCData = "/WEB-INF/" +
taglibLocationCData;
- }
- } catch (MalformedURLException e) {
- throw new TldParsingSAXException("Failed to detect URI
type for: " + taglibLocationCData, locator, e);
- }
- cDataCollector = null;
- } else if (E_TAGLIB.equals(qName)) {
- addTldLocation(
- isJarPath(taglibLocationCData)
- ? (TldLocation) new
ServletContextJarEntryTldLocation(
- taglibLocationCData,
DEFAULT_TLD_RESOURCE_PATH)
- : (TldLocation) new
ServletContextTldLocation(taglibLocationCData),
- taglibUriCData);
- }
- }
- }
-
- private static class TldParserForTaglibUriExtraction extends
DefaultHandler {
- private static final String E_URI = "uri";
-
- private StringBuilder cDataCollector;
- private String uri;
-
- TldParserForTaglibUriExtraction() {
- }
-
- String getTaglibUri() {
- return uri;
- }
-
- @Override
- public void startElement(
- String nsuri,
- String localName,
- String qName,
- Attributes atts) {
- if (E_URI.equals(qName)) {
- cDataCollector = new StringBuilder();
- }
- }
-
- @Override
- public void characters(char[] chars, int off, int len) {
- if (cDataCollector != null) {
- cDataCollector.append(chars, off, len);
- }
- }
-
- @Override
- public void endElement(String nsuri, String localName, String qName) {
- if (E_URI.equals(qName)) {
- uri = cDataCollector.toString().trim();
- cDataCollector = null;
- }
- }
- }
-
- static final class TldParserForTaglibBuilding extends DefaultHandler {
- private static final String E_TAG = "tag";
- private static final String E_NAME = "name";
- private static final String E_TAG_CLASS = "tag-class";
- private static final String E_TAG_CLASS_LEGACY = "tagclass";
-
- private static final String E_FUNCTION = "function";
- private static final String E_FUNCTION_CLASS = "function-class";
- private static final String E_FUNCTION_SIGNATURE =
"function-signature";
-
- private static final String E_LISTENER = "listener";
- private static final String E_LISTENER_CLASS = "listener-class";
-
- private final BeansWrapper beansWrapper;
-
- private final Map<String, TemplateModel> tagsAndFunctions = new
HashMap<String, TemplateModel>();
- private final List listeners = new ArrayList();
-
- private Locator locator;
- private StringBuilder cDataCollector;
-
- private Stack stack = new Stack();
-
- private String tagNameCData;
- private String tagClassCData;
- private String functionNameCData;
- private String functionClassCData;
- private String functionSignatureCData;
- private String listenerClassCData;
-
- TldParserForTaglibBuilding(ObjectWrapper wrapper) {
- if (wrapper instanceof BeansWrapper) {
- beansWrapper = (BeansWrapper) wrapper;
- } else {
- beansWrapper = null;
- if (LOG.isWarnEnabled()) {
- LOG.warn("Custom EL functions won't be loaded because "
- + (wrapper == null
- ? "no ObjectWrapper was specified for the
TaglibFactory "
- + "(via
TaglibFactory.setObjectWrapper(...), exists since 2.3.22)"
- : "the ObjectWrapper wasn't instance of "
+ BeansWrapper.class.getName())
- + ".");
- }
- }
- }
-
- Map<String, TemplateModel> getTagsAndFunctions() {
- return tagsAndFunctions;
- }
-
- List getListeners() {
- return listeners;
- }
-
- @Override
- public void setDocumentLocator(Locator locator) {
- this.locator = locator;
- }
-
- @Override
- public void startElement(String nsUri, String localName, String qName,
Attributes atts) {
- stack.push(qName);
- if (stack.size() == 3) {
- if (E_NAME.equals(qName) || E_TAG_CLASS_LEGACY.equals(qName)
|| E_TAG_CLASS.equals(qName)
- || E_LISTENER_CLASS.equals(qName) ||
E_FUNCTION_CLASS.equals(qName)
- || E_FUNCTION_SIGNATURE.equals(qName)) {
- cDataCollector = new StringBuilder();
- }
- }
- }
-
- @Override
- public void characters(char[] chars, int off, int len) {
- if (cDataCollector != null) {
- cDataCollector.append(chars, off, len);
- }
- }
-
- @Override
- public void endElement(String nsuri, String localName, String qName)
throws TldParsingSAXException {
- if (!stack.peek().equals(qName)) {
- throw new TldParsingSAXException("Unbalanced tag nesting at
\"" + qName + "\" end-tag.", locator);
- }
-
- if (stack.size() == 3) {
- if (E_NAME.equals(qName)) {
- if (E_TAG.equals(stack.get(1))) {
- tagNameCData = pullCData();
- } else if (E_FUNCTION.equals(stack.get(1))) {
- functionNameCData = pullCData();
- }
- } else if (E_TAG_CLASS_LEGACY.equals(qName) ||
E_TAG_CLASS.equals(qName)) {
- tagClassCData = pullCData();
- } else if (E_LISTENER_CLASS.equals(qName)) {
- listenerClassCData = pullCData();
- } else if (E_FUNCTION_CLASS.equals(qName)) {
- functionClassCData = pullCData();
- } else if (E_FUNCTION_SIGNATURE.equals(qName)) {
- functionSignatureCData = pullCData();
- }
- } else if (stack.size() == 2) {
- if (E_TAG.equals(qName)) {
- checkChildElementNotNull(qName, E_NAME, tagNameCData);
- checkChildElementNotNull(qName, E_TAG_CLASS,
tagClassCData);
-
- final Class tagClass = resoveClassFromTLD(tagClassCData,
"custom tag", tagNameCData);
-
- final TemplateModel customTagModel;
- try {
- if (Tag.class.isAssignableFrom(tagClass)) {
- customTagModel = new
TagTransformModel(tagNameCData, tagClass);
- } else {
- customTagModel = new
SimpleTagDirectiveModel(tagNameCData, tagClass);
- }
- } catch (IntrospectionException e) {
- throw new TldParsingSAXException(
- "JavaBean introspection failed on custom tag
class " + tagClassCData,
- locator,
- e);
- }
-
- TemplateModel replacedTagOrFunction =
tagsAndFunctions.put(tagNameCData, customTagModel);
- if (replacedTagOrFunction != null) {
- if
(CustomTagAndELFunctionCombiner.canBeCombinedAsELFunction(replacedTagOrFunction))
{
- tagsAndFunctions.put(tagNameCData,
CustomTagAndELFunctionCombiner.combine(
- customTagModel, (TemplateMethodModelEx)
replacedTagOrFunction));
- } else {
- if (LOG.isWarnEnabled()) {
- LOG.warn("TLD contains multiple tags with name
" + StringUtil.jQuote(tagNameCData)
- + "; keeping only the last one.");
- }
- }
- }
-
- tagNameCData = null;
- tagClassCData = null;
- } else if (E_FUNCTION.equals(qName) && beansWrapper != null) {
- checkChildElementNotNull(qName, E_FUNCTION_CLASS,
functionClassCData);
- checkChildElementNotNull(qName, E_FUNCTION_SIGNATURE,
functionSignatureCData);
- checkChildElementNotNull(qName, E_NAME, functionNameCData);
-
- final Class functionClass = resoveClassFromTLD(
- functionClassCData, "custom EL function",
functionNameCData);
-
- final Method functionMethod;
- try {
- functionMethod =
TaglibMethodUtil.getMethodByFunctionSignature(
- functionClass, functionSignatureCData);
- } catch (Exception e) {
- throw new TldParsingSAXException(
- "Error while trying to resolve signature " +
StringUtil.jQuote(functionSignatureCData)
- + " on class " +
StringUtil.jQuote(functionClass.getName())
- + " for custom EL function " +
StringUtil.jQuote(functionNameCData) + ".",
- locator,
- e);
- }
-
- final int modifiers = functionMethod.getModifiers();
- if (!Modifier.isPublic(modifiers) ||
!Modifier.isStatic(modifiers)) {
- throw new TldParsingSAXException(
- "The custom EL function method must be public
and static: " + functionMethod,
- locator);
- }
-
- final TemplateMethodModelEx elFunctionModel;
- try {
- elFunctionModel = beansWrapper.wrap(null,
functionMethod);
- } catch (Exception e) {
- throw new TldParsingSAXException(
- "FreeMarker object wrapping failed on method :
" + functionMethod,
- locator);
- }
-
- TemplateModel replacedTagOrFunction =
tagsAndFunctions.put(functionNameCData, elFunctionModel);
- if (replacedTagOrFunction != null) {
- if
(CustomTagAndELFunctionCombiner.canBeCombinedAsCustomTag(replacedTagOrFunction))
{
- tagsAndFunctions.put(functionNameCData,
CustomTagAndELFunctionCombiner.combine(
- replacedTagOrFunction, elFunctionModel));
- } else {
- if (LOG.isWarnEnabled()) {
- LOG.warn("TLD contains multiple functions with
name "
- + StringUtil.jQuote(functionNameCData)
+ "; keeping only the last one.");
- }
- }
- }
-
- functionNameCData = null;
- functionClassCData = null;
- functionSignatureCData = null;
- } else if (E_LISTENER.equals(qName)) {
- checkChildElementNotNull(qName, E_LISTENER_CLASS,
listenerClassCData);
-
- final Class listenerClass =
resoveClassFromTLD(listenerClassCData, E_LISTENER, null);
-
- final Object listener;
- try {
- listener = listenerClass.newInstance();
- } catch (Exception e) {
- throw new TldParsingSAXException(
- "Failed to create new instantiate from
listener class " + listenerClassCData,
- locator,
- e);
- }
-
- listeners.add(listener);
-
- listenerClassCData = null;
- }
- }
-
- stack.pop();
- }
-
- private String pullCData() {
- String r = cDataCollector.toString().trim();
- cDataCollector = null;
- return r;
- }
-
- private void checkChildElementNotNull(String parentElementName, String
childElementName, String value)
- throws TldParsingSAXException {
- if (value == null) {
- throw new TldParsingSAXException(
- "Missing required \"" + childElementName + "\" element
inside the \""
- + parentElementName + "\" element.", locator);
- }
- }
-
- private Class resoveClassFromTLD(String className, String entryType,
String entryName)
- throws TldParsingSAXException {
- try {
- return ClassUtil.forName(className);
- } catch (LinkageError e) {
- throw newTLDEntryClassLoadingException(e, className,
entryType, entryName);
- } catch (ClassNotFoundException e) {
- throw newTLDEntryClassLoadingException(e, className,
entryType, entryName);
- }
- }
-
- private TldParsingSAXException
newTLDEntryClassLoadingException(Throwable e, String className,
- String entryType, String entryName)
- throws TldParsingSAXException {
- int dotIdx = className.lastIndexOf('.');
- if (dotIdx != -1) {
- dotIdx = className.lastIndexOf('.', dotIdx - 1);
- }
- boolean looksLikeNestedClass =
- dotIdx != -1 && className.length() > dotIdx + 1
- && Character.isUpperCase(className.charAt(dotIdx +
1));
- return new TldParsingSAXException(
- (e instanceof ClassNotFoundException ? "Not found class "
: "Can't load class ")
- + StringUtil.jQuote(className) + " for " +
entryType
- + (entryName != null ? " " +
StringUtil.jQuote(entryName) : "") + "."
- + (looksLikeNestedClass
- ? " Hint: Before nested classes, use
\"$\", not \".\"."
- : ""),
- locator,
- e);
- }
-
- }
-
- /**
- * Dummy resolver that returns 0 length content for all requests.
- */
- private static final class EmptyContentEntityResolver implements
EntityResolver {
-
- @Override
- public InputSource resolveEntity(String publicId, String systemId) {
- InputSource is = new InputSource(new ByteArrayInputStream(new
byte[0]));
- is.setPublicId(publicId);
- is.setSystemId(systemId);
- return is;
- }
- }
-
- /**
- * Redefines {@code SAXParseException#toString()} and {@code
SAXParseException#getCause()} because it's broken on
- * Java 1.6 and earlier.
- */
- private static class TldParsingSAXException extends SAXParseException {
-
- private final Throwable cause;
-
- TldParsingSAXException(String message, Locator locator) {
- this(message, locator, null);
- }
-
- TldParsingSAXException(String message, Locator locator, Throwable e) {
- super(message, locator, e instanceof Exception ? (Exception) e :
new Exception(
- "Unchecked exception; see cause", e));
- cause = e;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(getClass().getName());
- sb.append(": ");
- int startLn = sb.length();
-
- String systemId = getSystemId();
- String publicId = getPublicId();
- if (systemId != null || publicId != null) {
- sb.append("In ");
- if (systemId != null) {
- sb.append(systemId);
- }
- if (publicId != null) {
- if (systemId != null) {
- sb.append(" (public ID: ");
- }
- sb.append(publicId);
- if (systemId != null) {
- sb.append(')');
- }
- }
- }
-
- int line = getLineNumber();
- if (line != -1) {
- sb.append(sb.length() != startLn ? ", at " : "At ");
- sb.append("line ");
- sb.append(line);
- int col = getColumnNumber();
- if (col != -1) {
- sb.append(", column ");
- sb.append(col);
- }
- }
-
- String message = getLocalizedMessage();
- if (message != null) {
- if (sb.length() != startLn) {
- sb.append(":\n");
- }
- sb.append(message);
- }
-
- return sb.toString();
- }
-
- @Override
- public Throwable getCause() {
- Throwable superCause = super.getCause();
- return superCause == null ? this.cause : superCause;
- }
-
- }
-
- private static class URLWithExternalForm implements Comparable {
-
- private final URL url;
- private final String externalForm;
-
- public URLWithExternalForm(URL url) {
- this.url = url;
- this.externalForm = url.toExternalForm();
- }
-
- public URL getUrl() {
- return url;
- }
-
- public String getExternalForm() {
- return externalForm;
- }
-
- @Override
- public int hashCode() {
- return externalForm.hashCode();
- }
-
- @Override
- public boolean equals(Object that) {
- if (this == that) return true;
- if (that == null) return false;
- if (getClass() != that.getClass()) return false;
- return !externalForm.equals(((URLWithExternalForm)
that).externalForm);
- }
-
- @Override
- public String toString() {
- return "URLWithExternalForm(" + externalForm + ")";
- }
-
- @Override
- public int compareTo(Object that) {
- return this.getExternalForm().compareTo(((URLWithExternalForm)
that).getExternalForm());
- }
-
- }
-
- private static class TaglibGettingException extends Exception {
-
- public TaglibGettingException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public TaglibGettingException(String message) {
- super(message);
- }
-
- }
-
-}