blob: 1bfbec8e70b7d60531604e530f73a59b44204930 [file] [log] [blame]
/*
* 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.net.URL;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.StringUtil;
/**
* A {@link TemplateLoader} that can load templates from the "classpath". Naturally, it can load from jar files, or from
* anywhere where Java can load classes from. Internally, it uses {@link Class#getResource(String)} or
* {@link ClassLoader#getResource(String)} to load templates.
*/
public class ClassTemplateLoader extends URLTemplateLoader {
private final Class resourceLoaderClass;
private final ClassLoader classLoader;
private final String basePackagePath;
/**
* Creates a template loader that will use the {@link Class#getResource(String)} method of its own class to load the
* resources, and {@code "/"} as base package path. This means that that template paths will be resolved relatively
* the root package of the class hierarchy, so you hardly ever should use this constructor, rather do something like
* this:<br>
* {@link #ClassTemplateLoader(Class, String) new ClassTemplateLoader(com.example.myapplication.SomeClass.class,
* "templates")}
*
* <p>
* If you extend this class, then the extending class will be used to load the resources.
*
* @deprecated It's a confusing constructor, and seldom useful; use {@link #ClassTemplateLoader(Class, String)}
* instead.
*/
@Deprecated
public ClassTemplateLoader() {
this(null, true, null, "/");
}
/**
* Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to
* load the resources, and {@code ""} as base package path. This means that template paths will be resolved
* relatively to the class location, that is, relatively to the directory (package) of the class.
*
* @param resourceLoaderClass
* the class whose {@link Class#getResource(String)} will be used to load the templates.
*
* @deprecated It's confusing that the base path is {@code ""}; use {@link #ClassTemplateLoader(Class, String)}
* instead.
*/
@Deprecated
public ClassTemplateLoader(Class resourceLoaderClass) {
this(resourceLoaderClass, "");
}
/**
* Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to
* load the resources, and the specified base package path (absolute or relative).
*
* <p>
* Examples:
* <ul>
* <li>Relative base path (will load from the {@code com.example.myapplication.templates} package):<br>
* {@code new ClassTemplateLoader(com.example.myapplication.SomeClass.class, "templates")}
* <li>Absolute base path:<br>
* {@code new ClassTemplateLoader(somepackage.SomeClass.class, "/com/example/myapplication/templates")}
* </ul>
*
* @param resourceLoaderClass
* The class whose {@link Class#getResource(String)} method will be used to load the templates. Be sure
* that you chose a class whose defining class-loader sees the templates. This parameter can't be
* {@code null}.
* @param basePackagePath
* The package that contains the templates, in path ({@code /}-separated) format. If it doesn't start
* with a {@code /} then it's relative to the path (package) of the {@code resourceLoaderClass} class. If
* it starts with {@code /} then it's relative to the root of the package hierarchy. Note that path
* components should be separated by forward slashes independently of the separator character used by the
* underlying operating system. This parameter can't be {@code null}.
*
* @see #ClassTemplateLoader(ClassLoader, String)
*/
public ClassTemplateLoader(Class resourceLoaderClass, String basePackagePath) {
this(resourceLoaderClass, false, null, basePackagePath);
}
/**
* Similar to {@link #ClassTemplateLoader(Class, String)}, but instead of {@link Class#getResource(String)} it uses
* {@link ClassLoader#getResource(String)}. Because a {@link ClassLoader} isn't bound to any Java package, it
* doesn't mater if the {@code basePackagePath} starts with {@code /} or not, it will be always relative to the root
* of the package hierarchy
*
* @since 2.3.22
*/
public ClassTemplateLoader(ClassLoader classLoader, String basePackagePath) {
this(null, true, classLoader, basePackagePath);
}
private ClassTemplateLoader(Class resourceLoaderClass, boolean allowNullBaseClass, ClassLoader classLoader,
String basePackagePath) {
if (!allowNullBaseClass) {
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;
}
/**
* 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) + "("
+ (resourceLoaderClass != null
? "resourceLoaderClass=" + resourceLoaderClass.getName()
: "classLoader=" + StringUtil.jQuote(classLoader))
+ ", basePackagePath"
+ "="
+ StringUtil.jQuote(basePackagePath)
+ (resourceLoaderClass != null
? (basePackagePath.startsWith("/") ? "" : " /* relatively to resourceLoaderClass pkg */")
: ""
)
+ ")";
}
/**
* See the similar parameter of {@link #ClassTemplateLoader(Class, String)}; {@code null} when other mechanism is
* used to load the resources.
*
* @since 2.3.22
*/
public Class getResourceLoaderClass() {
return resourceLoaderClass;
}
/**
* See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; {@code null} when other mechanism
* is used to load the resources.
*
* @since 2.3.22
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; note that this is a normalized
* version of what was actually passed to the constructor.
*
* @since 2.3.22
*/
public String getBasePackagePath() {
return basePackagePath;
}
}