blob: 1500056f85fa76724762a119c49aa35e714539d4 [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.io.IOException;
import java.io.Reader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 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, if a request
* for some template name was already satisfied in the past by one of the loaders, that Loader is queried first (a soft
* affinity).
*
* <p>
* This class is <em>not</em> thread-safe. If it's accessed from multiple threads concurrently, proper synchronization
* must be provided by the callers. Note that {@link TemplateCache}, the natural user of this class, provides the
* necessary synchronizations when it uses this class, so then you don't have to worry this.
*/
public class MultiTemplateLoader implements StatefulTemplateLoader {
private final TemplateLoader[] loaders;
private final Map lastLoaderForName = Collections.synchronizedMap(new HashMap());
/**
* Creates a new multi template Loader that will use the specified loaders.
*
* @param loaders
* the loaders that are used to load templates.
*/
public MultiTemplateLoader(TemplateLoader[] loaders) {
this.loaders = loaders.clone();
}
public Object findTemplateSource(String name)
throws IOException {
// Use soft affinity - give the loader that last found this
// resource a chance to find it again first.
TemplateLoader lastLoader = (TemplateLoader) lastLoaderForName.get(name);
if (lastLoader != null) {
Object source = lastLoader.findTemplateSource(name);
if (source != null) {
return new MultiSource(source, lastLoader);
}
}
// 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 (int i = 0; i < loaders.length; ++i) {
TemplateLoader loader = loaders[i];
Object source = loader.findTemplateSource(name);
if (source != null) {
lastLoaderForName.put(name, loader);
return new MultiSource(source, loader);
}
}
lastLoaderForName.remove(name);
// Resource not found
return null;
}
private Object modifyForIcI(Object source) {
// TODO Auto-generated method stub
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();
}
public void resetState() {
lastLoaderForName.clear();
for (int i = 0; i < loaders.length; i++) {
TemplateLoader loader = loaders[i];
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 < loaders.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append("loader").append(i + 1).append(" = ").append(loaders[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 loaders.length;
}
/**
* Returns the {@link TemplateLoader} at the given index.
*
* @param index
* Must be below {@link #getTemplateLoaderCount()}.
*/
public TemplateLoader getTemplateLoader(int index) {
return loaders[index];
}
}