/*
* 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;
}
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());
}
public long
getLastModified(
Object templateSource) {
if (
templateSource instanceof
File) {
return ((
File)
templateSource).
lastModified();
} else {
return ((
URLTemplateSource)
templateSource).
lastModified();
}
}
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);
}
}
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;
}
}