/*
* 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
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;
}
/**
* 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;
}
}