/*
 * 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.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

import freemarker.cache.MultiTemplateLoader.MultiSource;
import freemarker.core.BugException;
import freemarker.core.Environment;
import freemarker.core.TemplateConfiguration;
import freemarker.log.Logger;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateNotFoundException;
import freemarker.template._TemplateAPI;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.UndeclaredThrowableException;

Performs caching and on-demand loading of the templates. The actual template "file" loading is delegated to a TemplateLoader that you can specify in the constructor. Some aspects of caching is delegated to a CacheStorage that you can also specify in the constructor.

Typically you don't instantiate or otherwise use this class directly. The Configuration embeds an instance of this class, that you access indirectly through Configuration.getTemplate(String) and other Configuration API-s. Then TemplateLoader and CacheStorage can be set with Configuration.setTemplateLoader(TemplateLoader) and Configuration.setCacheStorage(CacheStorage).

/** * Performs caching and on-demand loading of the templates. * The actual template "file" loading is delegated to a {@link TemplateLoader} that you can specify in the constructor. * Some aspects of caching is delegated to a {@link CacheStorage} that you can also specify in the constructor. * * <p>Typically you don't instantiate or otherwise use this class directly. The {@link Configuration} embeds an * instance of this class, that you access indirectly through {@link Configuration#getTemplate(String)} and other * {@link Configuration} API-s. Then {@link TemplateLoader} and {@link CacheStorage} can be set with * {@link Configuration#setTemplateLoader(TemplateLoader)} and * {@link Configuration#setCacheStorage(CacheStorage)}. */
public class TemplateCache {
The default template update delay; see Configuration.setTemplateUpdateDelayMilliseconds(long).
Since:2.3.23
/** * The default template update delay; see {@link Configuration#setTemplateUpdateDelayMilliseconds(long)}. * * @since 2.3.23 */
public static final long DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS = 5000L; private static final String ASTERISKSTR = "*"; private static final char ASTERISK = '*'; private static final char SLASH = '/'; private static final String LOCALE_PART_SEPARATOR = "_"; private static final Logger LOG = Logger.getLogger("freemarker.cache");
Maybe null.
/** Maybe {@code null}. */
private final TemplateLoader templateLoader;
Here we keep our cached templates
/** Here we keep our cached templates */
private final CacheStorage storage; private final TemplateLookupStrategy templateLookupStrategy; private final TemplateNameFormat templateNameFormat; private final TemplateConfigurationFactory templateConfigurations; private final boolean isStorageConcurrent; /** {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} */ private long updateDelay = DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS; /** {@link Configuration#setLocalizedLookup(boolean)} */ private boolean localizedLookup = true; private Configuration config;
Returns a template cache that will first try to load a template from the file system relative to the current user directory (i.e. the value of the system property user.dir), then from the classpath.
Deprecated:Use TemplateCache(TemplateLoader) instead. The default loader is useless in most applications, also it can mean a security risk.
/** * Returns a template cache that will first try to load a template from * the file system relative to the current user directory (i.e. the value * of the system property <code>user.dir</code>), then from the classpath. * * @deprecated Use {@link #TemplateCache(TemplateLoader)} instead. The default loader is useless in most * applications, also it can mean a security risk. */
@Deprecated public TemplateCache() { this(_TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0)); }
Deprecated:Use TemplateCache(TemplateLoader, CacheStorage, Configuration) instead.
/** * @deprecated Use {@link #TemplateCache(TemplateLoader, CacheStorage, Configuration)} instead. */
@Deprecated public TemplateCache(TemplateLoader templateLoader) { this(templateLoader, (Configuration) null); }
Deprecated:Use TemplateCache(TemplateLoader, CacheStorage, Configuration) instead.
/** * @deprecated Use {@link #TemplateCache(TemplateLoader, CacheStorage, Configuration)} instead. */
@Deprecated public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage) { this(templateLoader, cacheStorage, null); }
Since:2.3.21
/** * Same as {@link #TemplateCache(TemplateLoader, CacheStorage, Configuration)} with a new {@link SoftCacheStorage} * as the 2nd parameter. * * @since 2.3.21 */
public TemplateCache(TemplateLoader templateLoader, Configuration config) { this(templateLoader, _TemplateAPI.createDefaultCacheStorage(Configuration.VERSION_2_3_0), config); }
Since:2.3.21
/** * Same as * {@link #TemplateCache(TemplateLoader, CacheStorage, TemplateLookupStrategy, TemplateNameFormat, Configuration)} * with {@link TemplateLookupStrategy#DEFAULT_2_3_0} and {@link TemplateNameFormat#DEFAULT_2_3_0}. * * @since 2.3.21 */
public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage, Configuration config) { this(templateLoader, cacheStorage, _TemplateAPI.getDefaultTemplateLookupStrategy(Configuration.VERSION_2_3_0), _TemplateAPI.getDefaultTemplateNameFormat(Configuration.VERSION_2_3_0), config); }
Since:2.3.22
/** * Same as * {@link TemplateCache#TemplateCache(TemplateLoader, CacheStorage, TemplateLookupStrategy, TemplateNameFormat, * TemplateConfigurationFactory, Configuration)} with {@code null} for {@code templateConfigurations}-s. * * @since 2.3.22 */
public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage, TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat, Configuration config) { this(templateLoader, cacheStorage, templateLookupStrategy, templateNameFormat, null, config); }
Params:
Since:2.3.24
/** * @param templateLoader * The {@link TemplateLoader} to use. Can be {@code null}, though then every request will result in * {@link TemplateNotFoundException}. * @param cacheStorage * The {@link CacheStorage} to use. Can't be {@code null}. * @param templateLookupStrategy * The {@link TemplateLookupStrategy} to use. Can't be {@code null}. * @param templateNameFormat * The {@link TemplateNameFormat} to use. Can't be {@code null}. * @param templateConfigurations * The {@link TemplateConfigurationFactory} to use. Can be {@code null} (then all templates will use the * settings coming from the {@link Configuration} as is). * @param config * The {@link Configuration} this cache will be used for. Can be {@code null} for backward compatibility, * as it can be set with {@link #setConfiguration(Configuration)} later. * * @since 2.3.24 */
public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage, TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat, TemplateConfigurationFactory templateConfigurations, Configuration config) { this.templateLoader = templateLoader; NullArgumentException.check("cacheStorage", cacheStorage); this.storage = cacheStorage; isStorageConcurrent = cacheStorage instanceof ConcurrentCacheStorage && ((ConcurrentCacheStorage) cacheStorage).isConcurrent(); NullArgumentException.check("templateLookupStrategy", templateLookupStrategy); this.templateLookupStrategy = templateLookupStrategy; NullArgumentException.check("templateNameFormat", templateNameFormat); this.templateNameFormat = templateNameFormat; // Can be null this.templateConfigurations = templateConfigurations; this.config = config; }
Sets the configuration object to which this cache belongs. This method is called by the configuration itself to establish the relation, and should not be called by users.
Deprecated:Use the TemplateCache(TemplateLoader, CacheStorage, Configuration) constructor.
/** * Sets the configuration object to which this cache belongs. This * method is called by the configuration itself to establish the * relation, and should not be called by users. * * @deprecated Use the {@link #TemplateCache(TemplateLoader, CacheStorage, Configuration)} constructor. */
@Deprecated public void setConfiguration(Configuration config) { this.config = config; clear(); } public TemplateLoader getTemplateLoader() { return templateLoader; } public CacheStorage getCacheStorage() { return storage; }
Since:2.3.22
/** * @since 2.3.22 */
public TemplateLookupStrategy getTemplateLookupStrategy() { return templateLookupStrategy; }
Since:2.3.22
/** * @since 2.3.22 */
public TemplateNameFormat getTemplateNameFormat() { return templateNameFormat; }
Since:2.3.24
/** * @since 2.3.24 */
public TemplateConfigurationFactory getTemplateConfigurations() { return templateConfigurations; }
Retrieves the template with the given name (and according the specified further parameters) from the template cache, loading it into the cache first if it's missing/staled.

All parameters must be non-null, except customLookupCondition. For the meaning of the parameters see Configuration.getTemplate(String, Locale, String, boolean).

Throws:
Returns:A MaybeMissingTemplate object that contains the Template, or a MaybeMissingTemplate object that contains null as the Template and information about the missing template. The return value itself is never null. Note that exceptions occurring during template loading will not be classified as a missing template, so they will cause an exception to be thrown by this method instead of returning a MaybeMissingTemplate. The idea is that having a missing template is normal (not exceptional), providing that the backing storage mechanism could indeed check that it's missing.
Since:2.3.22
/** * Retrieves the template with the given name (and according the specified further parameters) from the template * cache, loading it into the cache first if it's missing/staled. * * <p> * All parameters must be non-{@code null}, except {@code customLookupCondition}. For the meaning of the parameters * see {@link Configuration#getTemplate(String, Locale, String, boolean)}. * * @return A {@link MaybeMissingTemplate} object that contains the {@link Template}, or a * {@link MaybeMissingTemplate} object that contains {@code null} as the {@link Template} and information * about the missing template. The return value itself is never {@code null}. Note that exceptions occurring * during template loading will not be classified as a missing template, so they will cause an exception to * be thrown by this method instead of returning a {@link MaybeMissingTemplate}. The idea is that having a * missing template is normal (not exceptional), providing that the backing storage mechanism could indeed * check that it's missing. * * @throws MalformedTemplateNameException * If the {@code name} was malformed according the current {@link TemplateNameFormat}. However, if the * {@link TemplateNameFormat} is {@link TemplateNameFormat#DEFAULT_2_3_0} and * {@link Configuration#getIncompatibleImprovements()} is less than 2.4.0, then instead of throwing this * exception, a {@link MaybeMissingTemplate} will be returned, similarly as if the template were missing * (the {@link MaybeMissingTemplate#getMissingTemplateReason()} will describe the real error). * * @throws IOException * If reading the template has failed from a reason other than the template is missing. This method * should never be a {@link TemplateNotFoundException}, as that condition is indicated in the return * value. * * @since 2.3.22 */
public MaybeMissingTemplate getTemplate(String name, Locale locale, Object customLookupCondition, String encoding, boolean parseAsFTL) throws IOException { NullArgumentException.check("name", name); NullArgumentException.check("locale", locale); NullArgumentException.check("encoding", encoding); try { name = templateNameFormat.normalizeRootBasedName(name); } catch (MalformedTemplateNameException e) { // If we don't have to emulate backward compatible behavior, then just rethrow it: if (templateNameFormat != TemplateNameFormat.DEFAULT_2_3_0 || config.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_4_0) { throw e; } return new MaybeMissingTemplate(null, e); } if (templateLoader == null) { return new MaybeMissingTemplate(name, "The TemplateLoader was null."); } Template template = getTemplateInternal(name, locale, customLookupCondition, encoding, parseAsFTL); return template != null ? new MaybeMissingTemplate(template) : new MaybeMissingTemplate(name, (String) null); }
Similar to getTemplate(String, Locale, Object, String, boolean) with null customLookupCondition.
Returns:MaybeMissingTemplate.getTemplate() of the getTemplate(String, Locale, Object, String, boolean) return value.
Deprecated:Use getTemplate(String, Locale, Object, String, boolean), which can return more detailed result when the template is missing.
/** * Similar to {@link #getTemplate(String, Locale, Object, String, boolean)} with {@code null} * {@code customLookupCondition}. * * @return {@link MaybeMissingTemplate#getTemplate()} of the * {@link #getTemplate(String, Locale, Object, String, boolean)} return value. * * @deprecated Use {@link #getTemplate(String, Locale, Object, String, boolean)}, which can return more detailed * result when the template is missing. */
@Deprecated public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL) throws IOException { return getTemplate(name, locale, null, encoding, parseAsFTL).getTemplate(); }
Returns the deprecated default template loader of FreeMarker 2.3.0.
Deprecated:The TemplateLoader should be always specified by the constructor caller.
/** * Returns the deprecated default template loader of FreeMarker 2.3.0. * * @deprecated The {@link TemplateLoader} should be always specified by the constructor caller. */
@Deprecated protected static TemplateLoader createLegacyDefaultTemplateLoader() { return _TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0); } private Template getTemplateInternal( final String name, final Locale locale, final Object customLookupCondition, final String encoding, final boolean parseAsFTL) throws IOException { final boolean debug = LOG.isDebugEnabled(); final String debugName = debug ? buildDebugName(name, locale, customLookupCondition, encoding, parseAsFTL) : null; final TemplateKey tk = new TemplateKey(name, locale, customLookupCondition, encoding, parseAsFTL); CachedTemplate cachedTemplate; if (isStorageConcurrent) { cachedTemplate = (CachedTemplate) storage.get(tk); } else { synchronized (storage) { cachedTemplate = (CachedTemplate) storage.get(tk); } } final long now = System.currentTimeMillis(); long lastModified = -1L; boolean rethrown = false; TemplateLookupResult newLookupResult = null; try { if (cachedTemplate != null) { // If we're within the refresh delay, return the cached copy if (now - cachedTemplate.lastChecked < updateDelay) { if (debug) { LOG.debug(debugName + " cached copy not yet stale; using cached."); } // Can be null, indicating a cached negative lookup Object t = cachedTemplate.templateOrException; if (t instanceof Template || t == null) { return (Template) t; } else if (t instanceof RuntimeException) { throwLoadFailedException((RuntimeException) t); } else if (t instanceof IOException) { rethrown = true; throwLoadFailedException((IOException) t); } throw new BugException("t is " + t.getClass().getName()); } // Clone as the instance bound to the map should be treated as // immutable to ensure proper concurrent semantics cachedTemplate = cachedTemplate.cloneCachedTemplate(); // Update the last-checked flag cachedTemplate.lastChecked = now; // Find the template source newLookupResult = lookupTemplate(name, locale, customLookupCondition); // Template source was removed if (!newLookupResult.isPositive()) { if (debug) { LOG.debug(debugName + " no source found."); } storeNegativeLookup(tk, cachedTemplate, null); return null; } // If the source didn't change and its last modified date // also didn't change, return the cached version. final Object newLookupResultSource = newLookupResult.getTemplateSource(); lastModified = templateLoader.getLastModified(newLookupResultSource); boolean lastModifiedNotChanged = lastModified == cachedTemplate.lastModified; boolean sourceEquals = newLookupResultSource.equals(cachedTemplate.source); if (lastModifiedNotChanged && sourceEquals) { if (debug) { LOG.debug(debugName + ": using cached since " + newLookupResultSource + " hasn't changed."); } storeCached(tk, cachedTemplate); return (Template) cachedTemplate.templateOrException; } else if (debug) { if (!sourceEquals) { LOG.debug("Updating source because: " + "sourceEquals=" + sourceEquals + ", newlyFoundSource=" + StringUtil.jQuoteNoXSS(newLookupResultSource) + ", cached.source=" + StringUtil.jQuoteNoXSS(cachedTemplate.source)); } else if (!lastModifiedNotChanged) { LOG.debug("Updating source because: " + "lastModifiedNotChanged=" + lastModifiedNotChanged + ", cached.lastModified=" + cachedTemplate.lastModified + " != source.lastModified=" + lastModified); } } } else { if (debug) { LOG.debug("Couldn't find template in cache for " + debugName + "; will try to load it."); } // Construct a new CachedTemplate entry. Note we set the // cachedTemplate.lastModified to Long.MIN_VALUE. This is // a flag that signs it has to be explicitly queried later on. cachedTemplate = new CachedTemplate(); cachedTemplate.lastChecked = now; newLookupResult = lookupTemplate(name, locale, customLookupCondition); if (!newLookupResult.isPositive()) { storeNegativeLookup(tk, cachedTemplate, null); return null; } cachedTemplate.lastModified = lastModified = Long.MIN_VALUE; } Object source = newLookupResult.getTemplateSource(); cachedTemplate.source = source; // If we get here, then we need to (re)load the template if (debug) { LOG.debug("Loading template for " + debugName + " from " + StringUtil.jQuoteNoXSS(source)); } lastModified = lastModified == Long.MIN_VALUE ? templateLoader.getLastModified(source) : lastModified; Template template = loadTemplate( templateLoader, source, name, newLookupResult.getTemplateSourceName(), locale, customLookupCondition, encoding, parseAsFTL); cachedTemplate.templateOrException = template; cachedTemplate.lastModified = lastModified; storeCached(tk, cachedTemplate); return template; } catch (RuntimeException e) { if (cachedTemplate != null) { storeNegativeLookup(tk, cachedTemplate, e); } throw e; } catch (IOException e) { if (!rethrown) { storeNegativeLookup(tk, cachedTemplate, e); } throw e; } finally { if (newLookupResult != null && newLookupResult.isPositive()) { templateLoader.closeTemplateSource(newLookupResult.getTemplateSource()); } } } private static final Method INIT_CAUSE = getInitCauseMethod(); private static final Method getInitCauseMethod() { try { return Throwable.class.getMethod("initCause", new Class[] { Throwable.class }); } catch (NoSuchMethodException e) { return null; } }
Creates an IOException that has a cause exception.
/** * Creates an {@link IOException} that has a cause exception. */
// [Java 6] Remove private IOException newIOException(String message, Throwable cause) { if (cause == null) { return new IOException(message); } IOException ioe; if (INIT_CAUSE != null) { ioe = new IOException(message); try { INIT_CAUSE.invoke(ioe, cause); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new UndeclaredThrowableException(ex); } } else { ioe = new IOException(message + "\nCaused by: " + cause.getClass().getName() + ": " + cause.getMessage()); } return ioe; } private void throwLoadFailedException(Throwable e) throws IOException { throw newIOException("There was an error loading the " + "template on an earlier attempt; see cause exception.", e); } private void storeNegativeLookup(TemplateKey tk, CachedTemplate cachedTemplate, Exception e) { cachedTemplate.templateOrException = e; cachedTemplate.source = null; cachedTemplate.lastModified = 0L; storeCached(tk, cachedTemplate); } private void storeCached(TemplateKey tk, CachedTemplate cachedTemplate) { if (isStorageConcurrent) { storage.put(tk, cachedTemplate); } else { synchronized (storage) { storage.put(tk, cachedTemplate); } } } private Template loadTemplate( final TemplateLoader templateLoader, final Object source, final String name, final String sourceName, Locale locale, final Object customLookupCondition, String initialEncoding, final boolean parseAsFTL) throws IOException { final TemplateConfiguration tc; try { tc = templateConfigurations != null ? templateConfigurations.get(sourceName, source) : null; } catch (TemplateConfigurationFactoryException e) { throw newIOException("Error while getting TemplateConfiguration; see cause exception.", e); } if (tc != null) { // TC.{encoding,locale} is stronger than the cfg.getTemplate arguments by design. if (tc.isEncodingSet()) { initialEncoding = tc.getEncoding(); } if (tc.isLocaleSet()) { locale = tc.getLocale(); } } Template template; { if (parseAsFTL) { try { final Reader reader = templateLoader.getReader(source, initialEncoding); try { template = new Template(name, sourceName, reader, config, tc, initialEncoding); } finally { reader.close(); } } catch (Template.WrongEncodingException wee) { String actualEncoding = wee.getTemplateSpecifiedEncoding(); if (LOG.isDebugEnabled()) { LOG.debug("Initial encoding \"" + initialEncoding + "\" was incorrect, re-reading with \"" + actualEncoding + "\". Template: " + sourceName); } final Reader reader = templateLoader.getReader(source, actualEncoding); try { template = new Template(name, sourceName, reader, config, tc, actualEncoding); } finally { reader.close(); } } } else { // Read the contents into a StringWriter, then construct a single-text-block template from it. final StringWriter sw = new StringWriter(); final char[] buf = new char[4096]; final Reader reader = templateLoader.getReader(source, initialEncoding); try { fetchChars: while (true) { int charsRead = reader.read(buf); if (charsRead > 0) { sw.write(buf, 0, charsRead); } else if (charsRead < 0) { break fetchChars; } } } finally { reader.close(); } template = Template.getPlainTextTemplate(name, sourceName, sw.toString(), config); template.setEncoding(initialEncoding); } } if (tc != null) { tc.apply(template); } template.setLocale(locale); template.setCustomLookupCondition(customLookupCondition); return template; }
Gets the delay in milliseconds between checking for newer versions of a template source.
Returns:the current value of the delay
/** * Gets the delay in milliseconds between checking for newer versions of a * template source. * @return the current value of the delay */
public long getDelay() { // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. synchronized (this) { return updateDelay; } }
Sets the delay in milliseconds between checking for newer versions of a template sources.
Params:
  • delay – the new value of the delay
/** * Sets the delay in milliseconds between checking for newer versions of a * template sources. * @param delay the new value of the delay */
public void setDelay(long delay) { // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. synchronized (this) { this.updateDelay = delay; } }
Returns if localized template lookup is enabled or not.
/** * Returns if localized template lookup is enabled or not. */
public boolean getLocalizedLookup() { // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. synchronized (this) { return localizedLookup; } }
Setis if localized template lookup is enabled or not.
/** * Setis if localized template lookup is enabled or not. */
public void setLocalizedLookup(boolean localizedLookup) { // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. synchronized (this) { if (this.localizedLookup != localizedLookup) { this.localizedLookup = localizedLookup; clear(); } } }
Removes all entries from the cache, forcing reloading of templates on subsequent getTemplate(String, Locale, String, boolean) calls. If the configured template loader is stateful, then its StatefulTemplateLoader.resetState() method is invoked as well.
/** * Removes all entries from the cache, forcing reloading of templates * on subsequent {@link #getTemplate(String, Locale, String, boolean)} * calls. If the configured template loader is * {@link StatefulTemplateLoader stateful}, then its * {@link StatefulTemplateLoader#resetState()} method is invoked as well. */
public void clear() { synchronized (storage) { storage.clear(); if (templateLoader instanceof StatefulTemplateLoader) { ((StatefulTemplateLoader) templateLoader).resetState(); } } }
Same as removeTemplate(String, Locale, Object, String, boolean) with null customLookupCondition.
/** * Same as {@link #removeTemplate(String, Locale, Object, String, boolean)} with {@code null} * {@code customLookupCondition}. */
public void removeTemplate( String name, Locale locale, String encoding, boolean parse) throws IOException { removeTemplate(name, locale, null, encoding, parse); }
Removes an entry from the cache, hence forcing the re-loading of it when it's next time requested. (It doesn't delete the template file itself.) This is to give the application finer control over cache updating than setDelay(long) alone does. For the meaning of the parameters, see Configuration.getTemplate(String, Locale, Object, String, boolean, boolean)
/** * Removes an entry from the cache, hence forcing the re-loading of it when it's next time requested. (It doesn't * delete the template file itself.) This is to give the application finer control over cache updating than * {@link #setDelay(long)} alone does. * * For the meaning of the parameters, see * {@link Configuration#getTemplate(String, Locale, Object, String, boolean, boolean)} */
public void removeTemplate( String name, Locale locale, Object customLookupCondition, String encoding, boolean parse) throws IOException { if (name == null) { throw new IllegalArgumentException("Argument \"name\" can't be null"); } if (locale == null) { throw new IllegalArgumentException("Argument \"locale\" can't be null"); } if (encoding == null) { throw new IllegalArgumentException("Argument \"encoding\" can't be null"); } name = templateNameFormat.normalizeRootBasedName(name); if (name != null && templateLoader != null) { boolean debug = LOG.isDebugEnabled(); String debugName = debug ? buildDebugName(name, locale, customLookupCondition, encoding, parse) : null; TemplateKey tk = new TemplateKey(name, locale, customLookupCondition, encoding, parse); if (isStorageConcurrent) { storage.remove(tk); } else { synchronized (storage) { storage.remove(tk); } } if (debug) { LOG.debug(debugName + " was removed from the cache, if it was there"); } } } private String buildDebugName(String name, Locale locale, Object customLookupCondition, String encoding, boolean parse) { return StringUtil.jQuoteNoXSS(name) + "(" + StringUtil.jQuoteNoXSS(locale) + (customLookupCondition != null ? ", cond=" + StringUtil.jQuoteNoXSS(customLookupCondition) : "") + ", " + encoding + (parse ? ", parsed)" : ", unparsed]"); }
Throws:
Deprecated:Use Environment.toFullTemplateName(String, String) instead, as that can throw MalformedTemplateNameException, and is on a more logical place anyway.
/** * @deprecated Use {@link Environment#toFullTemplateName(String, String)} instead, as that can throw * {@link MalformedTemplateNameException}, and is on a more logical place anyway. * * @throws IllegalArgumentException * If the {@code baseName} or {@code targetName} is malformed according the {@link TemplateNameFormat} * in use. */
@Deprecated public static String getFullTemplatePath(Environment env, String baseName, String targetName) { try { return env.toFullTemplateName(baseName, targetName); } catch (MalformedTemplateNameException e) { throw new IllegalArgumentException(e.getMessage()); } } private TemplateLookupResult lookupTemplate(String name, Locale locale, Object customLookupCondition) throws IOException { final TemplateLookupResult lookupResult = templateLookupStrategy.lookup( new TemplateCacheTemplateLookupContext(name, locale, customLookupCondition)); if (lookupResult == null) { throw new NullPointerException("Lookup result shouldn't be null"); } return lookupResult; } private TemplateLookupResult lookupTemplateWithAcquisitionStrategy(String path) throws IOException { int asterisk = path.indexOf(ASTERISK); // Shortcut in case there is no acquisition if (asterisk == -1) { return TemplateLookupResult.from(path, findTemplateSource(path)); } StringTokenizer tok = new StringTokenizer(path, "/"); int lastAsterisk = -1; List tokpath = new ArrayList(); while (tok.hasMoreTokens()) { String pathToken = tok.nextToken(); if (pathToken.equals(ASTERISKSTR)) { if (lastAsterisk != -1) { tokpath.remove(lastAsterisk); } lastAsterisk = tokpath.size(); } tokpath.add(pathToken); } if (lastAsterisk == -1) { // if there was no real "*" step after all return TemplateLookupResult.from(path, findTemplateSource(path)); } String basePath = concatPath(tokpath, 0, lastAsterisk); String resourcePath = concatPath(tokpath, lastAsterisk + 1, tokpath.size()); if (resourcePath.endsWith("/")) { resourcePath = resourcePath.substring(0, resourcePath.length() - 1); } StringBuilder buf = new StringBuilder(path.length()).append(basePath); int l = basePath.length(); for (; ; ) { String fullPath = buf.append(resourcePath).toString(); Object templateSource = findTemplateSource(fullPath); if (templateSource != null) { return TemplateLookupResult.from(fullPath, templateSource); } if (l == 0) { return TemplateLookupResult.createNegativeResult(); } l = basePath.lastIndexOf(SLASH, l - 2) + 1; buf.setLength(l); } } private Object findTemplateSource(String path) throws IOException { final Object result = templateLoader.findTemplateSource(path); if (LOG.isDebugEnabled()) { LOG.debug("TemplateLoader.findTemplateSource(" + StringUtil.jQuote(path) + "): " + (result == null ? "Not found" : "Found")); } return modifyForConfIcI(result); }
If IcI >= 2.3.21, sets URLTemplateSource.setUseCaches(boolean) to false for sources that come from a TemplateLoader where URLConnection cache usage wasn't set explicitly.
/** * If IcI >= 2.3.21, sets {@link URLTemplateSource#setUseCaches(boolean)} to {@code false} for sources that come * from a {@link TemplateLoader} where {@link URLConnection} cache usage wasn't set explicitly. */
private Object modifyForConfIcI(Object templateSource) { if (templateSource == null) return null; if (config.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_21) { return templateSource; } if (templateSource instanceof URLTemplateSource) { URLTemplateSource urlTemplateSource = (URLTemplateSource) templateSource; if (urlTemplateSource.getUseCaches() == null) { // It was left unset urlTemplateSource.setUseCaches(false); } } else if (templateSource instanceof MultiSource) { modifyForConfIcI(((MultiSource) templateSource).getWrappedSource()); } return templateSource; } private String concatPath(List path, int from, int to) { StringBuilder buf = new StringBuilder((to - from) * 16); for (int i = from; i < to; ++i) { buf.append(path.get(i)).append('/'); } return buf.toString(); }
This class holds a (name, locale) pair and is used as the key in the cached templates map.
/** * This class holds a (name, locale) pair and is used as the key in * the cached templates map. */
private static final class TemplateKey { private final String name; private final Locale locale; private final Object customLookupCondition; private final String encoding; private final boolean parse; TemplateKey(String name, Locale locale, Object customLookupCondition, String encoding, boolean parse) { this.name = name; this.locale = locale; this.customLookupCondition = customLookupCondition; this.encoding = encoding; this.parse = parse; } @Override public boolean equals(Object o) { if (o instanceof TemplateKey) { TemplateKey tk = (TemplateKey) o; return parse == tk.parse && name.equals(tk.name) && locale.equals(tk.locale) && nullSafeEquals(customLookupCondition, tk.customLookupCondition) && encoding.equals(tk.encoding); } return false; } private boolean nullSafeEquals(Object o1, Object o2) { return o1 != null ? (o2 != null ? o1.equals(o2) : false) : o2 == null; } @Override public int hashCode() { return name.hashCode() ^ locale.hashCode() ^ encoding.hashCode() ^ (customLookupCondition != null ? customLookupCondition.hashCode() : 0) ^ Boolean.valueOf(!parse).hashCode(); } }
This class holds the cached template and associated information (the source object, and the last-checked and last-modified timestamps). It is used as the value in the cached templates map. Note: this class is Serializable to allow custom 3rd party CacheStorage implementations to serialize/replicate them (see tracker issue #1926150); FreeMarker code itself doesn't rely on its serializability.
/** * This class holds the cached template and associated information * (the source object, and the last-checked and last-modified timestamps). * It is used as the value in the cached templates map. Note: this class * is Serializable to allow custom 3rd party CacheStorage implementations * to serialize/replicate them (see tracker issue #1926150); FreeMarker * code itself doesn't rely on its serializability. */
private static final class CachedTemplate implements Cloneable, Serializable { private static final long serialVersionUID = 1L; Object templateOrException; Object source; long lastChecked; long lastModified; public CachedTemplate cloneCachedTemplate() { try { return (CachedTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new UndeclaredThrowableException(e); } } } private class TemplateCacheTemplateLookupContext extends TemplateLookupContext { TemplateCacheTemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) { super(templateName, localizedLookup ? templateLocale : null, customLookupCondition); } @Override public TemplateLookupResult lookupWithAcquisitionStrategy(String name) throws IOException { // Only one of the possible ways of making a name non-normalized, but is the easiest mistake to do: if (name.startsWith("/")) { throw new IllegalArgumentException("Non-normalized name, starts with \"/\": " + name); } return TemplateCache.this.lookupTemplateWithAcquisitionStrategy(name); } @Override public TemplateLookupResult lookupWithLocalizedThenAcquisitionStrategy(final String templateName, final Locale templateLocale) throws IOException { if (templateLocale == null) { return lookupWithAcquisitionStrategy(templateName); } int lastDot = templateName.lastIndexOf('.'); String prefix = lastDot == -1 ? templateName : templateName.substring(0, lastDot); String suffix = lastDot == -1 ? "" : templateName.substring(lastDot); String localeName = LOCALE_PART_SEPARATOR + templateLocale.toString(); StringBuilder buf = new StringBuilder(templateName.length() + localeName.length()); buf.append(prefix); tryLocaleNameVariations: while (true) { buf.setLength(prefix.length()); String path = buf.append(localeName).append(suffix).toString(); TemplateLookupResult lookupResult = lookupWithAcquisitionStrategy(path); if (lookupResult.isPositive()) { return lookupResult; } int lastUnderscore = localeName.lastIndexOf('_'); if (lastUnderscore == -1) { break tryLocaleNameVariations; } localeName = localeName.substring(0, lastUnderscore); } return createNegativeLookupResult(); } }
Since:2.3.22
/** * Used for the return value of {@link TemplateCache#getTemplate(String, Locale, Object, String, boolean)}. * * @since 2.3.22 */
public final static class MaybeMissingTemplate { private final Template template; private final String missingTemplateNormalizedName; private final String missingTemplateReason; private final MalformedTemplateNameException missingTemplateCauseException; private MaybeMissingTemplate(Template template) { this.template = template; this.missingTemplateNormalizedName = null; this.missingTemplateReason = null; this.missingTemplateCauseException = null; } private MaybeMissingTemplate(String normalizedName, MalformedTemplateNameException missingTemplateCauseException) { this.template = null; this.missingTemplateNormalizedName = normalizedName; this.missingTemplateReason = null; this.missingTemplateCauseException = missingTemplateCauseException; } private MaybeMissingTemplate(String normalizedName, String missingTemplateReason) { this.template = null; this.missingTemplateNormalizedName = normalizedName; this.missingTemplateReason = missingTemplateReason; this.missingTemplateCauseException = null; }
The Template if it wasn't missing, otherwise null.
/** * The {@link Template} if it wasn't missing, otherwise {@code null}. */
public Template getTemplate() { return template; }
When the template was missing, this possibly contains the explanation, or null. If the template wasn't missing (i.e., when getTemplate() return non-null) this is always null.
/** * When the template was missing, this <em>possibly</em> contains the explanation, or {@code null}. If the * template wasn't missing (i.e., when {@link #getTemplate()} return non-{@code null}) this is always * {@code null}. */
public String getMissingTemplateReason() { return missingTemplateReason != null ? missingTemplateReason : (missingTemplateCauseException != null ? missingTemplateCauseException.getMalformednessDescription() : null); }
When the template was missing, this possibly contains its normalized name. If the template wasn't missing (i.e., when getTemplate() return non-null) this is always null. When the template is missing, it will be null for example if the normalization itself was unsuccessful.
/** * When the template was missing, this <em>possibly</em> contains its normalized name. If the template wasn't * missing (i.e., when {@link #getTemplate()} return non-{@code null}) this is always {@code null}. When the * template is missing, it will be {@code null} for example if the normalization itself was unsuccessful. */
public String getMissingTemplateNormalizedName() { return missingTemplateNormalizedName; } } }