2021SC@SDUSC
cache.TemplateLoader及其子类
总览图
1.接口TemplateLoader
代码
public interface TemplateLoader {
public Object findTemplateSource(String name)
throws IOException;
public long getLastModified(Object templateSource);
public Reader getReader(Object templateSource, String encoding) throws IOException;
public void closeTemplateSource(Object templateSource) throws IOException;
}
作用:模板加载器的接口
2.URLTemplateloader
代码
public abstract class URLTemplateLoader implements TemplateLoader {
private Boolean urlConnectionUsesCaches;
@Override
public Object findTemplateSource(String name)
throws IOException {
URL url = getURL(name);
return url == null ? null : new URLTemplateSource(url, getURLConnectionUsesCaches());
}
@Override
public long getLastModified(Object templateSource) {
return ((URLTemplateSource) templateSource).lastModified();
}
@Override
public Reader getReader(Object templateSource, String encoding)
throws IOException {
return new InputStreamReader(
((URLTemplateSource) templateSource).getInputStream(),
encoding);
}
@Override
public void closeTemplateSource(Object templateSource)
throws IOException {
((URLTemplateSource) templateSource).close();
}
public Boolean getURLConnectionUsesCaches() {
return urlConnectionUsesCaches;
}
public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
this.urlConnectionUsesCaches = urlConnectionUsesCaches;
}
protected abstract URL getURL(String name);
protected static String canonicalizePrefix(String prefix) {
// make it foolproof
prefix = prefix.replace('\\', '/');
// ensure there's a trailing slash
if (prefix.length() > 0 && !prefix.endsWith("/")) {
prefix += "/";
}
return prefix;
}
}
作用:抽象模板加载器,它可以加载位置可以用URL描述的模板。这个超类只适用于仅获取URL就能立即判断资源是否存在的情况,而不适用于需要检查响应头才能知道的情况。子类只需要重写getURL(String)方法。
3.ClassTemplateLoader
代码
public class ClassTemplateLoader extends URLTemplateLoader {
private final Class<?> resourceLoaderClass;
private final ClassLoader classLoader;
private final String basePackagePath;
@Deprecated
public ClassTemplateLoader() {
this(null, true, null, "/");
}
@Deprecated
public ClassTemplateLoader(Class<?> resourceLoaderClass) {
this(resourceLoaderClass, "");
}
public ClassTemplateLoader(Class<?> resourceLoaderClass, String basePackagePath) {
this(resourceLoaderClass, false, null, basePackagePath);
}
public ClassTemplateLoader(ClassLoader classLoader, String basePackagePath) {
this(null, true, classLoader, basePackagePath);
}
private ClassTemplateLoader(Class<?> resourceLoaderClass, boolean allowNullResourceLoaderClass,
ClassLoader classLoader, String basePackagePath) {
if (!allowNullResourceLoaderClass) {
NullArgumentException.check("resourceLoaderClass", resourceLoaderClass);
}
NullArgumentException.check("basePackagePath", basePackagePath);
// Either set a non-null resourceLoaderClass or a non-null classLoader, not both:
this.resourceLoaderClass = classLoader == null ? (resourceLoaderClass == null ? this.getClass()
: resourceLoaderClass) : null;
if (this.resourceLoaderClass == null && classLoader == null) {
throw new NullArgumentException("classLoader");
}
this.classLoader = classLoader;
String canonBasePackagePath = canonicalizePrefix(basePackagePath);
if (this.classLoader != null && canonBasePackagePath.startsWith("/")) {
canonBasePackagePath = canonBasePackagePath.substring(1);
}
this.basePackagePath = canonBasePackagePath;
}
@Override
protected URL getURL(String name) {
String fullPath = basePackagePath + name;
// Block java.net.URLClassLoader exploits:
if (basePackagePath.equals("/") && !isSchemeless(fullPath)) {
return null;
}
return resourceLoaderClass != null ? resourceLoaderClass.getResource(fullPath) : classLoader
.getResource(fullPath);
}
private static boolean isSchemeless(String fullPath) {
int i = 0;
int ln = fullPath.length();
// Skip a single initial /, as things like "/file:/..." might work:
if (i < ln && fullPath.charAt(i) == '/') i++;
// Check if there's no ":" earlier than a '/', as the URLClassLoader
// could interpret that as an URL scheme:
while (i < ln) {
char c = fullPath.charAt(i);
if (c == '/') return true;
if (c == ':') return false;
i++;
}
return true;
}
@Override
public String toString() {
return TemplateLoaderUtils.getClassNameForToString(this) + "("
+ (resourceLoaderClass != null
? "resourceLoaderClass=" + resourceLoaderClass.getName()
: "classLoader=" + StringUtil.jQuote(classLoader))
+ ", basePackagePath"
+ "="
+ StringUtil.jQuote(basePackagePath)
+ (resourceLoaderClass != null
? (basePackagePath.startsWith("/") ? "" : " /* relatively to resourceLoaderClass pkg */")
: ""
)
+ ")";
}
public Class getResourceLoaderClass() {
return resourceLoaderClass;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public String getBasePackagePath() {
return basePackagePath;
}
}
作用:一个可以从类路径加载模板的TemplateLoader的子类。它既可以在jar包中加载,也可以通过调用此类进行加载。
4.WebappTemplateLoader
代码
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;
public WebappTemplateLoader(ServletContext servletContext) {
this(servletContext, "/");
}
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();
}
}
public Boolean getURLConnectionUsesCaches() {
return urlConnectionUsesCaches;
}
public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
this.urlConnectionUsesCaches = urlConnectionUsesCaches;
}
@Override
public String toString() {
return TemplateLoaderUtils.getClassNameForToString(this)
+ "(subdirPath=" + StringUtil.jQuote(subdirPath)
+ ", servletContext={contextPath=" + StringUtil.jQuote(getContextPath())
+ ", displayName=" + StringUtil.jQuote(servletContext.getServletContextName()) + "})";
}
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]";
}
}
public boolean getAttemptFileAccess() {
return attemptFileAccess;
}
public void setAttemptFileAccess(boolean attemptLoadingFromFile) {
this.attemptFileAccess = attemptLoadingFromFile;
}
}
作用:TemplateLoader的子类,以通过ServletContext.getResource(String)可以到达的stream作为模板来源
5.StringTemplateLoader
代码
public class StringTemplateLoader implements TemplateLoader {
private final Map<String, StringTemplateSource> templates = new HashMap<>();
public void putTemplate(String name, String templateContent) {
putTemplate(name, templateContent, System.currentTimeMillis());
}
public void putTemplate(String name, String templateContent, long lastModified) {
templates.put(name, new StringTemplateSource(name, templateContent, lastModified));
}
public boolean removeTemplate(String name) {
return templates.remove(name) != null;
}
@Override
public void closeTemplateSource(Object templateSource) {
}
@Override
public Object findTemplateSource(String name) {
return templates.get(name);
}
@Override
public long getLastModified(Object templateSource) {
return ((StringTemplateSource) templateSource).lastModified;
}
@Override
public Reader getReader(Object templateSource, String encoding) {
return new StringReader(((StringTemplateSource) templateSource).templateContent);
}
private static class StringTemplateSource {
private final String name;
private final String templateContent;
private final long lastModified;
StringTemplateSource(String name, String templateContent, long lastModified) {
if (name == null) {
throw new IllegalArgumentException("name == null");
}
if (templateContent == null) {
throw new IllegalArgumentException("source == null");
}
if (lastModified < -1L) {
throw new IllegalArgumentException("lastModified < -1L");
}
this.name = name;
this.templateContent = templateContent;
this.lastModified = lastModified;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StringTemplateSource other = (StringTemplateSource) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return name;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(TemplateLoaderUtils.getClassNameForToString(this));
sb.append("(Map { ");
int cnt = 0;
for (String name : templates.keySet()) {
cnt++;
if (cnt != 1) {
sb.append(", ");
}
if (cnt > 10) {
sb.append("...");
break;
}
sb.append(StringUtil.jQuote(name));
sb.append("=...");
}
if (cnt != 0) {
sb.append(' ');
}
sb.append("})");
return sb.toString();
}
}
作用:使用字符串创建模板加载器
6.ByteArrayTemplateLoader
代码
public class ByteArrayTemplateLoader implements TemplateLoader {
private final Map<String, ByteArrayTemplateSource> templates = new HashMap<>();
ds a template to this template loader; see {
@link StringTemplateLoader#putTemplate(String, String)} for more.
*/
public void putTemplate(String name, byte[] templateContent) {
putTemplate(name, templateContent, System.currentTimeMillis());
}
public void putTemplate(String name, byte[] templateContent, long lastModified) {
templates.put(name, new ByteArrayTemplateSource(name, templateContent, lastModified));
}
public boolean removeTemplate(String name) {
return templates.remove(name) != null;
}
@Override
public void closeTemplateSource(Object templateSource) {
}
@Override
public Object findTemplateSource(String name) {
return templates.get(name);
}
@Override
public long getLastModified(Object templateSource) {
return ((ByteArrayTemplateSource) templateSource).lastModified;
}
@Override
public Reader getReader(Object templateSource, String encoding) throws UnsupportedEncodingException {
return new InputStreamReader(
new ByteArrayInputStream(((ByteArrayTemplateSource) templateSource).templateContent),
encoding);
}
private static class ByteArrayTemplateSource {
private final String name;
private final byte[] templateContent;
private final long lastModified;
ByteArrayTemplateSource(String name, byte[] templateContent, long lastModified) {
if (name == null) {
throw new IllegalArgumentException("name == null");
}
if (templateContent == null) {
throw new IllegalArgumentException("templateContent == null");
}
if (lastModified < -1L) {
throw new IllegalArgumentException("lastModified < -1L");
}
this.name = name;
this.templateContent = templateContent;
this.lastModified = lastModified;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ByteArrayTemplateSource other = (ByteArrayTemplateSource) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(TemplateLoaderUtils.getClassNameForToString(this));
sb.append("(Map { ");
int cnt = 0;
for (String name : templates.keySet()) {
cnt++;
if (cnt != 1) {
sb.append(", ");
}
if (cnt > 10) {
sb.append("...");
break;
}
sb.append(StringUtil.jQuote(name));
sb.append("=...");
}
if (cnt != 0) {
sb.append(' ');
}
sb.append("})");
return sb.toString();
}
}
作用:与StringTemplateLoader类似,使用byte[]创建模板加载器
7.FileTemplateLoader
public class FileTemplateLoader implements TemplateLoader {
public static String SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM
= "org.freemarker.emulateCaseSensitiveFileSystem";
private static final boolean EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT;
static {
final String s = SecurityUtilities.getSystemProperty(SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM,
"false");
boolean emuCaseSensFS;
try {
emuCaseSensFS = StringUtil.getYesNo(s);
} catch (Exception e) {
emuCaseSensFS = false;
}
EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT = emuCaseSensFS;
}
private static final int CASE_CHECH_CACHE_HARD_SIZE = 50;
private static final int CASE_CHECK_CACHE__SOFT_SIZE = 1000;
private static final boolean SEP_IS_SLASH = File.separatorChar == '/';
private static final Logger LOG = Logger.getLogger("freemarker.cache");
public final File baseDir;
private final String canonicalBasePath;
private boolean emulateCaseSensitiveFileSystem;
private MruCacheStorage correctCasePaths;
@Deprecated
public FileTemplateLoader() throws IOException {
this(new File(SecurityUtilities.getSystemProperty("user.dir")));
}
public FileTemplateLoader(final File baseDir) throws IOException {
this(baseDir, false);
}
public FileTemplateLoader(final File baseDir, final boolean disableCanonicalPathCheck) throws IOException {
try {
Object[] retval = AccessController.doPrivileged(new PrivilegedExceptionAction<Object[]>() {
@Override
public Object[] run() throws IOException {
if (!baseDir.exists()) {
throw new FileNotFoundException(baseDir + " does not exist.");
}
if (!baseDir.isDirectory()) {
throw new IOException(baseDir + " is not a directory.");
}
Object[] retval = new Object[2];
if (disableCanonicalPathCheck) {
retval[0] = baseDir;
retval[1] = null;
} else {
retval[0] = baseDir.getCanonicalFile();
String basePath = ((File) retval[0]).getPath();
// Most canonical paths don't end with File.separator,
// but some does. Like, "C:\" VS "C:\templates".
if (!basePath.endsWith(File.separator)) {
basePath += File.separatorChar;
}
retval[1] = basePath;
}
return retval;
}
});
this.baseDir = (File) retval[0];
this.canonicalBasePath = (String) retval[1];
setEmulateCaseSensitiveFileSystem(getEmulateCaseSensitiveFileSystemDefault());
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
@Override
public Object findTemplateSource(final String name) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
@Override
public File run() throws IOException {
File source = new File(baseDir, SEP_IS_SLASH ? name :
name.replace('/', File.separatorChar));
if (!source.isFile()) {
return null;
}
// Security check for inadvertently returning something
// outside the template directory when linking is not
// allowed.
if (canonicalBasePath != null) {
String normalized = source.getCanonicalPath();
if (!normalized.startsWith(canonicalBasePath)) {
throw new SecurityException(source.getAbsolutePath()
+ " resolves to " + normalized + " which "
+ " doesn't start with " + canonicalBasePath);
}
}
if (emulateCaseSensitiveFileSystem && !isNameCaseCorrect(source)) {
return null;
}
return source;
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
@Override
public long getLastModified(final Object templateSource) {
return (AccessController.doPrivileged(new PrivilegedAction<Long>() {
@Override
public Long run() {
return Long.valueOf(((File) templateSource).lastModified());
}
})).longValue();
}
@Override
public Reader getReader(final Object templateSource, final String encoding) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Reader>() {
@Override
public Reader run() throws IOException {
if (!(templateSource instanceof File)) {
throw new IllegalArgumentException(
"templateSource wasn't a File, but a: " +
templateSource.getClass().getName());
}
return new InputStreamReader(new FileInputStream((File) templateSource), encoding);
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
private boolean isNameCaseCorrect(File source) throws IOException {
final String sourcePath = source.getPath();
synchronized (correctCasePaths) {
if (correctCasePaths.get(sourcePath) != null) {
return true;
}
}
final File parentDir = source.getParentFile();
if (parentDir != null) {
if (!baseDir.equals(parentDir) && !isNameCaseCorrect(parentDir)) {
return false;
}
final String[] listing = parentDir.list();
if (listing != null) {
final String fileName = source.getName();
boolean identicalNameFound = false;
for (int i = 0; !identicalNameFound && i < listing.length; i++) {
if (fileName.equals(listing[i])) {
identicalNameFound = true;
}
}
if (!identicalNameFound) {
// If we find a similarly named file that only differs in case, then this is a file-not-found.
for (int i = 0; i < listing.length; i++) {
final String listingEntry = listing[i];
if (fileName.equalsIgnoreCase(listingEntry)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Emulating file-not-found because of letter case differences to the "
+ "real file, for: " + sourcePath);
}
return false;
}
}
}
}
}
synchronized (correctCasePaths) {
correctCasePaths.put(sourcePath, Boolean.TRUE);
}
return true;
}
@Override
public void closeTemplateSource(Object templateSource) {
// Do nothing.
}
public File getBaseDirectory() {
return baseDir;
}
public void setEmulateCaseSensitiveFileSystem(boolean nameCaseChecked) {
// Ensure that the cache exists exactly when needed:
if (nameCaseChecked) {
if (correctCasePaths == null) {
correctCasePaths = new MruCacheStorage(CASE_CHECH_CACHE_HARD_SIZE, CASE_CHECK_CACHE__SOFT_SIZE);
}
} else {
correctCasePaths = null;
}
this.emulateCaseSensitiveFileSystem = nameCaseChecked;
}
public boolean getEmulateCaseSensitiveFileSystem() {
return emulateCaseSensitiveFileSystem;
}
protected boolean getEmulateCaseSensitiveFileSystemDefault() {
return EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT;
}
@Override
public String toString() {
// We don't StringUtil.jQuote paths here, because on Windows there will be \\-s then that some may find
// confusing.
return TemplateLoaderUtils.getClassNameForToString(this) + "("
+ "baseDir=\"" + baseDir + "\""
+ (canonicalBasePath != null ? ", canonicalBasePath=\"" + canonicalBasePath + "\"" : "")
+ (emulateCaseSensitiveFileSystem ? ", emulateCaseSensitiveFileSystem=true" : "")
+ ")";
}
}
作用:使用特定文件夹下的文件生成模板加载器。生成之前会进行安全性检测
8.MultiTemplateLoader
代码
public class MultiTemplateLoader implements StatefulTemplateLoader {
private final TemplateLoader[] templateLoaders;
private final Map<String, TemplateLoader> lastTemplateLoaderForName
= new ConcurrentHashMap<>();
private boolean sticky = true;
public MultiTemplateLoader(TemplateLoader[] templateLoaders) {
NullArgumentException.check("templateLoaders", templateLoaders);
this.templateLoaders = templateLoaders.clone();
}
@Override
public Object findTemplateSource(String name)
throws IOException {
TemplateLoader lastTemplateLoader = null;
if (sticky) {
// Use soft affinity - give the loader that last found this
// resource a chance to find it again first.
lastTemplateLoader = lastTemplateLoaderForName.get(name);
if (lastTemplateLoader != null) {
Object source = lastTemplateLoader.findTemplateSource(name);
if (source != null) {
return new MultiSource(source, lastTemplateLoader);
}
}
}
// If there is no affine loader, or it could not find the resource
// again, try all loaders in order of appearance. If any manages
// to find the resource, then associate it as the new affine loader
// for this resource.
for (TemplateLoader templateLoader : templateLoaders) {
if (lastTemplateLoader != templateLoader) {
Object source = templateLoader.findTemplateSource(name);
if (source != null) {
if (sticky) {
lastTemplateLoaderForName.put(name, templateLoader);
}
return new MultiSource(source, templateLoader);
}
}
}
if (sticky) {
lastTemplateLoaderForName.remove(name);
}
// Resource not found
return null;
}
@Override
public long getLastModified(Object templateSource) {
return ((MultiSource) templateSource).getLastModified();
}
@Override
public Reader getReader(Object templateSource, String encoding)
throws IOException {
return ((MultiSource) templateSource).getReader(encoding);
}
@Override
public void closeTemplateSource(Object templateSource)
throws IOException {
((MultiSource) templateSource).close();
}
@Override
public void resetState() {
lastTemplateLoaderForName.clear();
for (TemplateLoader loader : templateLoaders) {
if (loader instanceof StatefulTemplateLoader) {
((StatefulTemplateLoader) loader).resetState();
}
}
}
static final class MultiSource {
private final Object source;
private final TemplateLoader loader;
MultiSource(Object source, TemplateLoader loader) {
this.source = source;
this.loader = loader;
}
long getLastModified() {
return loader.getLastModified(source);
}
Reader getReader(String encoding)
throws IOException {
return loader.getReader(source, encoding);
}
void close()
throws IOException {
loader.closeTemplateSource(source);
}
Object getWrappedSource() {
return source;
}
@Override
public boolean equals(Object o) {
if (o instanceof MultiSource) {
MultiSource m = (MultiSource) o;
return m.loader.equals(loader) && m.source.equals(source);
}
return false;
}
@Override
public int hashCode() {
return loader.hashCode() + 31 * source.hashCode();
}
@Override
public String toString() {
return source.toString();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("MultiTemplateLoader(");
for (int i = 0; i < templateLoaders.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append("loader").append(i + 1).append(" = ").append(templateLoaders[i]);
}
sb.append(")");
return sb.toString();
}
public int getTemplateLoaderCount() {
return templateLoaders.length;
}
public TemplateLoader getTemplateLoader(int index) {
return templateLoaders[index];
}
lean isSticky() {
return sticky;
}
public void setSticky(boolean sticky) {
this.sticky = sticky;
}
}
作用:使用其他的模板生成器生成模板
注:Freemarker代码来自FreeMarker 中文官方参考手册
新手写的代码分析,文章若有错误还请指出