/*
* 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.
IOException;
import java.io.
Reader;
import java.util.
Map;
import java.util.concurrent.
ConcurrentHashMap;
import freemarker.template.utility.
NullArgumentException;
/**
* A {@link TemplateLoader} that uses a set of other loaders to load the templates. On every request, loaders are
* queried in the order of their appearance in the array of loaders provided to the constructor. However, by default, if
* a request for some template name was already satisfied in the past by one of the loaders, that loader is queried
* first (stickiness). This behavior can be disabled with {@link #setSticky(boolean)}, then the loaders are always
* queried in the order of their appearance in the array.
*
* <p>This class is thread-safe.
*/
public class
MultiTemplateLoader implements
StatefulTemplateLoader {
private final
TemplateLoader[]
templateLoaders;
private final
Map<
String,
TemplateLoader>
lastTemplateLoaderForName
= new
ConcurrentHashMap<
String,
TemplateLoader>();
private boolean
sticky = true;
/**
* Creates a new instance that will use the specified template loaders.
*
* @param templateLoaders
* the template loaders that are used to load templates, in the order as they will be searched
* (except where {@linkplain #setSticky(boolean) stickiness} says otherwise).
*/
public
MultiTemplateLoader(
TemplateLoader[]
templateLoaders) {
NullArgumentException.
check("templateLoaders",
templateLoaders);
this.
templateLoaders =
templateLoaders.
clone();
}
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;
}
public long
getLastModified(
Object templateSource) {
return ((
MultiSource)
templateSource).
getLastModified();
}
public
Reader getReader(
Object templateSource,
String encoding)
throws
IOException {
return ((
MultiSource)
templateSource).
getReader(
encoding);
}
public void
closeTemplateSource(
Object templateSource)
throws
IOException {
((
MultiSource)
templateSource).
close();
}
/**
* Clears the sickiness memory, also resets the state of all enclosed {@link StatefulTemplateLoader}-s.
*/
public void
resetState() {
lastTemplateLoaderForName.
clear();
for (
TemplateLoader loader :
templateLoaders) {
if (
loader instanceof
StatefulTemplateLoader) {
((
StatefulTemplateLoader)
loader).
resetState();
}
}
}
/**
* Represents a template source bound to a specific template loader. It serves as the complete template source
* descriptor used by the MultiTemplateLoader class.
*/
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();
}
}
/**
* Show class name and some details that are useful in template-not-found errors.
*
* @since 2.3.21
*/
@
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();
}
/**
* Returns the number of {@link TemplateLoader}-s directly inside this {@link TemplateLoader}.
*
* @since 2.3.23
*/
public int
getTemplateLoaderCount() {
return
templateLoaders.length;
}
/**
* Returns the {@link TemplateLoader} at the given index.
*
* @param index
* Must be below {@link #getTemplateLoaderCount()}.
*/
public
TemplateLoader getTemplateLoader(int
index) {
return
templateLoaders[
index];
}
/**
* Getter pair of {@link #setSticky(boolean)}.
*
* @since 2.3.24
*/
public boolean
isSticky() {
return
sticky;
}
/**
* Sets if for a name that was already loaded earlier the same {@link TemplateLoader} will be tried first, or
* we always try the {@link TemplateLoader}-s strictly in the order as it was specified in the constructor.
* The default is {@code true}.
*
* @since 2.3.24
*/
public void
setSticky(boolean
sticky) {
this.
sticky =
sticky;
}
}