| /* |
| * 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.cache; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| |
| import javax.servlet.ServletContext; |
| |
| import freemarker.log.Logger; |
| import freemarker.template.Configuration; |
| import freemarker.template.utility.CollectionUtils; |
| import freemarker.template.utility.NullArgumentException; |
| import freemarker.template.utility.StringUtil; |
| |
| /** |
| * A {@link TemplateLoader} that uses streams reachable through {@link ServletContext#getResource(String)} as its source |
| * of templates. |
| */ |
| public class WebappTemplateLoader implements TemplateLoader { |
| |
| private static final Logger LOG = Logger.getLogger("freemarker.cache"); |
| |
| private final ServletContext servletContext; |
| private final String subdirPath; |
| |
| private Boolean urlConnectionUsesCaches; |
| |
| private boolean attemptFileAccess = true; |
| |
| /** |
| * Creates a template loader that will use the specified servlet context to load the resources. It will use |
| * the base path of <code>"/"</code> meaning templates will be resolved relative to the servlet context root |
| * location. |
| * |
| * @param servletContext |
| * the servlet context whose {@link ServletContext#getResource(String)} will be used to load the |
| * templates. |
| */ |
| public WebappTemplateLoader(ServletContext servletContext) { |
| this(servletContext, "/"); |
| } |
| |
| /** |
| * Creates a template loader that will use the specified servlet context to load the resources. It will use the |
| * specified base path, which is interpreted relatively to the context root (does not mater if you start it with "/" |
| * or not). Path components should be separated by forward slashes independently of the separator character used by |
| * the underlying operating system. |
| * |
| * @param servletContext |
| * the servlet context whose {@link ServletContext#getResource(String)} will be used to load the |
| * templates. |
| * @param subdirPath |
| * the base path to template resources. |
| */ |
| public WebappTemplateLoader(ServletContext servletContext, String subdirPath) { |
| NullArgumentException.check("servletContext", servletContext); |
| NullArgumentException.check("subdirPath", subdirPath); |
| |
| subdirPath = subdirPath.replace('\\', '/'); |
| if (!subdirPath.endsWith("/")) { |
| subdirPath += "/"; |
| } |
| if (!subdirPath.startsWith("/")) { |
| subdirPath = "/" + subdirPath; |
| } |
| this.subdirPath = subdirPath; |
| this.servletContext = servletContext; |
| } |
| |
| @Override |
| public Object findTemplateSource(String name) throws IOException { |
| String fullPath = subdirPath + name; |
| |
| if (attemptFileAccess) { |
| // First try to open as plain file (to bypass servlet container resource caches). |
| try { |
| String realPath = servletContext.getRealPath(fullPath); |
| if (realPath != null) { |
| File file = new File(realPath); |
| if (file.canRead() && file.isFile()) { |
| return file; |
| } |
| } |
| } catch (SecurityException e) { |
| ;// ignore |
| } |
| } |
| |
| // If it fails, try to open it with servletContext.getResource. |
| URL url = null; |
| try { |
| url = servletContext.getResource(fullPath); |
| } catch (MalformedURLException e) { |
| LOG.warn("Could not retrieve resource " + StringUtil.jQuoteNoXSS(fullPath), |
| e); |
| return null; |
| } |
| return url == null ? null : new URLTemplateSource(url, getURLConnectionUsesCaches()); |
| } |
| |
| @Override |
| public long getLastModified(Object templateSource) { |
| if (templateSource instanceof File) { |
| return ((File) templateSource).lastModified(); |
| } else { |
| return ((URLTemplateSource) templateSource).lastModified(); |
| } |
| } |
| |
| @Override |
| public Reader getReader(Object templateSource, String encoding) |
| throws IOException { |
| if (templateSource instanceof File) { |
| return new InputStreamReader( |
| new FileInputStream((File) templateSource), |
| encoding); |
| } else { |
| return new InputStreamReader( |
| ((URLTemplateSource) templateSource).getInputStream(), |
| encoding); |
| } |
| } |
| |
| @Override |
| public void closeTemplateSource(Object templateSource) throws IOException { |
| if (templateSource instanceof File) { |
| // Do nothing. |
| } else { |
| ((URLTemplateSource) templateSource).close(); |
| } |
| } |
| |
| /** |
| * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}. |
| * |
| * @since 2.3.21 |
| */ |
| public Boolean getURLConnectionUsesCaches() { |
| return urlConnectionUsesCaches; |
| } |
| |
| /** |
| * It does the same as {@link URLTemplateLoader#setURLConnectionUsesCaches(Boolean)}; see there. |
| * |
| * @since 2.3.21 |
| */ |
| public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) { |
| this.urlConnectionUsesCaches = urlConnectionUsesCaches; |
| } |
| |
| /** |
| * Show class name and some details that are useful in template-not-found errors. |
| * |
| * @since 2.3.21 |
| */ |
| @Override |
| public String toString() { |
| return TemplateLoaderUtils.getClassNameForToString(this) |
| + "(subdirPath=" + StringUtil.jQuote(subdirPath) |
| + ", servletContext={contextPath=" + StringUtil.jQuote(getContextPath()) |
| + ", displayName=" + StringUtil.jQuote(servletContext.getServletContextName()) + "})"; |
| } |
| |
| /** Gets the context path if we are on Servlet 2.5+, or else returns failure description string. */ |
| private String getContextPath() { |
| try { |
| Method m = servletContext.getClass().getMethod("getContextPath", CollectionUtils.EMPTY_CLASS_ARRAY); |
| return (String) m.invoke(servletContext, CollectionUtils.EMPTY_OBJECT_ARRAY); |
| } catch (Throwable e) { |
| return "[can't query before Serlvet 2.5]"; |
| } |
| } |
| |
| /** |
| * Getter pair of {@link #setAttemptFileAccess(boolean)}. |
| * |
| * @since 2.3.23 |
| */ |
| public boolean getAttemptFileAccess() { |
| return attemptFileAccess; |
| } |
| |
| /** |
| * Specifies that before loading templates with {@link ServletContext#getResource(String)}, it should try to load |
| * the template as {@link File}; default is {@code true}, though it's not always recommended anymore. This is a |
| * workaround for the case when the servlet container doesn't show template modifications after the template was |
| * already loaded earlier. But it's certainly better to counter this problem by disabling the URL connection cache |
| * with {@link #setURLConnectionUsesCaches(Boolean)}, which is also the default behavior with |
| * {@link Configuration#setIncompatibleImprovements(freemarker.template.Version) incompatible_improvements} 2.3.21 |
| * and later. |
| * |
| * @since 2.3.23 |
| */ |
| public void setAttemptFileAccess(boolean attemptLoadingFromFile) { |
| this.attemptFileAccess = attemptLoadingFromFile; |
| } |
| |
| } |