Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
    * 
    * 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 freemarker.ext.jsp;
  
  import java.io.File;
  import java.net.URI;
  import java.net.URL;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Stack;
  import java.util.TreeSet;
  
  import  javax.servlet.ServletContext;
  import  javax.servlet.http.HttpServletRequest;
  import  javax.servlet.jsp.tagext.Tag;
  
  
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 freemarker.ext.servlet.FreemarkerServlet under key 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 getClasspathTlds(); an empty list.

Since:
2.3.22
 
     public static final List DEFAULT_CLASSPATH_TLDS = .;
    
    
The default of getMetaInfTldSources(); a list that contains 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(.);
 
     private static final Logger LOG = Logger.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 =  + "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 = ;
     private List/*<String>*/ classpathTlds = ;
     
     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 ServletContext. You should at least call setObjectWrapper(ObjectWrapper) before start using this object.

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)).

Parameters:
ctx The servlet context whose JSP tag libraries this factory will load.
 
     public TaglibFactory(ServletContext ctx) {
         this. = 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.

Parameters:
taglibUri The URI used in templates to refer to the taglib (like <%@ taglib uri="..." ... %> in JSP). It can be any of the three forms allowed by the JSP specification: absolute URI (like http://example.com/foo), root relative URI (like /bar/foo.tld) and non-root relative URI (like 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 HttpRequestHashModel object named Request in the root data model. FreemarkerServlet provides this object under the expected name, and custom servlets that want to integrate JSP taglib support should do the same.
Returns:
a TemplateHashModel representing the JSP taglib. Each element of this hash represents a single custom tag or EL function from the library, implemented as a TemplateTransformModel or TemplateMethodModelEx, respectively.
 
     public TemplateModel get(final String taglibUrithrows TemplateModelException {
         synchronized () {
             {
                 final Taglib taglib = (Taglib.get(taglibUri);
                 if (taglib != null) {
                     return taglib;
                 }
             }
 
             boolean failedTldListAlreadyIncluded = false;
             final TldLocation tldLocation;
             final String normalizedTaglibUri;
             try {
                 if (.isDebugEnabled()) {
                     .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 == ) {
                         normalizedTaglibUri = resolveRelativeUri(taglibUri);
                     } else if (urlType == ) {
                         normalizedTaglibUri = taglibUri;
                     } else if (urlType == ) {
                         // 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 "
                                 + "\"" + . + "\" and "
                                 + "\"" + . + "\" "
                                 + 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.get(normalizedTaglibUri);
                         if (taglib != null) {
                             return taglib;
                         }
                     }
 
                     tldLocation = isJarPath(normalizedTaglibUri)
                                 ? (TldLocationnew ServletContextJarEntryTldLocation(
                                         normalizedTaglibUri)
                                 : (TldLocationnew 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(tldLocationnormalizedTaglibUri);
             } 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 null if there was none.
 
     private String getFailedTLDsList() {
         synchronized () {
             if (.isEmpty()) {
                 return null;
             }
             StringBuffer sb = new StringBuffer();
             for (int i = 0; i < .size(); i++) {
                 if (i != 0) {
                     sb.append(", ");
                 }
                 sb.append(StringUtil.jQuote(.get(i)));
             }
             return sb.toString();
         }
     }

    
Returns false.
 
     public boolean isEmpty() {
         return false;
     }
    
    
See setObjectWrapper(ObjectWrapper).

Since:
2.3.22
 
     public ObjectWrapper getObjectWrapper() {
         return ;
     }

    
Sets the ObjectWrapper used when building the JSP tag library TemplateHashModel-s from the TLD-s. Usually, it should be the same ObjectWrapper that will be used inside the templates. null value is only supported for backward compatibility. For custom EL functions to be exposed, it must be non-null and an intanceof BeansWrapper (like typically, a DefaultObjectWrapper).

Since:
2.3.22
 
     public void setObjectWrapper(ObjectWrapper objectWrapper) {
         checkNotStarted();
         this. = objectWrapper;
     }

    
See setMetaInfTldSources(List).

Since:
2.3.22
 
     public List/*<Pattern>*/ getMetaInfTldSources() {
         return ;
     }

    
Sets the list of places where we will look for META-INF/**/*.tld files. By default this is a list that only contains WebInfPerLibJarMetaInfTldSource.INSTANCE. This corresponds to the behavior that the JSP specification describes. See the MetaInfTldSource subclasses for the possible values and their meanings.

This is usually set via the init-params of FreemarkerServlet.

Parameters:
metaInfTldSources The list of MetaInfTldSource subclass instances. Their order matters if multiple TLD-s define a taglib with the same taglib-uri. In that case, the one found by the earlier MetaInfTldSource wins.
Since:
2.3.22
See also:
setClasspathTlds(List)
 
     public void setMetaInfTldSources(List/*<? extends MetaInfTldSource>*/ metaInfTldSources) {
         checkNotStarted();
         NullArgumentException.check("metaInfTldSources"metaInfTldSources);
         this. = metaInfTldSources;
     }

    
See setClasspathTlds(List).

Since:
2.3.22
 
     public List/*<String>*/ getClasspathTlds() {
         return ;
     }

    
Sets the class-loader resource paths of the TLD-s that aren't inside the locations covered by setMetaInfTldSources(List), yet you want them to be discovered. They will be loaded with the class loader provided by the servlet container.

This is usually set via the init-params of FreemarkerServlet.

Parameters:
classpathTlds List of String-s, maybe null. Each item is a resource path, like "/META-INF/my.tld". (Relative resource paths will be interpreted as root-relative.)
Since:
2.3.22
See also:
setMetaInfTldSources(List)
 
     public void setClasspathTlds(List/*<String>*/ classpathTlds) {
         checkNotStarted();
         NullArgumentException.check("classpathTlds"classpathTlds);
         this. = classpathTlds;
     }
 
     private void checkNotStarted() {
         synchronized () {
             if ( != 0) {
                 throw new IllegalStateException(TaglibFactory.class.getName() + " object was already in use.");
             }
         }
     }
 
     private TldLocation getExplicitlyMappedTldLocation(final String urithrows SAXExceptionIOException,
             TaglibGettingException {
         while (true) {
             final TldLocation tldLocation = (TldLocation.get(uri);
             if (tldLocation != null) {
                 return tldLocation;
             }
 
             switch () {
             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();
             }
             ++;
         }
     }
 
     private void addTldLocationsFromWebXml() throws SAXExceptionIOException {
         .debug("Looking for TLD locations in servletContext:/WEB-INF/web.xml");
 
         WebXmlParser webXmlParser = new WebXmlParser();
         InputStream in = .getResourceAsStream("/WEB-INF/web.xml");
         if (in == null) {
             .debug("No web.xml was found in servlet context");
             return;
         }
         try {
             parseXml(in.getResource("/WEB-INF/web.xml").toExternalForm(), webXmlParser);
         } finally {
             in.close();
         }
     }
 
     private void addTldLocationsFromWebInfTlds()
             throws IOExceptionSAXException {
         .debug("Looking for TLD locations in servletContext:/WEB-INF/**/*.tld");
     }
 
     private void addTldLocationsFromServletContextResourceTlds(String basePath)
             throws IOExceptionSAXException {
         Set unsortedResourcePaths = .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 = (Stringit.next();
                 if (resourcePath.endsWith(".tld")) {
                     addTldLocationFromTld(new ServletContextTldLocation(resourcePath));
                 }
             }
             // ... only later the directories
             for (Iterator it = resourcePaths.iterator(); it.hasNext();) {
                 String resourcePath = (Stringit.next();
                 if (resourcePath.endsWith("/")) {
                     addTldLocationsFromServletContextResourceTlds(resourcePath);
                 }
             }
         }
     }
     
     private void addTldLocationsFromMetaInfTlds() throws IOExceptionSAXException {
         if ( == null || .isEmpty()) {
             return;
         }
 
         Set/*<URLWithExternalForm>*/ cpMetaInfDirUrlsWithEF = null;
         
         // Skip past the last "clear":
         int srcIdxStart = 0;
         for (int i = .size() - 1; i >= 0; i--) {
             if (.get(iinstanceof ClearMetaInfTldSource) {
                 srcIdxStart = i + 1;
                 break;
             }
         }
         
         for (int srcIdx = srcIdxStartsrcIdx < .size(); srcIdx++) {
             MetaInfTldSource miTldSource = (MetaInfTldSource.get(srcIdx);
             
             if (miTldSource == .) {
                 addTldLocationsFromWebInfPerLibJarMetaInfTlds();
             } else if (miTldSource instanceof ClasspathMetaInfTldSource) {
                 ClasspathMetaInfTldSource cpMiTldLocation = (ClasspathMetaInfTldSourcemiTldSource;
                 if (.isDebugEnabled()) {
                     .debug("Looking for TLD-s in "
                             + "classpathRoots[" + cpMiTldLocation.getRootContainerPattern() + "]"
                             +  + "**/*.tld");
                 }
                 
                 if (cpMetaInfDirUrlsWithEF == null) {
                     cpMetaInfDirUrlsWithEF = collectMetaInfUrlsFromClassLoaders();
                 }
 
                 for (Iterator iterator = cpMetaInfDirUrlsWithEF.iterator(); iterator.hasNext();) {
                     URLWithExternalForm urlWithEF = (URLWithExternalFormiterator.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();
                         rootContainerUrl = sep != -1 ? urlEF.substring(0, sep) : urlEF;
                     } else {
                         rootContainerUrl = urlEF.endsWith()
                                 ? urlEF.substring(0, urlEF.length() - .length())
                                 : urlEF;
                     }
                     
                     if (cpMiTldLocation.getRootContainerPattern().matcher(rootContainerUrl).matches()) {
                         final File urlAsFile = urlToFileOrNull(url);
                         if (urlAsFile != null) {
                             addTldLocationsFromFileDirectory(urlAsFile);
                         } else if (isJarUrl) {
                             addTldLocationsFromJarDirectoryEntryURL(url);
                         } else {
                             if (.isDebugEnabled()) {
                                 .warn("Can't list entries under this URL; TLD-s won't be discovered here: "
                                         + urlWithEF.getExternalForm());
                             }
                         }
                     }
                 }
             } else {
                 throw new BugException();
             }
         }
     }
     
         if (.isDebugEnabled()) {
             .debug("Looking for TLD locations in servletContext:/WEB-INF/lib/*.{jar,zip}" + 
                     + "*.tld");
         }
 
         Set libEntPaths = .getResourcePaths("/WEB-INF/lib");
         if (libEntPaths != null) {
             for (Iterator iter = libEntPaths.iterator(); iter.hasNext();) {
                 final String libEntryPath = (Stringiter.next();
                 if (isJarPath(libEntryPath)) {
                     addTldLocationsFromServletContextJar(libEntryPath);
                 }
             }
         }
     }
 
         if ( == null || .size() == 0) {
             return;
         }
         
         .debug("Looking for TLD locations in TLD-s specified in cfg.classpathTlds");
         
         for (Iterator it = .iterator(); it.hasNext();) {
             String tldResourcePath = (Stringit.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 (.isWarnEnabled()) {
                     .warn("Ignored classpath TLD location " + StringUtil.jQuoteNoXSS(tldResourcePath)
                             + " because of error"e);
                 }
                 in = null;
             }
             if (in != null) {
                 try {
                     addTldLocationFromTld(intldLocation);
                 } finally {
                     in.close();
                 }
             }
         }
     }

    
Finds and processes *.tld inside a jar in the servet context.
 
     private void addTldLocationsFromServletContextJar(
             final String jarResourcePath)
             throws IOExceptionMalformedURLExceptionSAXException {
         final String metaInfEntryPath = normalizeJarEntryPath(true);
         
         // Null for non-random-access backing resource:
         final JarFile jarFile = servletContextResourceToFileOrNull(jarResourcePath);
         if (jarFile != null) {
             if (.isDebugEnabled()) {
                 .debug("Scanning for " +  + "*.tld-s in JarFile: servletContext:"
                         + jarResourcePath);
             }
             for (Enumeration/*<JarEntry>*/ entries = jarFile.entries(); entries.hasMoreElements();) {
                 final JarEntry curEntry = (JarEntryentries.nextElement();
                 final String curEntryPath = normalizeJarEntryPath(curEntry.getName(), false);
                 if (curEntryPath.startsWith(metaInfEntryPath) && curEntryPath.endsWith(".tld")) {
                     addTldLocationFromTld(new ServletContextJarEntryTldLocation(jarResourcePathcurEntryPath));
                 }
             }
         } else {  // jarFile == null => fall back to streamed access
             if (.isDebugEnabled()) {
                 .debug("Scanning for " + 
                         + "*.tld-s in ZipInputStream (slow): servletContext:" + jarResourcePath);
             }
     
             final InputStream in = .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 == nullbreak;
         
                         String curEntryPath = normalizeJarEntryPath(curEntry.getName(), false);
                         if (curEntryPath.startsWith(metaInfEntryPath) && curEntryPath.endsWith(".tld")) {
                             addTldLocationFromTld(zipIn,
                                     new ServletContextJarEntryTldLocation(jarResourcePathcurEntryPath)); 
                         }
                     }
                 } finally {
                     zipIn.close();
                 }
             } finally {
                 in.close();
             }
         }
     }

    
Finds and processes *.tld inside a directory in a jar.

Parameters:
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 IOExceptionMalformedURLExceptionSAXException {
         // 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 (! && urlCon instanceof JarURLConnection) {
                 final JarURLConnection jarCon = (JarURLConnectionurlCon;
                 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();
                 if (jarEntrySepIdx == -1) {
                     throw newFailedToExtractEntryPathException(jarBaseEntryUrl);
                 }
                 rawJarContentUrlEF = jarBaseEntryUrlEF.substring(jarBaseEntryUrlEF.indexOf(':') + 1, jarEntrySepIdx);
                 baseEntryPath = normalizeJarEntryPath(
                         jarBaseEntryUrlEF.substring(jarEntrySepIdx + .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 (.isDebugEnabled()) {
                 .debug("Scanning for " +  + "**/*.tld-s in random access mode: "
                         + jarBaseEntryUrl);
             }
             for (Enumeration/*<JarEntry>*/ entries = jarFile.entries(); entries.hasMoreElements();) {
                 final JarEntry curEntry = (JarEntryentries.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(jarBaseEntryUrlcurEntryBaseRelativePath);
                     addTldLocationFromTld(new JarEntryUrlTldLocation(tldUrlnull));
                 }
             }
         } else {
             // Not a random-access file, so we fall back to the slower ZipInputStream approach.
             if (.isDebugEnabled()) {
                 .debug("Scanning for " +  + "**/*.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 == nullbreak;
         
                         String curEntryPath = normalizeJarEntryPath(curEntry.getName(), false);
                         if (curEntryPath.startsWith(baseEntryPath) && curEntryPath.endsWith(".tld")) {
                             final String curEntryBaseRelativePath = curEntryPath.substring(baseEntryPath.length());
                             final URL tldUrl = createJarEntryUrl(jarBaseEntryUrlcurEntryBaseRelativePath);
                             addTldLocationFromTld(zipInnew JarEntryUrlTldLocation(tldUrlnull));
                         }
                     }
                 } finally {
                     zipIn.close();
                 }
             } finally {
                 in.close();
             }
         }
     }
 
     private void addTldLocationsFromFileDirectory(final File dirthrows IOExceptionSAXException {
         if (dir.isDirectory()) {
             if (.isDebugEnabled()) {
                 .debug("Scanning for *.tld-s in File directory: " + StringUtil.jQuoteNoXSS(dir));
             }
             File[] tldFiles = dir.listFiles(new FilenameFilter() {
     
                 public boolean accept(File urlAsFileString name) {
                     return isTldFileNameIgnoreCase(name);
                 }
     
             });
             for (int i = 0; i < tldFiles.lengthi++) {
                 final File file = tldFiles[i];
                 addTldLocationFromTld(new FileTldLocation(file));
             }
         } else {
             .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 tldLocationthrows IOExceptionSAXException {
         InputStream in = tldLocation.getInputStream();
         try {
             addTldLocationFromTld(intldLocation);
         } finally {
             in.close();
         }
     }

    
Use this overload only if you already have the InputStream for some reason, otherwise use addTldLocationFromTld(TldLocation).

Parameters:
reusedIn The stream that we already had (so we don't have to open a new one from the tldLocation).
 
     private void addTldLocationFromTld(InputStream reusedInTldLocation tldLocationthrows SAXException,
             IOException {
         String taglibUri;
         try {
             taglibUri = getTaglibUriFromTld(reusedIntldLocation.getXmlSystemId());
         } catch (SAXException e) {
             .error("Error while parsing TLD; skipping: " + tldLocatione);
             synchronized () {
                 .add(tldLocation.toString());
             }
             taglibUri = null;
         }
         if (taglibUri != null) {
                 addTldLocation(tldLocationtaglibUri);
         }
     }
 
     private void addTldLocation(TldLocation tldLocationString taglibUri) {
         if (.containsKey(taglibUri)) {
             if (.isDebugEnabled()) {
                 .debug("Ignored duplicate mapping of taglib URI " + StringUtil.jQuoteNoXSS(taglibUri)
                         + " to TLD location " + StringUtil.jQuoteNoXSS(tldLocation));
             }
         } else {
             .put(taglibUritldLocation);
             if (.isDebugEnabled()) {
                 .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(tcclmetainfDirUrls);
         }
     
         final ClassLoader cccl = TaglibFactory.class.getClassLoader();
         if (!isDescendantOfOrSameAs(tcclcccl)) {
             collectMetaInfUrlsFromClassLoader(ccclmetainfDirUrls);
         }
         return metainfDirUrls;
     }
 
     private static void collectMetaInfUrlsFromClassLoader(ClassLoader clSet/* <URLWithExternalForm> */metainfDirUrls)
             throws IOException {
         Enumeration/*<URL>*/ urls = cl.getResources();
         if (urls != null) {
             while (urls.hasMoreElements()) {
                 metainfDirUrls.add(new URLWithExternalForm((URLurls.nextElement()));
             }
         }
     }
 
     private String getTaglibUriFromTld(InputStream tldFileInString tldFileXmlSystemIdthrows SAXExceptionIOException {
         parseXml(tldFileIntldFileXmlSystemIdtldParser);
         return tldParser.getTaglibUri();
     }

    

Parameters:
tldLocation The physical location of the TLD file
taglibUri The URI used in templates to refer to the taglib (like <%@ taglib uri="..." ... %> in JSP).
 
     private TemplateHashModel loadTaglib(TldLocation tldLocationString taglibUrithrows IOExceptionSAXException {
         if (.isDebugEnabled()) {
             .debug("Loading taglib for URI " + StringUtil.jQuoteNoXSS(taglibUri)
                     + " from TLD location " + StringUtil.jQuoteNoXSS(tldLocation));
         }
         final Taglib taglib = new Taglib(tldLocation);
         .put(taglibUritaglib);
         .remove(taglibUri);
         return taglib;
     }
 
     private static void parseXml(InputStream inString systemIdDefaultHandler handler)
             throws SAXExceptionIOException {
         InputSource inSrc = new InputSource();
         inSrc.setSystemId(systemId);
         inSrc.setByteStream(toCloseIgnoring(in));
         
         SAXParserFactory factory = SAXParserFactory.newInstance();
         factory.setNamespaceAware(false);
         factory.setValidating(false);
         XMLReader reader;
         try {
             reader = factory.newSAXParser().getXMLReader();
         } catch (ParserConfigurationException e) {
             // Not expected
             throw new RuntimeException("XML parser setup failed"e);
         }
         reader.setEntityResolver(new LocalDtdEntityResolver());
         reader.setContentHandler(handler);
         reader.setErrorHandler(handler);
         
         reader.parse(inSrc);
     }
 
     private static String resolveRelativeUri(String urithrows TaglibGettingException
     {
         TemplateModel reqHash;
         try {
             reqHash = Environment.getCurrentEnvironment().getVariable(
                     .);
         } catch (TemplateModelException e) {
             throw new TaglibGettingException("Failed to get FreemarkerServlet request information"e);
         }
         if (reqHash instanceof HttpRequestHashModel) {
             HttpServletRequest req =
                     ((HttpRequestHashModelreqHash).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) {
             public void close() {
                 // Do nothing 
             }
         };
     }
     
     private static int getUriType(String urithrows 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 ;
         }
         // 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 ;
         }
         final int colon = uri.indexOf(':');
         if (colon == -1) { // Must have a colon
             return ;
         }
         // 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 ;
             }
         }
         return ;
     }
 
     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 jarBaseEntryUrlString relativeEntryPath)
             throws MalformedURLException {
         if (relativeEntryPath.startsWith("/")) {
             relativeEntryPath = relativeEntryPath.substring(1);
         }
         try {
             return new URL(jarBaseEntryUrl, StringUtil.URLPathEnc(relativeEntryPath));
         } catch (UnsupportedEncodingException e) {
             throw new BugException();
         }
     }

    
Trying to hide any JarFile implementation inconsistencies.
 
     private static String normalizeJarEntryPath(String jarEntryDirPathboolean 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 File object, if the URL format (scheme) makes is possible.
 
     private File urlToFileOrNull(URL url) {
         if () {
             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 = toUri(url).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(), );
            } catch (UnsupportedEncodingException e2) {
                throw new BugException(e2);
            }
        }
        return new File(filePath);
    }
    private static final Method toURIMethod;
    static {
        Method m;
        try {
            m = URL.class.getMethod("toURI"new Class[] { });
        } catch (Exception e) {
            m = null;
        }
         = m;
    }
    
    // Java 5: remove
    
Calls Java 5 URL.toURI() on Java 1.4.
    private static URI toUri(URL urlthrows URISyntaxException {
        if ( != null) {
            try {
                return (URI.invoke(urlnew Object[] { });
            } catch (InvocationTargetException e) {
                final Throwable targetE = e.getTargetException();
                if (targetE instanceof URISyntaxException) {
                    throw (URISyntaxExceptiontargetE;
                }
                if (targetE instanceof RuntimeException) {
                    throw (RuntimeExceptiontargetE;
                }
                throw new RuntimeException("toURI() call failed"e);
            } catch (Exception e) {
                throw new RuntimeException("toURI() call failed"e);
            }
        } else {
            return new URI(url.toString());
        }
    }

    
Gets a servlet context resource as a JarFile if possible, return 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 jarResourcePaththrows MalformedURLException,
            IOException {
        URL jarResourceUrl = .getResource(jarResourcePath);
        if (jarResourceUrl == null) {
            .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()) {
            .error("Jar file doesn't exist - falling back to stream mode: " + jarResourceAsFile);
            return null;
        }
        return new JarFile(jarResourceAsFile);
    }
    private static URL tryCreateServletContextJarEntryUrl(
            ServletContext servletContextfinal String servletContextJarFilePathfinal 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:"
                    + toUri(jarFileUrl)
                    + 
                    + URLEncoder.encode(
                            entryPath.startsWith("/") ? entryPath.substring(1) : entryPath,
                            ));
        } catch (Exception e) {
            .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;
            .warn("Can't access Thread Context ClassLoader"e);
        }
        return tccl;
    }
    
    private static boolean isDescendantOfOrSameAs(ClassLoader descendantClassLoader parent) {
        while (true) {
            if (descendant == null) {
                return false;
            }
            if (descendant == parent) {
                return true;
            }
            descendant = descendant.getParent();
        }
    }
    
    
A location within which we will look for META-INF/**/*.tld-s. Used in the parameter to setMetaInfTldSources. See concrete subclasses for more.

Since:
2.3.22
    public static abstract class MetaInfTldSource {
        private MetaInfTldSource() { }
    }

    
To search TLD-s under sevletContext:/WEB-INF/lib/*.{jar,zip}/META-INF/**/*.tld, 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 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 META-INF/**/*.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, META-INF/taglib.tld), that is, those TLD-s won't shadow each other, all of them will be loaded independently.

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
        
        

Parameters:
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 ".*taglib\.jar$". To search everywhere, the pattern should be ".*". The pattern need to match the whole URL, not just part of it.
        public ClasspathMetaInfTldSource(Pattern rootContainerPattern) {
            this. = rootContainerPattern;
        }

        
See constructor argument: ClasspathMetaInfTldSource(Pattern).
        public Pattern getRootContainerPattern() {
            return ;
        };
        
    }

    
When it occurs in the MetaInfTldSource list, all 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 also:
FreemarkerServlet.SYSTEM_PROPERTY_META_INF_TLD_SOURCES
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.

Returns:
Not null
        public abstract InputStream getInputStream() throws IOException;
        
        
The absolute URL of the TLD file.

Returns:
Not null
        public abstract String getXmlSystemId() throws IOException;
    }
    private interface InputStreamFactory {
        InputStream getInputStream();
    
    }
    private class ServletContextTldLocation implements TldLocation {
        
        private final String fileR