http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolver.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolver.java deleted file mode 100644 index e6b9ea1..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolver.java +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * 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 org.apache.freemarker.core.templateresolver; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.StringTokenizer; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.Template; -import org.apache.freemarker.core.TemplateNotFoundException; -import org.apache.freemarker.core._CoreLogs; -import org.apache.freemarker.core._TemplateAPI; -import org.apache.freemarker.core.ast.BugException; -import org.apache.freemarker.core.ast.MarkReleaserTemplateSpecifiedEncodingHandler; -import org.apache.freemarker.core.ast.TemplateConfiguration; -import org.apache.freemarker.core.ast.TemplateSpecifiedEncodingHandler; -import org.apache.freemarker.core.util.NullArgumentException; -import org.apache.freemarker.core.util.StringUtil; -import org.apache.freemarker.core.util.UndeclaredThrowableException; -import org.slf4j.Logger; - -/** - * 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 DefaultTemplateResolver extends TemplateResolver { - - /** - * 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 = _CoreLogs.TEMPLATE_RESOLVER; - - /** Maybe {@code null}. */ - private final TemplateLoader templateLoader; - - /** Here we keep our cached templates */ - private final CacheStorage cacheStorage; - private final TemplateLookupStrategy templateLookupStrategy; - private final TemplateNameFormat templateNameFormat; - private final TemplateConfigurationFactory templateConfigurations; - - private final boolean isCacheStorageConcurrent; - /** {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} */ - private long templateUpdateDelayMilliseconds = DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS; - /** {@link Configuration#setLocalizedLookup(boolean)} */ - private boolean localizedLookup = true; - - private Configuration config; - - /** - * Same as {@link #DefaultTemplateResolver(TemplateLoader, CacheStorage, Configuration)} with a new {@link SoftCacheStorage} - * as the 2nd parameter. - * - * @since 2.3.21 - */ - public DefaultTemplateResolver(TemplateLoader templateLoader, Configuration config) { - this(templateLoader, _TemplateAPI.createDefaultCacheStorage(Configuration.VERSION_2_3_0), config); - } - - /** - * Same as - * {@link #DefaultTemplateResolver(TemplateLoader, CacheStorage, TemplateLookupStrategy, TemplateNameFormat, Configuration)} - * with {@link TemplateLookupStrategy#DEFAULT_2_3_0} and {@link TemplateNameFormat#DEFAULT_2_3_0}. - * - * @since 2.3.21 - */ - public DefaultTemplateResolver(TemplateLoader templateLoader, CacheStorage cacheStorage, Configuration config) { - this(templateLoader, cacheStorage, - _TemplateAPI.getDefaultTemplateLookupStrategy(Configuration.VERSION_2_3_0), - _TemplateAPI.getDefaultTemplateNameFormat(Configuration.VERSION_2_3_0), - config); - } - - /** - * Same as - * {@link #DefaultTemplateResolver(TemplateLoader, CacheStorage, TemplateLookupStrategy, TemplateNameFormat, - * TemplateConfigurationFactory, Configuration)} with {@code null} for {@code templateConfigurations}-s. - * - * @since 2.3.22 - */ - public DefaultTemplateResolver(TemplateLoader templateLoader, CacheStorage cacheStorage, - TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat, - Configuration config) { - this(templateLoader, cacheStorage, templateLookupStrategy, templateNameFormat, null, config); - } - - /** - * @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, except in the very rare case where a - * {@link TemplateLoader} itself specifies a {@link TemplateConfiguration}). - * @param config - * The {@link Configuration} this cache will be used for. Can't be {@code null}. - * - * @since 2.3.24 - */ - public DefaultTemplateResolver(TemplateLoader templateLoader, CacheStorage cacheStorage, - TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat, - TemplateConfigurationFactory templateConfigurations, - Configuration config) { - super(config); - - this.templateLoader = templateLoader; - - NullArgumentException.check("cacheStorage", cacheStorage); - this.cacheStorage = cacheStorage; - isCacheStorageConcurrent = 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; - - NullArgumentException.check("config", config); - this.config = config; - } - - /** - * Returns the configuration for internal usage. - */ - @Override - public Configuration getConfiguration() { - return config; - } - - public TemplateLoader getTemplateLoader() { - return templateLoader; - } - - public CacheStorage getCacheStorage() { - return cacheStorage; - } - - /** - * @since 2.3.22 - */ - public TemplateLookupStrategy getTemplateLookupStrategy() { - return templateLookupStrategy; - } - - /** - * @since 2.3.22 - */ - public TemplateNameFormat getTemplateNameFormat() { - return templateNameFormat; - } - - /** - * @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. - * - * <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 GetTemplateResult} object that contains the {@link Template}, or a - * {@link GetTemplateResult} 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 GetTemplateResult}. 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 GetTemplateResult} will be returned, similarly as if the template were missing - * (the {@link GetTemplateResult#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 - */ - @Override - public GetTemplateResult 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 GetTemplateResult(null, e); - } - - if (templateLoader == null) { - return new GetTemplateResult(name, "The TemplateLoader (and TemplateLoader2) was null."); - } - - Template template = getTemplateInternal(name, locale, customLookupCondition, encoding, parseAsFTL); - return template != null ? new GetTemplateResult(template) : new GetTemplateResult(name, (String) null); - } - - 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 debugPrefix = debug - ? getDebugPrefix("getTemplate", name, locale, customLookupCondition, encoding, parseAsFTL) - : null; - final CachedResultKey cacheKey = new CachedResultKey(name, locale, customLookupCondition, encoding, parseAsFTL); - - CachedResult oldCachedResult; - if (isCacheStorageConcurrent) { - oldCachedResult = (CachedResult) cacheStorage.get(cacheKey); - } else { - synchronized (cacheStorage) { - oldCachedResult = (CachedResult) cacheStorage.get(cacheKey); - } - } - - final long now = System.currentTimeMillis(); - - boolean rethrownCachedException = false; - boolean suppressFinallyException = false; - TemplateLookupResult newLookupResult = null; - CachedResult newCachedResult = null; - TemplateLoaderSession session = null; - try { - if (oldCachedResult != null) { - // If we're within the refresh delay, return the cached result - if (now - oldCachedResult.lastChecked < templateUpdateDelayMilliseconds) { - if (debug) { - LOG.debug(debugPrefix + "Cached copy not yet stale; using cached."); - } - Object t = oldCachedResult.templateOrException; - // t can be null, indicating a cached negative lookup - if (t instanceof Template || t == null) { - return (Template) t; - } else if (t instanceof RuntimeException) { - rethrowCachedException((RuntimeException) t); - } else if (t instanceof IOException) { - rethrownCachedException = true; - rethrowCachedException((IOException) t); - } - throw new BugException("Unhandled class for t: " + t.getClass().getName()); - } - // The freshness of the cache result must be checked. - - // Clone, as the instance in the cache store must not be modified to ensure proper concurrent behavior. - newCachedResult = oldCachedResult.clone(); - newCachedResult.lastChecked = now; - - session = templateLoader.createSession(); - if (debug && session != null) { - LOG.debug(debugPrefix + "Session created."); - } - - // Find the template source, load it if it doesn't correspond to the cached result. - newLookupResult = lookupAndLoadTemplateIfChanged( - name, locale, customLookupCondition, oldCachedResult.source, oldCachedResult.version, session); - - // Template source was removed (TemplateLoader2ResultStatus.NOT_FOUND, or no TemplateLoader2Result) - if (!newLookupResult.isPositive()) { - if (debug) { - LOG.debug(debugPrefix + "No source found."); - } - setToNegativeAndPutIntoCache(cacheKey, newCachedResult, null); - return null; - } - - final TemplateLoadingResult newTemplateLoaderResult = newLookupResult.getTemplateLoaderResult(); - if (newTemplateLoaderResult.getStatus() == TemplateLoadingResultStatus.NOT_MODIFIED) { - // Return the cached version. - if (debug) { - LOG.debug(debugPrefix + ": Using cached template " - + "(source: " + newTemplateLoaderResult.getSource() + ")" - + " as it hasn't been changed on the backing store."); - } - putIntoCache(cacheKey, newCachedResult); - return (Template) newCachedResult.templateOrException; - } else { - if (newTemplateLoaderResult.getStatus() != TemplateLoadingResultStatus.OPENED) { - // TemplateLoader2ResultStatus.NOT_FOUND was already handler earlier - throw new BugException("Unxpected status: " + newTemplateLoaderResult.getStatus()); - } - if (debug) { - StringBuilder debugMsg = new StringBuilder(); - debugMsg.append(debugPrefix) - .append("Reloading template instead of using the cached result because "); - if (newCachedResult.templateOrException instanceof Throwable) { - debugMsg.append("it's a cached error (retrying)."); - } else { - Object newSource = newTemplateLoaderResult.getSource(); - if (!nullSafeEquals(newSource, oldCachedResult.source)) { - debugMsg.append("the source has been changed: ") - .append("cached.source=").append(StringUtil.jQuoteNoXSS(oldCachedResult.source)) - .append(", current.source=").append(StringUtil.jQuoteNoXSS(newSource)); - } else { - Serializable newVersion = newTemplateLoaderResult.getVersion(); - if (!nullSafeEquals(oldCachedResult.version, newVersion)) { - debugMsg.append("the version has been changed: ") - .append("cached.version=").append(oldCachedResult.version) - .append(", current.version=").append(newVersion); - } else { - debugMsg.append("??? (unknown reason)"); - } - } - } - LOG.debug(debugMsg.toString()); - } - } - } else { // if there was no cached result - if (debug) { - LOG.debug(debugPrefix + "No cached result was found; will try to load template."); - } - - newCachedResult = new CachedResult(); - newCachedResult.lastChecked = now; - - session = templateLoader.createSession(); - if (debug && session != null) { - LOG.debug(debugPrefix + "Session created."); - } - - newLookupResult = lookupAndLoadTemplateIfChanged( - name, locale, customLookupCondition, null, null, session); - - if (!newLookupResult.isPositive()) { - setToNegativeAndPutIntoCache(cacheKey, newCachedResult, null); - return null; - } - } - // We have newCachedResult and newLookupResult initialized at this point. - - TemplateLoadingResult templateLoaderResult = newLookupResult.getTemplateLoaderResult(); - newCachedResult.source = templateLoaderResult.getSource(); - - // If we get here, then we need to (re)load the template - if (debug) { - LOG.debug(debugPrefix + "Reading template content (source: " - + StringUtil.jQuoteNoXSS(newCachedResult.source) + ")"); - } - - Template template = loadTemplate( - templateLoaderResult, - name, newLookupResult.getTemplateSourceName(), locale, customLookupCondition, - encoding, parseAsFTL); - if (session != null) { - session.close(); - if (debug) { - LOG.debug(debugPrefix + "Session closed."); - } - } - newCachedResult.templateOrException = template; - newCachedResult.version = templateLoaderResult.getVersion(); - putIntoCache(cacheKey, newCachedResult); - return template; - } catch (RuntimeException e) { - if (newCachedResult != null) { - setToNegativeAndPutIntoCache(cacheKey, newCachedResult, e); - } - suppressFinallyException = true; - throw e; - } catch (IOException e) { - // Rethrown cached exceptions are wrapped into IOException-s, so we only need this condition here. - if (!rethrownCachedException) { - setToNegativeAndPutIntoCache(cacheKey, newCachedResult, e); - } - suppressFinallyException = true; - throw e; - } finally { - try { - // Close streams first: - - if (newLookupResult != null && newLookupResult.isPositive()) { - TemplateLoadingResult templateLoaderResult = newLookupResult.getTemplateLoaderResult(); - Reader reader = templateLoaderResult.getReader(); - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { // [FM3] Exception e - if (suppressFinallyException) { - if (LOG.isWarnEnabled()) { - LOG.warn("Failed to close template content Reader for: " + name, e); - } - } else { - suppressFinallyException = true; - throw e; - } - } - } else if (templateLoaderResult.getInputStream() != null) { - try { - templateLoaderResult.getInputStream().close(); - } catch (IOException e) { // [FM3] Exception e - if (suppressFinallyException) { - if (LOG.isWarnEnabled()) { - LOG.warn("Failed to close template content InputStream for: " + name, e); - } - } else { - suppressFinallyException = true; - throw e; - } - } - } - } - } finally { - // Then close streams: - - if (session != null && !session.isClosed()) { - try { - session.close(); - if (debug) { - LOG.debug(debugPrefix + "Session closed."); - } - } catch (IOException e) { // [FM3] Exception e - if (suppressFinallyException) { - if (LOG.isWarnEnabled()) { - LOG.warn("Failed to close template loader session for" + name, e); - } - } else { - suppressFinallyException = true; - throw e; - } - } - } - } - } - } - - - - 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 {@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 rethrowCachedException(Throwable e) throws IOException { - throw newIOException("There was an error loading the " + - "template on an earlier attempt; see cause exception.", e); - } - - private void setToNegativeAndPutIntoCache(CachedResultKey cacheKey, CachedResult cachedResult, Exception e) { - cachedResult.templateOrException = e; - cachedResult.source = null; - cachedResult.version = null; - putIntoCache(cacheKey, cachedResult); - } - - private void putIntoCache(CachedResultKey tk, CachedResult cachedTemplate) { - if (isCacheStorageConcurrent) { - cacheStorage.put(tk, cachedTemplate); - } else { - synchronized (cacheStorage) { - cacheStorage.put(tk, cachedTemplate); - } - } - } - - @SuppressWarnings("deprecation") - private Template loadTemplate( - TemplateLoadingResult templateLoaderResult, - final String name, final String sourceName, Locale locale, final Object customLookupCondition, - String initialEncoding, final boolean parseAsFTL) throws IOException { - TemplateConfiguration tc; - { - TemplateConfiguration cfgTC; - try { - cfgTC = templateConfigurations != null - ? templateConfigurations.get(sourceName, templateLoaderResult.getSource()) : null; - } catch (TemplateConfigurationFactoryException e) { - throw newIOException("Error while getting TemplateConfiguration; see cause exception.", e); - } - TemplateConfiguration resultTC = templateLoaderResult.getTemplateConfiguration(); - if (resultTC != null) { - TemplateConfiguration mergedTC = new TemplateConfiguration(); - if (cfgTC != null) { - mergedTC.merge(cfgTC); - } - if (resultTC != null) { - mergedTC.merge(resultTC); - } - mergedTC.setParentConfiguration(config); - - tc = mergedTC; - } else { - tc = cfgTC; - } - } - - 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; - { - Reader reader = templateLoaderResult.getReader(); - InputStream inputStream = templateLoaderResult.getInputStream(); - TemplateSpecifiedEncodingHandler templateSpecifiedEncodingHandler; - if (reader != null) { - if (inputStream != null) { - throw new IllegalStateException("For a(n) " + templateLoaderResult.getClass().getName() - + ", both getReader() and getInputStream() has returned non-null."); - } - initialEncoding = null; // No charset decoding has happened - templateSpecifiedEncodingHandler = TemplateSpecifiedEncodingHandler.DEFAULT; - } else if (inputStream != null) { - if (parseAsFTL) { - // We need mark support, to restart if the charset suggested by <#ftl encoding=...> differs - // from that we use initially. - if (!inputStream.markSupported()) { - inputStream = new BufferedInputStream(inputStream); - } - inputStream.mark(Integer.MAX_VALUE); // Mark is released after the 1st FTL tag - templateSpecifiedEncodingHandler = new MarkReleaserTemplateSpecifiedEncodingHandler(inputStream); - } else { - templateSpecifiedEncodingHandler = null; - } - // Regarding buffering worries: On the Reader side we should only read in chunks (like through a - // BufferedReader), so there shouldn't be a problem if the InputStream is not buffered. (Also, at least - // on Oracle JDK and OpenJDK 7 the InputStreamReader itself has an internal ~8K buffer.) - reader = new InputStreamReader(inputStream, initialEncoding); - } else { - throw new IllegalStateException("For a(n) " + templateLoaderResult.getClass().getName() - + ", both getReader() and getInputStream() has returned null."); - } - - try { - if (parseAsFTL) { - try { - template = new Template(name, sourceName, reader, config, tc, - initialEncoding, templateSpecifiedEncodingHandler); - } catch (Template.WrongEncodingException wee) { - final String templateSpecifiedEncoding = wee.getTemplateSpecifiedEncoding(); - - if (inputStream != null) { - // We restart InputStream to re-decode it with the new charset. - inputStream.reset(); - - // Don't close `reader`; it's an InputStreamReader that would close the wrapped InputStream. - reader = new InputStreamReader(inputStream, templateSpecifiedEncoding); - } else { - // Should be impossible to get here - throw new BugException(); - } - - template = new Template(name, sourceName, reader, config, tc, - templateSpecifiedEncoding, templateSpecifiedEncodingHandler); - } - } else { - // Read the contents into a StringWriter, then construct a single-text-block template from it. - final StringBuilder sb = new StringBuilder(); - final char[] buf = new char[4096]; - int charsRead; - while ((charsRead = reader.read(buf)) > 0) { - sb.append(buf, 0, charsRead); - } - template = Template.getPlainTextTemplate(name, sourceName, sb.toString(), config); - template.setEncoding(initialEncoding); - } - } finally { - reader.close(); - } - } - - 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. - * @return the current value of the delay - */ - public long getTemplateUpdateDelayMilliseconds() { - // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. - synchronized (this) { - return templateUpdateDelayMilliseconds; - } - } - - /** - * Sets the delay in milliseconds between checking for newer versions of a - * template sources. - * @param templateUpdateDelayMilliseconds the new value of the delay - */ - public void setTemplateUpdateDelayMilliseconds(long templateUpdateDelayMilliseconds) { - // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not. - synchronized (this) { - this.templateUpdateDelayMilliseconds = templateUpdateDelayMilliseconds; - } - } - - /** - * 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. - */ - 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; - clearTemplateCache(); - } - } - } - - /** - * Removes all entries from the cache, forcing reloading of templates on subsequent - * {@link #getTemplate(String, Locale, Object, String, boolean)} calls. - * - * @param resetTemplateLoader - * Whether to call {@link TemplateLoader#resetState()}. on the template loader. - */ - public void clearTemplateCache(boolean resetTemplateLoader) { - synchronized (cacheStorage) { - cacheStorage.clear(); - if (templateLoader != null && resetTemplateLoader) { - templateLoader.resetState(); - } - } - } - - /** - * Same as {@link #clearTemplateCache(boolean)} with {@code true} {@code resetTemplateLoader} argument. - */ - @Override - public void clearTemplateCache() { - synchronized (cacheStorage) { - cacheStorage.clear(); - if (templateLoader != null) { - templateLoader.resetState(); - } - } - } - - /** - * Same as {@link #removeTemplateFromCache(String, Locale, Object, String, boolean)} with {@code null} - * {@code customLookupCondition}. - */ - @Override - public void removeTemplateFromCache( - String name, Locale locale, String encoding, boolean parse) throws IOException { - removeTemplateFromCache(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 - * {@link #setTemplateUpdateDelayMilliseconds(long)} alone does. - * - * For the meaning of the parameters, see - * {@link Configuration#getTemplate(String, Locale, Object, String, boolean, boolean)} - */ - public void removeTemplateFromCache( - 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 debugPrefix = debug - ? getDebugPrefix("removeTemplate", name, locale, customLookupCondition, encoding, parse) - : null; - CachedResultKey tk = new CachedResultKey(name, locale, customLookupCondition, encoding, parse); - - if (isCacheStorageConcurrent) { - cacheStorage.remove(tk); - } else { - synchronized (cacheStorage) { - cacheStorage.remove(tk); - } - } - if (debug) { - LOG.debug(debugPrefix + "Template was removed from the cache, if it was there"); - } - } - } - - private String getDebugPrefix(String operation, String name, Locale locale, Object customLookupCondition, String encoding, - boolean parse) { - return operation + " " + StringUtil.jQuoteNoXSS(name) + "(" - + StringUtil.jQuoteNoXSS(locale) - + (customLookupCondition != null ? ", cond=" + StringUtil.jQuoteNoXSS(customLookupCondition) : "") - + ", " + encoding - + (parse ? ", parsed)" : ", unparsed]") - + ": "; - } - - /** - * Looks up according the {@link TemplateLookupStrategy} and then starts reading the template, if it was changed - * compared to the cached result, or if there was no cached result yet. - */ - private TemplateLookupResult lookupAndLoadTemplateIfChanged( - String name, Locale locale, Object customLookupCondition, - TemplateLoadingSource cachedResultSource, Serializable cachedResultVersion, - TemplateLoaderSession session) throws IOException { - final TemplateLookupResult lookupResult = templateLookupStrategy.lookup( - new DefaultTemplateResolverTemplateLookupContext( - name, locale, customLookupCondition, - cachedResultSource, cachedResultVersion, - session)); - if (lookupResult == null) { - throw new NullPointerException("Lookup result shouldn't be null"); - } - return lookupResult; - } - - private String concatPath(List<String> pathSteps, int from, int to) { - StringBuilder buf = new StringBuilder((to - from) * 16); - for (int i = from; i < to; ++i) { - buf.append(pathSteps.get(i)); - if (i < pathSteps.size() - 1) { - buf.append('/'); - } - } - return buf.toString(); - } - - // Replace with Objects.equals in Java 7 - private static boolean nullSafeEquals(Object o1, Object o2) { - if (o1 == o2) return true; - if (o1 == null || o2 == null) return false; - return o1.equals(o2); - } - - /** - * Used as cache key to look up a {@link CachedResult}. - */ - @SuppressWarnings("serial") - private static final class CachedResultKey implements Serializable { - private final String name; - private final Locale locale; - private final Object customLookupCondition; - private final String encoding; - private final boolean parse; - - CachedResultKey(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 CachedResultKey) { - CachedResultKey tk = (CachedResultKey) o; - return - parse == tk.parse && - name.equals(tk.name) && - locale.equals(tk.locale) && - nullSafeEquals(customLookupCondition, tk.customLookupCondition) && - encoding.equals(tk.encoding); - } - return false; - } - - @Override - public int hashCode() { - return - name.hashCode() ^ - locale.hashCode() ^ - encoding.hashCode() ^ - (customLookupCondition != null ? customLookupCondition.hashCode() : 0) ^ - Boolean.valueOf(!parse).hashCode(); - } - } - - /** - * Hold the a cached {@link #getTemplate(String, Locale, Object, String, boolean)} result and the associated - * information needed to check if the cached value is up to date. - * - * <p> - * Note: this class is Serializable to allow custom 3rd party CacheStorage implementations to serialize/replicate - * them; FreeMarker code itself doesn't rely on its serializability. - * - * @see CachedResultKey - */ - private static final class CachedResult implements Cloneable, Serializable { - private static final long serialVersionUID = 1L; - - Object templateOrException; - TemplateLoadingSource source; - Serializable version; - long lastChecked; - - @Override - public CachedResult clone() { - try { - return (CachedResult) super.clone(); - } catch (CloneNotSupportedException e) { - throw new UndeclaredThrowableException(e); - } - } - } - - private class DefaultTemplateResolverTemplateLookupContext extends TemplateLookupContext { - - private final TemplateLoaderSession session; - - DefaultTemplateResolverTemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition, - TemplateLoadingSource cachedResultSource, Serializable cachedResultVersion, - TemplateLoaderSession session) { - super(templateName, localizedLookup ? templateLocale : null, customLookupCondition, - cachedResultSource, cachedResultVersion); - this.session = session; - } - - @Override - public TemplateLookupResult lookupWithAcquisitionStrategy(String path) throws IOException { - // Only one of the possible ways of making a name non-normalized, but is the easiest mistake to do: - if (path.startsWith("/")) { - throw new IllegalArgumentException("Non-normalized name, starts with \"/\": " + path); - } - - int asterisk = path.indexOf(ASTERISK); - // Shortcut in case there is no acquisition - if (asterisk == -1) { - return TemplateLookupResult.from( - path, - templateLoader.load(path, getCachedResultSource(), getCachedResultVersion(), session)); - } - StringTokenizer pathTokenizer = new StringTokenizer(path, "/"); - int lastAsterisk = -1; - List<String> pathSteps = new ArrayList<String>(); - while (pathTokenizer.hasMoreTokens()) { - String pathStep = pathTokenizer.nextToken(); - if (pathStep.equals(ASTERISKSTR)) { - if (lastAsterisk != -1) { - pathSteps.remove(lastAsterisk); - } - lastAsterisk = pathSteps.size(); - } - pathSteps.add(pathStep); - } - if (lastAsterisk == -1) { // if there was no real "*" step after all - return TemplateLookupResult.from( - path, - templateLoader.load(path, getCachedResultSource(), getCachedResultVersion(), session)); - } - String basePath = concatPath(pathSteps, 0, lastAsterisk); - String postAsteriskPath = concatPath(pathSteps, lastAsterisk + 1, pathSteps.size()); - StringBuilder buf = new StringBuilder(path.length()).append(basePath); - int basePathLen = basePath.length(); - while (true) { - String fullPath = buf.append(postAsteriskPath).toString(); - TemplateLoadingResult templateLoaderResult = templateLoader.load( - fullPath, getCachedResultSource(), getCachedResultVersion(), session); - if (templateLoaderResult.getStatus() == TemplateLoadingResultStatus.OPENED) { - return TemplateLookupResult.from(fullPath, templateLoaderResult); - } - if (basePathLen == 0) { - return TemplateLookupResult.createNegativeResult(); - } - basePathLen = basePath.lastIndexOf(SLASH, basePathLen - 2) + 1; - buf.setLength(basePathLen); - } - } - - @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(); - } - - } - - @Override - public String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException { - return templateNameFormat.toRootBasedName(baseName, targetName); - } - - @Override - public String normalizeRootBasedName(String name) throws MalformedTemplateNameException { - return templateNameFormat.normalizeRootBasedName(name); - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/FileTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/FileTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/FileTemplateLoader.java deleted file mode 100644 index 2dacb2d..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/FileTemplateLoader.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * 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 org.apache.freemarker.core.templateresolver; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Objects; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core._CoreLogs; -import org.apache.freemarker.core.util.SecurityUtilities; -import org.apache.freemarker.core.util.StringUtil; -import org.slf4j.Logger; - -/** - * A {@link TemplateLoader} that uses files inside a specified directory as the source of templates. By default it does - * security checks on the <em>canonical</em> path that will prevent it serving templates outside that specified - * directory. If you want symbolic links that point outside the template directory to work, you need to disable this - * feature by using {@link #FileTemplateLoader(File, boolean)} with {@code true} second argument, but before that, - * check the security implications there! - */ -public class FileTemplateLoader implements TemplateLoader { - - /** - * By setting this Java system property to {@code true}, you can change the default of - * {@code #getEmulateCaseSensitiveFileSystem()}. - */ - public static String SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM - = "org.freemarker.emulateCaseSensitiveFileSystem"; - private static final boolean EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT; - static { - final String s = SecurityUtilities.getSystemProperty(SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM, - "false"); - boolean emuCaseSensFS; - try { - emuCaseSensFS = StringUtil.getYesNo(s); - } catch (Exception e) { - emuCaseSensFS = false; - } - EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT = emuCaseSensFS; - } - - private static final int CASE_CHECH_CACHE_HARD_SIZE = 50; - private static final int CASE_CHECK_CACHE__SOFT_SIZE = 1000; - private static final boolean SEP_IS_SLASH = File.separatorChar == '/'; - - private static final Logger LOG = _CoreLogs.TEMPLATE_RESOLVER; - - public final File baseDir; - private final String canonicalBasePath; - private boolean emulateCaseSensitiveFileSystem; - private MruCacheStorage correctCasePaths; - - /** - * Creates a new file template loader that will use the specified directory - * as the base directory for loading templates. It will not allow access to - * template files that are accessible through symlinks that point outside - * the base directory. - * @param baseDir the base directory for loading templates - */ - public FileTemplateLoader(final File baseDir) - throws IOException { - this(baseDir, false); - } - - /** - * Creates a new file template loader that will use the specified directory as the base directory for loading - * templates. See the parameters for allowing symlinks that point outside the base directory. - * - * @param baseDir - * the base directory for loading templates - * - * @param disableCanonicalPathCheck - * If {@code true}, it will not check if the file to be loaded is inside the {@code baseDir} or not, - * according the <em>canonical</em> paths of the {@code baseDir} and the file to load. Note that - * {@link Configuration#getTemplate(String)} and (its overloads) already prevents backing out from the - * template directory with paths like {@code /../../../etc/password}, however, that can be circumvented - * with symbolic links or other file system features. If you really want to use symbolic links that point - * outside the {@code baseDir}, set this parameter to {@code true}, but then be very careful with - * template paths that are supplied by the visitor or an external system. - */ - public FileTemplateLoader(final File baseDir, final boolean disableCanonicalPathCheck) - throws IOException { - try { - Object[] retval = AccessController.doPrivileged(new PrivilegedExceptionAction<Object[]>() { - @Override - public Object[] run() throws IOException { - if (!baseDir.exists()) { - throw new FileNotFoundException(baseDir + " does not exist."); - } - if (!baseDir.isDirectory()) { - throw new IOException(baseDir + " is not a directory."); - } - Object[] retval = new Object[2]; - if (disableCanonicalPathCheck) { - retval[0] = baseDir; - retval[1] = null; - } else { - retval[0] = baseDir.getCanonicalFile(); - String basePath = ((File) retval[0]).getPath(); - // Most canonical paths don't end with File.separator, - // but some does. Like, "C:\" VS "C:\templates". - if (!basePath.endsWith(File.separator)) { - basePath += File.separatorChar; - } - retval[1] = basePath; - } - return retval; - } - }); - this.baseDir = (File) retval[0]; - this.canonicalBasePath = (String) retval[1]; - - setEmulateCaseSensitiveFileSystem(getEmulateCaseSensitiveFileSystemDefault()); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); - } - } - - private File getFile(final String name) throws IOException { - try { - return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() { - @Override - public File run() throws IOException { - File source = new File(baseDir, SEP_IS_SLASH ? name : - name.replace('/', File.separatorChar)); - if (!source.isFile()) { - return null; - } - // Security check for inadvertently returning something - // outside the template directory when linking is not - // allowed. - if (canonicalBasePath != null) { - String normalized = source.getCanonicalPath(); - if (!normalized.startsWith(canonicalBasePath)) { - throw new SecurityException(source.getAbsolutePath() - + " resolves to " + normalized + " which " + - " doesn't start with " + canonicalBasePath); - } - } - - if (emulateCaseSensitiveFileSystem && !isNameCaseCorrect(source)) { - return null; - } - - return source; - } - }); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); - } - } - - private long getLastModified(final File templateSource) { - return (AccessController.<Long>doPrivileged(new PrivilegedAction<Long>() { - @Override - public Long run() { - return Long.valueOf((templateSource).lastModified()); - } - })).longValue(); - } - - private InputStream getInputStream(final File templateSource) - throws IOException { - try { - return AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() { - @Override - public InputStream run() throws IOException { - return new FileInputStream(templateSource); - } - }); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); - } - } - - /** - * Called by {@link #getFile(String)} when {@link #getEmulateCaseSensitiveFileSystem()} is {@code true}. - */ - private boolean isNameCaseCorrect(File source) throws IOException { - final String sourcePath = source.getPath(); - synchronized (correctCasePaths) { - if (correctCasePaths.get(sourcePath) != null) { - return true; - } - } - - final File parentDir = source.getParentFile(); - if (parentDir != null) { - if (!baseDir.equals(parentDir) && !isNameCaseCorrect(parentDir)) { - return false; - } - - final String[] listing = parentDir.list(); - if (listing != null) { - final String fileName = source.getName(); - - boolean identicalNameFound = false; - for (int i = 0; !identicalNameFound && i < listing.length; i++) { - if (fileName.equals(listing[i])) { - identicalNameFound = true; - } - } - - if (!identicalNameFound) { - // If we find a similarly named file that only differs in case, then this is a file-not-found. - for (int i = 0; i < listing.length; i++) { - final String listingEntry = listing[i]; - if (fileName.equalsIgnoreCase(listingEntry)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Emulating file-not-found because of letter case differences to the " - + "real file, for: {}", sourcePath); - } - return false; - } - } - } - } - } - - synchronized (correctCasePaths) { - correctCasePaths.put(sourcePath, Boolean.TRUE); - } - return true; - } - - /** - * Returns the base directory in which the templates are searched. This comes from the constructor argument, but - * it's possibly a canonicalized version of that. - * - * @since 2.3.21 - */ - public File getBaseDirectory() { - return baseDir; - } - - /** - * Intended for development only, checks if the template name matches the case (upper VS lower case letters) of the - * actual file name, and if it doesn't, it emulates a file-not-found even if the file system is case insensitive. - * This is useful when developing application on Windows, which will be later installed on Linux, OS X, etc. This - * check can be resource intensive, as to check the file name the directories involved, up to the - * {@link #getBaseDirectory()} directory, must be listed. Positive results (matching case) will be cached without - * expiration time. - * - * <p>The default in {@link FileTemplateLoader} is {@code false}, but subclasses may change they by overriding - * {@link #getEmulateCaseSensitiveFileSystemDefault()}. - * - * @since 2.3.23 - */ - public void setEmulateCaseSensitiveFileSystem(boolean nameCaseChecked) { - // Ensure that the cache exists exactly when needed: - if (nameCaseChecked) { - if (correctCasePaths == null) { - correctCasePaths = new MruCacheStorage(CASE_CHECH_CACHE_HARD_SIZE, CASE_CHECK_CACHE__SOFT_SIZE); - } - } else { - correctCasePaths = null; - } - - this.emulateCaseSensitiveFileSystem = nameCaseChecked; - } - - /** - * Getter pair of {@link #setEmulateCaseSensitiveFileSystem(boolean)}. - * - * @since 2.3.23 - */ - public boolean getEmulateCaseSensitiveFileSystem() { - return emulateCaseSensitiveFileSystem; - } - - /** - * Returns the default of {@link #getEmulateCaseSensitiveFileSystem()}. In {@link FileTemplateLoader} it's - * {@code false}, unless the {@link #SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM} system property was - * set to {@code true}, but this can be overridden here in custom subclasses. For example, if your environment - * defines something like developer mode, you may want to override this to return {@code true} on Windows. - * - * @since 2.3.23 - */ - protected boolean getEmulateCaseSensitiveFileSystemDefault() { - return EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT; - } - - /** - * Show class name and some details that are useful in template-not-found errors. - * - * @since 2.3.21 - */ - @Override - public String toString() { - // We don't StringUtil.jQuote paths here, because on Windows there will be \\-s then that some may find - // confusing. - return _TemplateLoaderUtils.getClassNameForToString(this) + "(" - + "baseDir=\"" + baseDir + "\"" - + (canonicalBasePath != null ? ", canonicalBasePath=\"" + canonicalBasePath + "\"" : "") - + (emulateCaseSensitiveFileSystem ? ", emulateCaseSensitiveFileSystem=true" : "") - + ")"; - } - - @Override - public TemplateLoaderSession createSession() { - return null; - } - - @Override - public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, - Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException { - File file = getFile(name); - if (file == null) { - return TemplateLoadingResult.NOT_FOUND; - } - - FileTemplateLoadingSource source = new FileTemplateLoadingSource(file); - - long lmd = getLastModified(file); - Long version = lmd != -1 ? lmd : null; - - if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(source) - && Objects.equals(ifVersionDiffersFrom, version)) { - return TemplateLoadingResult.NOT_MODIFIED; - } - - return new TemplateLoadingResult(source, version, getInputStream(file), null); - } - - @Override - public void resetState() { - // Does nothing - } - - @SuppressWarnings("serial") - private static class FileTemplateLoadingSource implements TemplateLoadingSource { - - private final File file; - - FileTemplateLoadingSource(File file) { - this.file = file; - } - - @Override - public int hashCode() { - return file.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - return file.equals(((FileTemplateLoadingSource) obj).file); - } - - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java b/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java index 6e954bb..689cf6e 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java @@ -23,18 +23,21 @@ import java.io.IOException; import org.apache.freemarker.core.Configuration; import org.apache.freemarker.core.TemplateNotFoundException; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2; import org.apache.freemarker.core.util.StringUtil; /** * Indicates that the template name given was malformed according the {@link TemplateNameFormat} in use. Note that for - * backward compatibility, {@link TemplateNameFormat#DEFAULT_2_3_0} doesn't throw this exception, - * {@link TemplateNameFormat#DEFAULT_2_4_0} does. This exception extends {@link IOException} for backward compatibility. + * backward compatibility, {@link DefaultTemplateNameFormatFM2} doesn't throw this exception, + * {@link DefaultTemplateNameFormat} does. This exception extends {@link IOException} for backward compatibility. * * @since 2.3.22 * * @see TemplateNotFoundException * @see Configuration#getTemplate(String) */ +@SuppressWarnings("serial") public class MalformedTemplateNameException extends IOException { private final String templateName; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/MruCacheStorage.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MruCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/MruCacheStorage.java deleted file mode 100644 index 1b34662..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/MruCacheStorage.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * 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 org.apache.freemarker.core.templateresolver; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Map; - -/** - * A cache storage that implements a two-level Most Recently Used cache. In the - * first level, items are strongly referenced up to the specified maximum. When - * the maximum is exceeded, the least recently used item is moved into the - * second level cache, where they are softly referenced, up to another - * specified maximum. When the second level maximum is also exceeded, the least - * recently used item is discarded altogether. This cache storage is a - * generalization of both {@link StrongCacheStorage} and - * {@link SoftCacheStorage} - the effect of both of them can be achieved by - * setting one maximum to zero and the other to the largest positive integer. - * On the other hand, if you wish to use this storage in a strong-only mode, or - * in a soft-only mode, you might consider using {@link StrongCacheStorage} or - * {@link SoftCacheStorage} instead, as they can be used by - * {@link DefaultTemplateResolver} concurrently without any synchronization on a 5.0 or - * later JRE. - * - * <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 DefaultTemplateResolver}, the natural user of this class provides the - * necessary synchronizations when it uses the class. - * Also you might consider whether you need this sort of a mixed storage at all - * in your solution, as in most cases SoftCacheStorage can also be sufficient. - * SoftCacheStorage will use Java soft references, and they already use access - * timestamps internally to bias the garbage collector against clearing - * recently used references, so you can get reasonably good (and - * memory-sensitive) most-recently-used caching through - * {@link SoftCacheStorage} as well. - * - * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage) - */ -public class MruCacheStorage implements CacheStorageWithGetSize { - private final MruEntry strongHead = new MruEntry(); - private final MruEntry softHead = new MruEntry(); - { - softHead.linkAfter(strongHead); - } - private final Map map = new HashMap(); - private final ReferenceQueue refQueue = new ReferenceQueue(); - private final int strongSizeLimit; - private final int softSizeLimit; - private int strongSize = 0; - private int softSize = 0; - - /** - * Creates a new MRU cache storage with specified maximum cache sizes. Each - * cache size can vary between 0 and {@link Integer#MAX_VALUE}. - * @param strongSizeLimit the maximum number of strongly referenced templates; when exceeded, the entry used - * the least recently will be moved into the soft cache. - * @param softSizeLimit the maximum number of softly referenced templates; when exceeded, the entry used - * the least recently will be discarded. - */ - public MruCacheStorage(int strongSizeLimit, int softSizeLimit) { - if (strongSizeLimit < 0) throw new IllegalArgumentException("strongSizeLimit < 0"); - if (softSizeLimit < 0) throw new IllegalArgumentException("softSizeLimit < 0"); - this.strongSizeLimit = strongSizeLimit; - this.softSizeLimit = softSizeLimit; - } - - public Object get(Object key) { - removeClearedReferences(); - MruEntry entry = (MruEntry) map.get(key); - if (entry == null) { - return null; - } - relinkEntryAfterStrongHead(entry, null); - Object value = entry.getValue(); - if (value instanceof MruReference) { - // This can only happen with strongSizeLimit == 0 - return ((MruReference) value).get(); - } - return value; - } - - public void put(Object key, Object value) { - removeClearedReferences(); - MruEntry entry = (MruEntry) map.get(key); - if (entry == null) { - entry = new MruEntry(key, value); - map.put(key, entry); - linkAfterStrongHead(entry); - } else { - relinkEntryAfterStrongHead(entry, value); - } - - } - - public void remove(Object key) { - removeClearedReferences(); - removeInternal(key); - } - - private void removeInternal(Object key) { - MruEntry entry = (MruEntry) map.remove(key); - if (entry != null) { - unlinkEntryAndInspectIfSoft(entry); - } - } - - public void clear() { - strongHead.makeHead(); - softHead.linkAfter(strongHead); - map.clear(); - strongSize = softSize = 0; - // Quick refQueue processing - while (refQueue.poll() != null); - } - - private void relinkEntryAfterStrongHead(MruEntry entry, Object newValue) { - if (unlinkEntryAndInspectIfSoft(entry) && newValue == null) { - // Turn soft reference into strong reference, unless is was cleared - MruReference mref = (MruReference) entry.getValue(); - Object strongValue = mref.get(); - if (strongValue != null) { - entry.setValue(strongValue); - linkAfterStrongHead(entry); - } else { - map.remove(mref.getKey()); - } - } else { - if (newValue != null) { - entry.setValue(newValue); - } - linkAfterStrongHead(entry); - } - } - - private void linkAfterStrongHead(MruEntry entry) { - entry.linkAfter(strongHead); - if (strongSize == strongSizeLimit) { - // softHead.previous is LRU strong entry - MruEntry lruStrong = softHead.getPrevious(); - // Attila: This is equaivalent to strongSizeLimit != 0 - // DD: But entry.linkAfter(strongHead) was just executed above, so - // lruStrong != strongHead is true even if strongSizeLimit == 0. - if (lruStrong != strongHead) { - lruStrong.unlink(); - if (softSizeLimit > 0) { - lruStrong.linkAfter(softHead); - lruStrong.setValue(new MruReference(lruStrong, refQueue)); - if (softSize == softSizeLimit) { - // List is circular, so strongHead.previous is LRU soft entry - MruEntry lruSoft = strongHead.getPrevious(); - lruSoft.unlink(); - map.remove(lruSoft.getKey()); - } else { - ++softSize; - } - } else { - map.remove(lruStrong.getKey()); - } - } - } else { - ++strongSize; - } - } - - private boolean unlinkEntryAndInspectIfSoft(MruEntry entry) { - entry.unlink(); - if (entry.getValue() instanceof MruReference) { - --softSize; - return true; - } else { - --strongSize; - return false; - } - } - - private void removeClearedReferences() { - for (; ; ) { - MruReference ref = (MruReference) refQueue.poll(); - if (ref == null) { - break; - } - removeInternal(ref.getKey()); - } - } - - /** - * Returns the configured upper limit of the number of strong cache entries. - * - * @since 2.3.21 - */ - public int getStrongSizeLimit() { - return strongSizeLimit; - } - - /** - * Returns the configured upper limit of the number of soft cache entries. - * - * @since 2.3.21 - */ - public int getSoftSizeLimit() { - return softSizeLimit; - } - - /** - * Returns the <em>current</em> number of strong cache entries. - * - * @see #getStrongSizeLimit() - * @since 2.3.21 - */ - public int getStrongSize() { - return strongSize; - } - - /** - * Returns a close approximation of the <em>current</em> number of soft cache entries. - * - * @see #getSoftSizeLimit() - * @since 2.3.21 - */ - public int getSoftSize() { - removeClearedReferences(); - return softSize; - } - - /** - * Returns a close approximation of the current number of cache entries. - * - * @see #getStrongSize() - * @see #getSoftSize() - * @since 2.3.21 - */ - public int getSize() { - return getSoftSize() + getStrongSize(); - } - - private static final class MruEntry { - private MruEntry prev; - private MruEntry next; - private final Object key; - private Object value; - - /** - * Used solely to construct the head element - */ - MruEntry() { - makeHead(); - key = value = null; - } - - MruEntry(Object key, Object value) { - this.key = key; - this.value = value; - } - - Object getKey() { - return key; - } - - Object getValue() { - return value; - } - - void setValue(Object value) { - this.value = value; - } - - MruEntry getPrevious() { - return prev; - } - - void linkAfter(MruEntry entry) { - next = entry.next; - entry.next = this; - prev = entry; - next.prev = this; - } - - void unlink() { - next.prev = prev; - prev.next = next; - prev = null; - next = null; - } - - void makeHead() { - prev = next = this; - } - } - - private static class MruReference extends SoftReference { - private final Object key; - - MruReference(MruEntry entry, ReferenceQueue queue) { - super(entry.getValue(), queue); - this.key = entry.getKey(); - } - - Object getKey() { - return key; - } - } - - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/MultiTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MultiTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/MultiTemplateLoader.java deleted file mode 100644 index 0f4a7c1..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/MultiTemplateLoader.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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 org.apache.freemarker.core.templateresolver; - -import java.io.IOException; -import java.io.Serializable; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.freemarker.core.util.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. Except, when the - * {@linkplain #setSticky(boolean)} sticky} setting is set to {@code true} (default is false {@code false}), if - * a request for some template name was already satisfied in the past by one of the loaders, that loader is queried - * first (stickiness). - * - * <p>This class is thread-safe. - */ -// TODO JUnit test -public class MultiTemplateLoader implements TemplateLoader { - - private final TemplateLoader[] templateLoaders; - private final Map<String, TemplateLoader> lastTemplateLoaderForName = new ConcurrentHashMap<String, TemplateLoader>(); - - private boolean sticky = false; - - /** - * 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(); - } - - /** - * Clears the sickiness memory, also resets the state of all enclosed {@link TemplateLoader}-s. - */ - @Override - public void resetState() { - lastTemplateLoaderForName.clear(); - for (TemplateLoader templateLoader : templateLoaders) { - templateLoader.resetState(); - } - } - - /** - * 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)}. - */ - 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 false}. - */ - public void setSticky(boolean sticky) { - this.sticky = sticky; - } - - @Override - public TemplateLoaderSession createSession() { - return null; - } - - @Override - public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, - Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException { - TemplateLoader lastLoader = null; - if (sticky) { - // Use soft affinity - give the loader that last found this - // resource a chance to find it again first. - lastLoader = lastTemplateLoaderForName.get(name); - if (lastLoader != null) { - TemplateLoadingResult result = lastLoader.load(name, ifSourceDiffersFrom, ifVersionDiffersFrom, session); - if (result.getStatus() != TemplateLoadingResultStatus.NOT_FOUND) { - return result; - } - } - } - - // 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 (lastLoader != templateLoader) { - TemplateLoadingResult result = templateLoader.load( - name, ifSourceDiffersFrom, ifVersionDiffersFrom, session); - if (result.getStatus() != TemplateLoadingResultStatus.NOT_FOUND) { - if (sticky) { - lastTemplateLoaderForName.put(name, templateLoader); - } - return result; - } - } - } - - if (sticky) { - lastTemplateLoaderForName.remove(name); - } - return TemplateLoadingResult.NOT_FOUND; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/NullCacheStorage.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/NullCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/NullCacheStorage.java deleted file mode 100644 index 1f63675..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/NullCacheStorage.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 org.apache.freemarker.core.templateresolver; - -/** - * A cache storage that doesn't store anything. Use this if you - * don't want caching. - * - * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage) - * - * @since 2.3.17 - */ -public class NullCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize { - - /** - * @since 2.3.22 - */ - public static final NullCacheStorage INSTANCE = new NullCacheStorage(); - - public boolean isConcurrent() { - return true; - } - - public Object get(Object key) { - return null; - } - - public void put(Object key, Object value) { - // do nothing - } - - public void remove(Object key) { - // do nothing - } - - public void clear() { - // do nothing - } - - /** - * Always returns 0. - * - * @since 2.3.21 - */ - public int getSize() { - return 0; - } - -}
