http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java deleted file mode 100644 index 0f75912..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java +++ /dev/null @@ -1,141 +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.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.apache.freemarker.core.util.UndeclaredThrowableException; - -/** - * Soft cache storage is a cache storage that uses {@link SoftReference} objects to hold the objects it was passed, - * therefore allows the garbage collector to purge the cache when it determines that it wants to free up memory. This - * class is thread-safe to the extent that its underlying map is. The parameterless constructor uses a thread-safe map - * since 2.3.24 or Java 5. - * - * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage) - */ -public class SoftCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize { - private static final Method atomicRemove = getAtomicRemoveMethod(); - - private final ReferenceQueue queue = new ReferenceQueue(); - private final Map map; - private final boolean concurrent; - - /** - * Creates an instance that uses a {@link ConcurrentMap} internally. - */ - public SoftCacheStorage() { - this(new ConcurrentHashMap()); - } - - /** - * Returns true if the underlying Map is a {@code ConcurrentMap}. - */ - public boolean isConcurrent() { - return concurrent; - } - - public SoftCacheStorage(Map backingMap) { - map = backingMap; - this.concurrent = map instanceof ConcurrentMap; - } - - public Object get(Object key) { - processQueue(); - Reference ref = (Reference) map.get(key); - return ref == null ? null : ref.get(); - } - - public void put(Object key, Object value) { - processQueue(); - map.put(key, new SoftValueReference(key, value, queue)); - } - - public void remove(Object key) { - processQueue(); - map.remove(key); - } - - public void clear() { - map.clear(); - processQueue(); - } - - /** - * Returns a close approximation of the number of cache entries. - * - * @since 2.3.21 - */ - public int getSize() { - processQueue(); - return map.size(); - } - - private void processQueue() { - for (; ; ) { - SoftValueReference ref = (SoftValueReference) queue.poll(); - if (ref == null) { - return; - } - Object key = ref.getKey(); - if (concurrent) { - try { - atomicRemove.invoke(map, new Object[] { key, ref }); - } catch (IllegalAccessException e) { - throw new UndeclaredThrowableException(e); - } catch (InvocationTargetException e) { - throw new UndeclaredThrowableException(e); - } - } else if (map.get(key) == ref) { - map.remove(key); - } - } - } - - private static final class SoftValueReference extends SoftReference { - private final Object key; - - SoftValueReference(Object key, Object value, ReferenceQueue queue) { - super(value, queue); - this.key = key; - } - - Object getKey() { - return key; - } - } - - private static Method getAtomicRemoveMethod() { - try { - return Class.forName("java.util.concurrent.ConcurrentMap").getMethod("remove", new Class[] { Object.class, Object.class }); - } catch (ClassNotFoundException e) { - return null; - } catch (NoSuchMethodException e) { - throw new UndeclaredThrowableException(e); - } - } -} \ 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/StringTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java deleted file mode 100644 index 5d0d378..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java +++ /dev/null @@ -1,194 +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.ObjectOutputStream; -import java.io.Serializable; -import java.io.StringReader; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.freemarker.core.util.StringUtil; - -/** - * A {@link TemplateLoader} that uses a {@link Map} with {@code String} as its source of templates. This is similar to - * {@link StringTemplateLoader}, but uses {@code String} instead of {@link String}; see more details there. - * - * <p>Note that {@link StringTemplateLoader} can't be used with a distributed (cluster-wide) {@link CacheStorage}, - * as it produces {@link TemplateLoadingSource}-s that deliberately throw exception on serialization (because the - * content is only accessible within a single JVM, and is also volatile). - */ -// TODO JUnit tests -public class StringTemplateLoader implements TemplateLoader { - - private static final AtomicLong INSTANCE_COUNTER = new AtomicLong(); - - private final long instanceId = INSTANCE_COUNTER.get(); - private final AtomicLong templatesRevision = new AtomicLong(); - private final ConcurrentMap<String, ContentHolder> templates = new ConcurrentHashMap<>(); - - /** - * Puts a template into the template loader. The name can contain slashes to denote logical directory structure, but - * must not start with a slash. Each template will get an unique revision number, thus replacing a template will - * cause the template cache to reload it (when the update delay expires). - * - * <p>This method is thread-safe. - * - * @param name - * the name of the template. - * @param content - * the source code of the template. - */ - public void putTemplate(String name, String content) { - templates.put( - name, - new ContentHolder(content, new Source(instanceId, name), templatesRevision.incrementAndGet())); - } - - /** - * Removes the template with the specified name if it was added earlier. - * - * <p> - * This method is thread-safe. - * - * @param name - * Exactly the key with which the template was added. - * - * @return Whether a template was found with the given key (and hence was removed now) - */ - public boolean removeTemplate(String name) { - return templates.remove(name) != null; - } - - @Override - public TemplateLoaderSession createSession() { - return null; - } - - @Override - public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, - Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException { - ContentHolder contentHolder = templates.get(name); - if (contentHolder == null) { - return TemplateLoadingResult.NOT_FOUND; - } else if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(contentHolder.source) - && Objects.equals(ifVersionDiffersFrom, contentHolder.version)) { - return TemplateLoadingResult.NOT_MODIFIED; - } else { - return new TemplateLoadingResult( - contentHolder.source, contentHolder.version, - new StringReader(contentHolder.content), - null); - } - } - - @Override - public void resetState() { - // Do nothing - } - - /** - * Show class name and some details that are useful in template-not-found errors. - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(_TemplateLoaderUtils.getClassNameForToString(this)); - sb.append("(Map { "); - int cnt = 0; - for (String name : templates.keySet()) { - cnt++; - if (cnt != 1) { - sb.append(", "); - } - if (cnt > 10) { - sb.append("..."); - break; - } - sb.append(StringUtil.jQuote(name)); - sb.append("=..."); - } - if (cnt != 0) { - sb.append(' '); - } - sb.append("})"); - return sb.toString(); - } - - private static class ContentHolder { - private final String content; - private final Source source; - private final long version; - - public ContentHolder(String content, Source source, long version) { - this.content = content; - this.source = source; - this.version = version; - } - - } - - @SuppressWarnings("serial") - private static class Source implements TemplateLoadingSource { - - private final long instanceId; - private final String name; - - public Source(long instanceId, String name) { - this.instanceId = instanceId; - this.name = name; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (instanceId ^ (instanceId >>> 32)); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - Source other = (Source) obj; - if (instanceId != other.instanceId) return false; - if (name == null) { - if (other.name != null) return false; - } else if (!name.equals(other.name)) { - return false; - } - return true; - } - - private void writeObject(ObjectOutputStream out) throws IOException { - throw new IOException(StringTemplateLoader.class.getName() - + " sources can't be serialized, as they don't support clustering."); - } - - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java deleted file mode 100644 index ad5079f..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java +++ /dev/null @@ -1,67 +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.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Strong cache storage is a cache storage that simply wraps a {@link Map}. It holds a strong reference to all objects - * it was passed, therefore prevents the cache from being purged during garbage collection. This class is always - * thread-safe since 2.3.24, before that if we are running on Java 5 or later. - * - * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage) - */ -public class StrongCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize { - - private final Map map = new ConcurrentHashMap(); - - /** - * Always returns {@code true}. - */ - public boolean isConcurrent() { - return true; - } - - public Object get(Object key) { - return map.get(key); - } - - public void put(Object key, Object value) { - map.put(key, value); - } - - public void remove(Object key) { - map.remove(key); - } - - /** - * Returns a close approximation of the number of cache entries. - * - * @since 2.3.21 - */ - public int getSize() { - return map.size(); - } - - public void clear() { - map.clear(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java index f484224..2a58de7 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java @@ -23,6 +23,7 @@ import java.io.Serializable; import org.apache.freemarker.core.Configuration; import org.apache.freemarker.core.TemplateNotFoundException; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; /** * FreeMarker loads template "files" through objects that implement this interface, thus the templates need not be real @@ -61,7 +62,7 @@ public interface TemplateLoader { * * @param name * The name (template root directory relative path) of the template, already localized and normalized by - * the {@link org.apache.freemarker.core.templateresolver.DefaultTemplateResolver cache}. It is completely up to the loader implementation to + * the {@link org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver cache}. It is completely up to the loader implementation to * interpret the name, however it should expect to receive hierarchical paths where path components are * separated by a slash (not backslash). Backslashes (or any other OS specific separator character) are * not considered as separators by FreeMarker, and thus they will not be replaced with slash before http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java index 9fd99ff..262dcce 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; + /** * Stores shared state between {@link TemplateLoader} operations that are executed close to each other in the same * thread. For example, a {@link TemplateLoader} that reads from a database might wants to store the database http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java index 426e050..94844a4 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java @@ -27,6 +27,7 @@ import java.util.Date; import org.apache.freemarker.core.Configuration; import org.apache.freemarker.core.ast.TemplateConfiguration; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; import org.apache.freemarker.core.util.NullArgumentException; /** http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java index 42b5398..2af5721 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java @@ -21,6 +21,9 @@ package org.apache.freemarker.core.templateresolver; import java.io.Serializable; import java.util.HashMap; +import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader; +import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader; + /** * The point of {@link TemplateLoadingSource} is that with their {@link Object#equals(Object)} method we can tell if two * cache entries were generated from the same physical resource or not. Comparing the template names isn't enough, http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java index 304d9b6..a37e043 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java @@ -24,6 +24,7 @@ import java.io.Serializable; import java.util.Locale; import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy; /** * Used as the parameter of {@link TemplateLookupStrategy#lookup(TemplateLookupContext)}. @@ -31,13 +32,11 @@ import org.apache.freemarker.core.Configuration; * * @since 2.3.22 */ -public abstract class TemplateLookupContext { +public abstract class TemplateLookupContext<R extends TemplateLookupResult> { private final String templateName; private final Locale templateLocale; private final Object customLookupCondition; - private final TemplateLoadingSource cachedResultSource; - private final Serializable cachedResultVersion; /** * Finds the template source based on its <em>normalized</em> name; handles {@code *} steps (so called acquisition), @@ -56,7 +55,7 @@ public abstract class TemplateLookupContext { * not for {@link TemplateLoader}-s). Hence discarding a positive result and looking for another can * generate substantial extra I/O. */ - public abstract TemplateLookupResult lookupWithAcquisitionStrategy(String templateName) throws IOException; + public abstract R lookupWithAcquisitionStrategy(String templateName) throws IOException; /** * Finds the template source based on its <em>normalized</em> name; tries localized variations going from most @@ -64,19 +63,16 @@ public abstract class TemplateLookupContext { * If {@code templateLocale} is {@code null} (typically, because {@link Configuration#getLocalizedLookup()} is * {@code false})), then it's the same as calling {@link #lookupWithAcquisitionStrategy(String)} directly. This is * the default strategy of FreeMarker (at least in 2.3.x), so for more information, see - * {@link TemplateLookupStrategy#DEFAULT_2_3_0}. + * {@link DefaultTemplateLookupStrategy#INSTANCE}. */ - public abstract TemplateLookupResult lookupWithLocalizedThenAcquisitionStrategy(String templateName, + public abstract R lookupWithLocalizedThenAcquisitionStrategy(String templateName, Locale templateLocale) throws IOException; /** Default visibility to prevent extending the class from outside this package. */ - TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition, - TemplateLoadingSource cachedResultSource, Serializable cachedResultVersion) { + protected TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) { this.templateName = templateName; this.templateLocale = templateLocale; this.customLookupCondition = customLookupCondition; - this.cachedResultSource = cachedResultSource; - this.cachedResultVersion = cachedResultVersion; } /** @@ -111,16 +107,6 @@ public abstract class TemplateLookupContext { * {@link TemplateLookupStrategy#lookup(TemplateLookupContext)}. (In the current implementation it just always * returns the same static singleton, but that might need to change in the future.) */ - public TemplateLookupResult createNegativeLookupResult() { - return TemplateLookupResult.createNegativeResult(); - } - - TemplateLoadingSource getCachedResultSource() { - return cachedResultSource; - } - - Serializable getCachedResultVersion() { - return cachedResultVersion; - } + public abstract R createNegativeLookupResult(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java index c6a4aa5..4917878 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java @@ -20,30 +20,23 @@ package org.apache.freemarker.core.templateresolver; import org.apache.freemarker.core.Template; -import org.apache.freemarker.core.util.NullArgumentException; +import org.apache.freemarker.core.templateresolver.impl.TemplateLoaderBasedTemplateLookupResult; /** * The return value of {@link TemplateLookupStrategy#lookup(TemplateLookupContext)} and similar lookup methods. You * usually get one from {@link TemplateLookupContext#lookupWithAcquisitionStrategy(String)} or - * {@link TemplateLookupContext#createNegativeLookupResult()}; you can't create instances of this directly. + * {@link TemplateLookupContext#createNegativeLookupResult()}. + * + * <p> + * Subclass this only if you are implementing a {@link TemplateLookupContext}; if the {@link TemplateLookupContext} that + * you are implementing uses {@link TemplateLoader}-s, consider using {@link TemplateLoaderBasedTemplateLookupResult} + * instead of writing your own subclass. * * @since 2.3.22 */ public abstract class TemplateLookupResult { - /** Used internally to get a not-found result (currently just a static singleton). */ - static TemplateLookupResult createNegativeResult() { - return NegativeTemplateLookupResult.INSTANCE; - } - - /** Used internally to create the appropriate kind of result from the parameters. */ - static TemplateLookupResult from(String templateSourceName, TemplateLoadingResult templateLoaderResult) { - return templateLoaderResult.getStatus() != TemplateLoadingResultStatus.NOT_FOUND - ? new PositiveTemplateLookupResult(templateSourceName, templateLoaderResult) - : createNegativeResult(); - } - - private TemplateLookupResult() { + protected TemplateLookupResult() { // nop } @@ -57,77 +50,5 @@ public abstract class TemplateLookupResult { * Tells if the lookup has found a matching template. */ public abstract boolean isPositive(); - - /** - * Used internally to extract the {@link TemplateLoadingResult}; {@code null} if {@link #isPositive()} is - * {@code false}. - */ - abstract TemplateLoadingResult getTemplateLoaderResult(); - - private static final class PositiveTemplateLookupResult extends TemplateLookupResult { - - private final String templateSourceName; - private final TemplateLoadingResult templateLoaderResult; - - /** - * @param templateSourceName - * The name of the matching template found. This is not necessarily the same as the template name - * with which the template was originally requested. For example, one may gets a template for the - * {@code "foo.ftl"} name, but due to localized lookup the template is actually loaded from - * {@code "foo_de.ftl"}. Then this parameter must be {@code "foo_de.ftl"}, not {@code "foo.ftl"}. Not - * {@code null}. - * - * @param templateLoaderResult - * See {@link TemplateLoader#load} to understand what that means. Not - * {@code null}. - */ - private PositiveTemplateLookupResult(String templateSourceName, TemplateLoadingResult templateLoaderResult) { - NullArgumentException.check("templateName", templateSourceName); - NullArgumentException.check("templateLoaderResult", templateLoaderResult); - - this.templateSourceName = templateSourceName; - this.templateLoaderResult = templateLoaderResult; - } - - @Override - public String getTemplateSourceName() { - return templateSourceName; - } - - @Override - TemplateLoadingResult getTemplateLoaderResult() { - return templateLoaderResult; - } - - @Override - public boolean isPositive() { - return true; - } - } - - private static final class NegativeTemplateLookupResult extends TemplateLookupResult { - - private static final NegativeTemplateLookupResult INSTANCE = new NegativeTemplateLookupResult(); - - private NegativeTemplateLookupResult() { - // nop - } - - @Override - public String getTemplateSourceName() { - return null; - } - - @Override - TemplateLoadingResult getTemplateLoaderResult() { - return null; - } - - @Override - public boolean isPositive() { - return false; - } - - } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java index e993140..a1c9577 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java @@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver; import java.io.IOException; -import java.util.Locale; import org.apache.freemarker.core.Configuration; import org.apache.freemarker.core.Template; @@ -58,28 +57,6 @@ import org.apache.freemarker.core.Template; public abstract class TemplateLookupStrategy { /** - * <p> - * The default lookup strategy of FreeMarker. - * - * <p> - * Through an example: Assuming localized lookup is enabled and that a template is requested for the name - * {@code example.ftl} and {@code Locale("es", "ES", "Traditional_WIN")}, it will try the following template names, - * in this order: {@code "foo_en_AU_Traditional_WIN.ftl"}, {@code "foo_en_AU_Traditional.ftl"}, - * {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. It stops at the first variation where it finds - * a template. (If the template name contains "*" steps, finding the template for the attempted localized variation - * happens with the template acquisition mechanism.) If localized lookup is disabled, it won't try to add any locale - * strings, so it just looks for {@code "foo.ftl"}. - * - * <p> - * The generation of the localized name variation with the default lookup strategy, happens like this: It removes - * the file extension (the part starting with the <em>last</em> dot), then appends {@link Locale#toString()} after - * it, and puts back the extension. Then it starts to remove the parts from the end of the locale, considering - * {@code "_"} as the separator between the parts. It won't remove parts that are not part of the locale string - * (like if the requested template name is {@code foo_bar.ftl}, it won't remove the {@code "_bar"}). - */ - public static final TemplateLookupStrategy DEFAULT_2_3_0 = new Default020300(); - - /** * Finds the template source that matches the template name, locale (if not {@code null}) and other parameters * specified in the {@link TemplateLookupContext}. See also the class-level {@link TemplateLookupStrategy} * documentation to understand lookup strategies more. @@ -96,20 +73,6 @@ public abstract class TemplateLookupStrategy { * {@code TemplateLookupContext#createNegativeLookupResult()} if no matching template exists. Can't be * {@code null}. */ - public abstract TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException; - - private static class Default020300 extends TemplateLookupStrategy { - - @Override - public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException { - return ctx.lookupWithLocalizedThenAcquisitionStrategy(ctx.getTemplateName(), ctx.getTemplateLocale()); - } - - @Override - public String toString() { - return "TemplateLookupStrategy.DEFAULT_2_3_0"; - } - - } + public abstract <R extends TemplateLookupResult> R lookup(TemplateLookupContext<R> ctx) throws IOException; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java index 5b68241..ebcbe9a 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java @@ -19,11 +19,6 @@ package org.apache.freemarker.core.templateresolver; -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.TemplateNotFoundException; -import org.apache.freemarker.core.Version; -import org.apache.freemarker.core.util.StringUtil; - /** * Symbolized template name format. The API of this class isn't exposed as it's too immature, so custom * template name formats aren't possible yet. @@ -32,389 +27,28 @@ import org.apache.freemarker.core.util.StringUtil; */ public abstract class TemplateNameFormat { - private TemplateNameFormat() { - // Currently can't be instantiated from outside + protected TemplateNameFormat() { + // } /** - * The default template name format when {@link Configuration#Configuration(Version) incompatible_improvements} is - * below 2.4.0. As of FreeMarker 2.4.0, the default {@code incompatible_improvements} is still {@code 2.3.0}, and it - * will certainly remain so for a very long time. In new projects it's highly recommended to use - * {@link #DEFAULT_2_4_0} instead. - */ - public static final TemplateNameFormat DEFAULT_2_3_0 = new Default020300(); - - /** - * The default template name format only when {@link Configuration#Configuration(Version) incompatible_improvements} - * is set to 2.4.0 (or higher). This is not the out-of-the-box default format of FreeMarker 2.4.x, because the - * default {@code incompatible_improvements} is still 2.3.0 there. - * - * <p> - * Differences to the {@link #DEFAULT_2_3_0} format: - * - * <ul> - * - * <li>The scheme and the path need not be separated with {@code "://"} anymore, only with {@code ":"}. This makes - * template names like {@code "classpath:foo.ftl"} interpreted as an absolute name with scheme {@code "classpath"} - * and absolute path "foo.ftl". The scheme name before the {@code ":"} can't contain {@code "/"}, or else it's - * treated as a malformed name. The scheme part can be separated either with {@code "://"} or just {@code ":"} from - * the path. Hence, {@code myschme:/x} is normalized to {@code myschme:x}, while {@code myschme:///x} is normalized - * to {@code myschme://x}, but {@code myschme://x} or {@code myschme:/x} aren't changed by normalization. It's up - * the {@link TemplateLoader} to which the normalized names are passed to decide which of these scheme separation - * conventions are valid (maybe both).</li> - * - * <li>{@code ":"} is not allowed in template names, except as the scheme separator (see previous point). - * - * <li>Malformed paths throw {@link MalformedTemplateNameException} instead of acting like if the template wasn't - * found. - * - * <li>{@code "\"} (backslash) is not allowed in template names, and causes {@link MalformedTemplateNameException}. - * With {@link #DEFAULT_2_3_0} you would certainly end up with a {@link TemplateNotFoundException} (or worse, - * it would work, but steps like {@code ".."} wouldn't be normalized by FreeMarker). - * - * <li>Template names might end with {@code /}, like {@code "foo/"}, and the presence or lack of the terminating - * {@code /} is seen as significant. While their actual interpretation is up to the {@link TemplateLoader}, - * operations that manipulate template names assume that the last step refers to a "directory" as opposed to a - * "file" exactly if the terminating {@code /} is present. Except, the empty name is assumed to refer to the root - * "directory" (despite that it doesn't end with {@code /}). - * - * <li>{@code //} is normalized to {@code /}, except of course if it's in the scheme name terminator. Like - * {@code foo//bar///baaz.ftl} is normalized to {@code foo/bar/baaz.ftl}. (In general, 0 long step names aren't - * possible anymore.)</li> - * - * <li>The {@code ".."} bugs of the legacy normalizer are fixed: {@code ".."} steps has removed the preceding - * {@code "."} or {@code "*"} or scheme steps, not treating them specially as they should be. Now these work as - * expected. Examples: {@code "a/./../c"} has become to {@code "a/c"}, now it will be {@code "c"}; {@code "a/b/*} - * {@code /../c"} has become to {@code "a/b/c"}, now it will be {@code "a/*}{@code /c"}; {@code "scheme://.."} has - * become to {@code "scheme:/"}, now it will be {@code null} ({@link TemplateNotFoundException}) for backing out of - * the root directory.</li> - * - * <li>As now directory paths has to be handled as well, it recognizes terminating, leading, and lonely {@code ".."} - * and {@code "."} steps. For example, {@code "foo/bar/.."} now becomes to {@code "foo/"}</li> - * - * <li>Multiple consecutive {@code *} steps are normalized to one</li> - * - * </ul> - */ - public static final TemplateNameFormat DEFAULT_2_4_0 = new Default020400(); - - /** * Implements {@link TemplateResolver#toRootBasedName(String, String)}; see more there. */ - abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException; + public abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException; /** * Implements {@link TemplateResolver#normalizeRootBasedName(String)}; see more there. */ - abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException; - - private static final class Default020300 extends TemplateNameFormat { - @Override - String toRootBasedName(String baseName, String targetName) { - if (targetName.indexOf("://") > 0) { - return targetName; - } else if (targetName.startsWith("/")) { - int schemeSepIdx = baseName.indexOf("://"); - if (schemeSepIdx > 0) { - return baseName.substring(0, schemeSepIdx + 2) + targetName; - } else { - return targetName.substring(1); - } - } else { - if (!baseName.endsWith("/")) { - baseName = baseName.substring(0, baseName.lastIndexOf("/") + 1); - } - return baseName + targetName; - } - } - - @Override - String normalizeRootBasedName(final String name) throws MalformedTemplateNameException { - // Disallow 0 for security reasons. - checkNameHasNoNullCharacter(name); - - // The legacy algorithm haven't considered schemes, so the name is in effect a path. - // Also, note that `path` will be repeatedly replaced below, while `name` is final. - String path = name; - - for (; ; ) { - int parentDirPathLoc = path.indexOf("/../"); - if (parentDirPathLoc == 0) { - // If it starts with /../, then it reaches outside the template - // root. - throw newRootLeavingException(name); - } - if (parentDirPathLoc == -1) { - if (path.startsWith("../")) { - throw newRootLeavingException(name); - } - break; - } - int previousSlashLoc = path.lastIndexOf('/', parentDirPathLoc - 1); - path = path.substring(0, previousSlashLoc + 1) + - path.substring(parentDirPathLoc + "/../".length()); - } - for (; ; ) { - int currentDirPathLoc = path.indexOf("/./"); - if (currentDirPathLoc == -1) { - if (path.startsWith("./")) { - path = path.substring("./".length()); - } - break; - } - path = path.substring(0, currentDirPathLoc) + - path.substring(currentDirPathLoc + "/./".length() - 1); - } - // Editing can leave us with a leading slash; strip it. - if (path.length() > 1 && path.charAt(0) == '/') { - path = path.substring(1); - } - return path; - } - - @Override - public String toString() { - return "TemplateNameFormat.DEFAULT_2_3_0"; - } - - } - - private static final class Default020400 extends TemplateNameFormat { - @Override - String toRootBasedName(String baseName, String targetName) { - if (findSchemeSectionEnd(targetName) != 0) { - return targetName; - } else if (targetName.startsWith("/")) { // targetName is an absolute path - final String targetNameAsRelative = targetName.substring(1); - final int schemeSectionEnd = findSchemeSectionEnd(baseName); - if (schemeSectionEnd == 0) { - return targetNameAsRelative; - } else { - // Prepend the scheme of baseName: - return baseName.substring(0, schemeSectionEnd) + targetNameAsRelative; - } - } else { // targetName is a relative path - if (!baseName.endsWith("/")) { - // Not a directory name => get containing directory name - int baseEnd = baseName.lastIndexOf("/") + 1; - if (baseEnd == 0) { - // For something like "classpath:t.ftl", must not remove the scheme part: - baseEnd = findSchemeSectionEnd(baseName); - } - baseName = baseName.substring(0, baseEnd); - } - return baseName + targetName; - } - } - - @Override - String normalizeRootBasedName(final String name) throws MalformedTemplateNameException { - // Disallow 0 for security reasons. - checkNameHasNoNullCharacter(name); - - if (name.indexOf('\\') != -1) { - throw new MalformedTemplateNameException( - name, - "Backslash (\"\\\") is not allowed in template names. Use slash (\"/\") instead."); - } - - // Split name to a scheme and a path: - final String scheme; - String path; - { - int schemeSectionEnd = findSchemeSectionEnd(name); - if (schemeSectionEnd == 0) { - scheme = null; - path = name; - } else { - scheme = name.substring(0, schemeSectionEnd); - path = name.substring(schemeSectionEnd); - } - } - - if (path.indexOf(':') != -1) { - throw new MalformedTemplateNameException(name, - "The ':' character can only be used after the scheme name (if there's any), " - + "not in the path part"); - } - - path = removeRedundantSlashes(path); - // path now doesn't start with "/" - - path = removeDotSteps(path); - - path = resolveDotDotSteps(path, name); - - path = removeRedundantStarSteps(path); - - return scheme == null ? path : scheme + path; - } - - private int findSchemeSectionEnd(String name) { - int schemeColonIdx = name.indexOf(":"); - if (schemeColonIdx == -1 || name.lastIndexOf('/', schemeColonIdx - 1) != -1) { - return 0; - } else { - // If there's a following "//", it's treated as the part of the scheme section: - if (schemeColonIdx + 2 < name.length() - && name.charAt(schemeColonIdx + 1) == '/' && name.charAt(schemeColonIdx + 2) == '/') { - return schemeColonIdx + 3; - } else { - return schemeColonIdx + 1; - } - } - } - - private String removeRedundantSlashes(String path) { - String prevName; - do { - prevName = path; - path = StringUtil.replace(path, "//", "/"); - } while (prevName != path); - return path.startsWith("/") ? path.substring(1) : path; - } - - private String removeDotSteps(String path) { - int nextFromIdx = path.length() - 1; - findDotSteps: while (true) { - final int dotIdx = path.lastIndexOf('.', nextFromIdx); - if (dotIdx < 0) { - return path; - } - nextFromIdx = dotIdx - 1; - - if (dotIdx != 0 && path.charAt(dotIdx - 1) != '/') { - // False alarm - continue findDotSteps; - } - - final boolean slashRight; - if (dotIdx + 1 == path.length()) { - slashRight = false; - } else if (path.charAt(dotIdx + 1) == '/') { - slashRight = true; - } else { - // False alarm - continue findDotSteps; - } - - if (slashRight) { // "foo/./bar" or "./bar" - path = path.substring(0, dotIdx) + path.substring(dotIdx + 2); - } else { // "foo/." or "." - path = path.substring(0, path.length() - 1); - } - } - } - - /** - * @param name The original name, needed for exception error messages. - */ - private String resolveDotDotSteps(String path, final String name) throws MalformedTemplateNameException { - int nextFromIdx = 0; - findDotDotSteps: while (true) { - final int dotDotIdx = path.indexOf("..", nextFromIdx); - if (dotDotIdx < 0) { - return path; - } - - if (dotDotIdx == 0) { - throw newRootLeavingException(name); - } else if (path.charAt(dotDotIdx - 1) != '/') { - // False alarm - nextFromIdx = dotDotIdx + 3; - continue findDotDotSteps; - } - // Here we know that it has a preceding "/". - - final boolean slashRight; - if (dotDotIdx + 2 == path.length()) { - slashRight = false; - } else if (path.charAt(dotDotIdx + 2) == '/') { - slashRight = true; - } else { - // False alarm - nextFromIdx = dotDotIdx + 3; - continue findDotDotSteps; - } - - int previousSlashIdx; - boolean skippedStarStep = false; - { - int searchSlashBacwardsFrom = dotDotIdx - 2; // before the "/.." - scanBackwardsForSlash: while (true) { - if (searchSlashBacwardsFrom == -1) { - throw newRootLeavingException(name); - } - previousSlashIdx = path.lastIndexOf('/', searchSlashBacwardsFrom); - if (previousSlashIdx == -1) { - if (searchSlashBacwardsFrom == 0 && path.charAt(0) == '*') { - // "*/.." - throw newRootLeavingException(name); - } - break scanBackwardsForSlash; - } - if (path.charAt(previousSlashIdx + 1) == '*' && path.charAt(previousSlashIdx + 2) == '/') { - skippedStarStep = true; - searchSlashBacwardsFrom = previousSlashIdx - 1; - } else { - break scanBackwardsForSlash; - } - } - } - - // Note: previousSlashIdx is possibly -1 - // Removed part in {}: "a/{b/*/../}c" or "a/{b/*/..}" - path = path.substring(0, previousSlashIdx + 1) - + (skippedStarStep ? "*/" : "") - + path.substring(dotDotIdx + (slashRight ? 3 : 2)); - nextFromIdx = previousSlashIdx + 1; - } - } - - private String removeRedundantStarSteps(String path) { - String prevName; - removeDoubleStarSteps: do { - int supiciousIdx = path.indexOf("*/*"); - if (supiciousIdx == -1) { - break removeDoubleStarSteps; - } - - prevName = path; - - // Is it delimited on both sided by "/" or by the string boundaires? - if ((supiciousIdx == 0 || path.charAt(supiciousIdx - 1) == '/') - && (supiciousIdx + 3 == path.length() || path.charAt(supiciousIdx + 3) == '/')) { - path = path.substring(0, supiciousIdx) + path.substring(supiciousIdx + 2); - } - } while (prevName != path); - - // An initial "*" step is redundant: - if (path.startsWith("*")) { - if (path.length() == 1) { - path = ""; - } else if (path.charAt(1) == '/') { - path = path.substring(2); - } - // else: it's wasn't a "*" step. - } - - return path; - } - - @Override - public String toString() { - return "TemplateNameFormat.DEFAULT_2_4_0"; - } - } + public abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException; - private static void checkNameHasNoNullCharacter(final String name) throws MalformedTemplateNameException { + protected void checkNameHasNoNullCharacter(final String name) throws MalformedTemplateNameException { if (name.indexOf(0) != -1) { throw new MalformedTemplateNameException(name, "Null character (\\u0000) in the name; possible attack attempt"); } } - private static MalformedTemplateNameException newRootLeavingException(final String name) { + protected MalformedTemplateNameException newRootLeavingException(final String name) { return new MalformedTemplateNameException(name, "Backing out from the root directory is not allowed"); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java index 6253354..800b1b8 100644 --- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java +++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java @@ -25,6 +25,7 @@ import org.apache.freemarker.core.Configuration; import org.apache.freemarker.core.Template; import org.apache.freemarker.core.TemplateNotFoundException; import org.apache.freemarker.core.ast.ParseException; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; /** * This class was introduced to allow users to fully implement the template lookup, loading and caching logic, @@ -96,10 +97,10 @@ public abstract class TemplateResolver { * <p> * This method is thread-safe and can be called while the engine processes templates. * - * @throw {@link UnsupportedOperationException} If the {@link TemplateResolver} implementation doesn't support this + * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this * operation. */ - public abstract void clearTemplateCache(); + public abstract void clearTemplateCache() throws UnsupportedOperationException; /** * Removes a template from the template cache, hence forcing the re-loading of it when it's next time requested; @@ -112,11 +113,11 @@ public abstract class TemplateResolver { * <p> * This method is thread-safe and can be called while the engine processes templates. * - * @throw {@link UnsupportedOperationException} If the {@link TemplateResolver} implementation doesn't support this + * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this * operation. */ public abstract void removeTemplateFromCache(String name, Locale locale, String encoding, boolean parse) - throws IOException; + throws IOException, UnsupportedOperationException;; /** * Converts a name to a template root directory based name, so that it can be used to find a template without http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java deleted file mode 100644 index 70cb733..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java +++ /dev/null @@ -1,224 +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.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.Objects; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core._CoreLogs; -import org.slf4j.Logger; - -/** - * This is an abstract template loader that can load templates whose location can be described by an URL. Subclasses - * only need to override the {@link #getURL(String)}, {@link #extractNegativeResult(URLConnection)}, and perhaps the - * {@link #prepareConnection(URLConnection)} method. - */ -// TODO JUnit test (including implementing a HTTP-based template loader to test the new protected methods) -public abstract class URLTemplateLoader implements TemplateLoader { - - private static final Logger LOG = _CoreLogs.TEMPLATE_RESOLVER; - - private Boolean urlConnectionUsesCaches = false; - - /** - * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}. - * - * @since 2.3.21 - */ - public Boolean getURLConnectionUsesCaches() { - return urlConnectionUsesCaches; - } - - /** - * Sets if {@link URLConnection#setUseCaches(boolean)} will be called, and with what value. By default this is - * {@code false}, because FreeMarker has its own template cache with its own update delay setting - * ({@link Configuration#setTemplateUpdateDelay(int)}). If this is set to {@code null}, - * {@link URLConnection#setUseCaches(boolean)} won't be called. - */ - public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) { - this.urlConnectionUsesCaches = urlConnectionUsesCaches; - } - - @Override - public TemplateLoaderSession createSession() { - return null; - } - - @Override - public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, - Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException { - URL url = getURL(name); - if (url == null) { - return TemplateLoadingResult.NOT_FOUND; - } - - URLConnection conn = url.openConnection(); - Boolean urlConnectionUsesCaches = getURLConnectionUsesCaches(); - if (urlConnectionUsesCaches != null) { - conn.setUseCaches(urlConnectionUsesCaches); - } - - prepareConnection(conn); - conn.connect(); - - InputStream inputStream = null; - Long version; - URLTemplateLoadingSource source; - try { - TemplateLoadingResult negativeResult = extractNegativeResult(conn); - if (negativeResult != null) { - return negativeResult; - } - - // To prevent clustering issues, getLastModified(fallbackToJarLMD=false) - long lmd = getLastModified(conn, false); - version = lmd != -1 ? lmd : null; - - source = new URLTemplateLoadingSource(url); - - if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(source) - && Objects.equals(ifVersionDiffersFrom, version)) { - return TemplateLoadingResult.NOT_MODIFIED; - } - - inputStream = conn.getInputStream(); - } catch (Throwable e) { - try { - if (inputStream == null) { - // There's no URLConnection.close(), so we do this hack. In case of HttpURLConnection we could call - // disconnect(), but that's perhaps too aggressive. - conn.getInputStream().close(); - } - } catch (IOException e2) { - LOG.debug("Failed to close connection inputStream", e2); - } - throw e; - } - return new TemplateLoadingResult(source, version, inputStream, null); - } - - @Override - public void resetState() { - // Do nothing - } - - /** - * {@link URLConnection#getLastModified()} with JDK bug workarounds. Because of JDK-6956385, for files inside a jar, - * it returns the last modification time of the jar itself, rather than the last modification time of the file - * inside the jar. - * - * @param fallbackToJarLMD - * Tells if the file is in side jar, then we should return the last modification time of the jar itself, - * or -1 (to work around JDK-6956385). - */ - public static long getLastModified(URLConnection conn, boolean fallbackToJarLMD) throws IOException { - if (conn instanceof JarURLConnection) { - // There is a bug in sun's jar url connection that causes file handle leaks when calling getLastModified() - // (see https://bugs.openjdk.java.net/browse/JDK-6956385). - // Since the time stamps of jar file contents can't vary independent from the jar file timestamp, just use - // the jar file timestamp - if (fallbackToJarLMD) { - URL jarURL = ((JarURLConnection) conn).getJarFileURL(); - if (jarURL.getProtocol().equals("file")) { - // Return the last modified time of the underlying file - saves some opening and closing - return new File(jarURL.getFile()).lastModified(); - } else { - // Use the URL mechanism - URLConnection jarConn = null; - try { - jarConn = jarURL.openConnection(); - return jarConn.getLastModified(); - } finally { - try { - if (jarConn != null) { - jarConn.getInputStream().close(); - } - } catch (IOException e) { - LOG.warn("Failed to close URL connection for: {}", conn, e); - } - } - } - } else { - return -1; - } - } else { - return conn.getLastModified(); - } - } - - /** - * Given a template name (plus potential locale decorations) retrieves an URL that points the template source. - * - * @param name - * the name of the sought template (including the locale decorations, or other decorations the - * {@link TemplateLookupStrategy} uses). - * - * @return An URL that points to the template source, or null if it can be determined that the template source does - * not exist. For many implementations the existence of the template can't be decided at this point, and you - * rely on {@link #extractNegativeResult(URLConnection)} instead. - */ - protected abstract URL getURL(String name); - - /** - * Called before the resource if read, checks if we can immediately return a {@link TemplateLoadingResult#NOT_FOUND} - * or {@link TemplateLoadingResult#NOT_MODIFIED}, or throw an {@link IOException}. For example, for a HTTP-based - * storage, the HTTP response status 404 could result in {@link TemplateLoadingResult#NOT_FOUND}, 304 in - * {@link TemplateLoadingResult#NOT_MODIFIED}, and some others, like 500 in throwing an {@link IOException}. - * - * <p>Some - * implementations rely on {@link #getURL(String)} returning {@code null} when a template is missing, in which case - * this method is certainly not applicable. - */ - protected abstract TemplateLoadingResult extractNegativeResult(URLConnection conn) throws IOException; - - /** - * Called before anything that causes the connection to actually build up. This is where - * {@link URLConnection#setIfModifiedSince(long)} and such can be called if someone overrides this. - * The default implementation in {@link URLTemplateLoader} does nothing. - */ - protected void prepareConnection(URLConnection conn) { - // Does nothing - } - - /** - * Can be used by subclasses to canonicalize URL path prefixes. - * @param prefix the path prefix to canonicalize - * @return the canonicalized prefix. All backslashes are replaced with - * forward slashes, and a trailing slash is appended if the original - * prefix wasn't empty and didn't already end with a slash. - */ - protected static String canonicalizePrefix(String prefix) { - // make it foolproof - prefix = prefix.replace('\\', '/'); - // ensure there's a trailing slash - if (prefix.length() > 0 && !prefix.endsWith("/")) { - prefix += "/"; - } - return prefix; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java b/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java deleted file mode 100644 index 99fbc25..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java +++ /dev/null @@ -1,57 +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.net.URL; - -import org.apache.freemarker.core.util.NullArgumentException; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -@SuppressWarnings("serial") -public class URLTemplateLoadingSource implements TemplateLoadingSource { - - private final URL url; - - public URLTemplateLoadingSource(URL url) { - NullArgumentException.check("url", url); - this.url = url; - } - - public URL getUrl() { - return url; - } - - @Override - public int hashCode() { - return url.hashCode(); - } - - @Override - @SuppressFBWarnings("EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS") - public boolean equals(Object obj) { - return url.equals(obj); - } - - @Override - public String toString() { - return url.toString(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java b/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java deleted file mode 100644 index 9c7e8a8..0000000 --- a/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java +++ /dev/null @@ -1,42 +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 org.apache.freemarker.core.Configuration; - -/** - * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! - * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can - * access things inside this package that users shouldn't. - */ -public final class _TemplateLoaderUtils { - - private _TemplateLoaderUtils() { - // Not meant to be instantiated - } - - public static String getClassNameForToString(TemplateLoader templateLoader) { - final Class<? extends TemplateLoader> tlClass = templateLoader.getClass(); - final Package tlPackage = tlClass.getPackage(); - return tlPackage == Configuration.class.getPackage() || tlPackage == TemplateLoader.class.getPackage() - ? tlClass.getSimpleName() : tlClass.getName(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java new file mode 100644 index 0000000..f77de7c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java @@ -0,0 +1,199 @@ +/* + * 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.impl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.freemarker.core.templateresolver.CacheStorage; +import org.apache.freemarker.core.templateresolver.TemplateLoader; +import org.apache.freemarker.core.templateresolver.TemplateLoaderSession; +import org.apache.freemarker.core.templateresolver.TemplateLoadingResult; +import org.apache.freemarker.core.templateresolver.TemplateLoadingSource; +import org.apache.freemarker.core.util.StringUtil; + +/** + * A {@link TemplateLoader} that uses a {@link Map} with {@code byte[]} as its source of templates. This is similar to + * {@link StringTemplateLoader}, but uses {@code byte[]} instead of {@link String}; see more details there. + * + * <p>Note that {@link ByteArrayTemplateLoader} can't be used with a distributed (cluster-wide) {@link CacheStorage}, + * as it produces {@link TemplateLoadingSource}-s that deliberately throw exception on serialization (because the + * content is only accessible within a single JVM, and is also volatile). + */ +// TODO JUnit tests +public class ByteArrayTemplateLoader implements TemplateLoader { + + private static final AtomicLong INSTANCE_COUNTER = new AtomicLong(); + + private final long instanceId = INSTANCE_COUNTER.get(); + private final AtomicLong templatesRevision = new AtomicLong(); + private final ConcurrentMap<String, ContentHolder> templates = new ConcurrentHashMap<>(); + + /** + * Puts a template into the template loader. The name can contain slashes to denote logical directory structure, but + * must not start with a slash. Each template will get an unique revision number, thus replacing a template will + * cause the template cache to reload it (when the update delay expires). + * + * <p>This method is thread-safe. + * + * @param name + * the name of the template. + * @param content + * the source code of the template. + */ + public void putTemplate(String name, byte[] content) { + templates.put( + name, + new ContentHolder(content, new Source(instanceId, name), templatesRevision.incrementAndGet())); + } + + /** + * Removes the template with the specified name if it was added earlier. + * + * <p> + * This method is thread-safe. + * + * @param name + * Exactly the key with which the template was added. + * + * @return Whether a template was found with the given key (and hence was removed now) + */ + public boolean removeTemplate(String name) { + return templates.remove(name) != null; + } + + @Override + public TemplateLoaderSession createSession() { + return null; + } + + @Override + public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, + Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException { + ContentHolder contentHolder = templates.get(name); + if (contentHolder == null) { + return TemplateLoadingResult.NOT_FOUND; + } else if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(contentHolder.source) + && Objects.equals(ifVersionDiffersFrom, contentHolder.version)) { + return TemplateLoadingResult.NOT_MODIFIED; + } else { + return new TemplateLoadingResult( + contentHolder.source, contentHolder.version, + new ByteArrayInputStream(contentHolder.content), + null); + } + } + + @Override + public void resetState() { + // Do nothing + } + + /** + * Show class name and some details that are useful in template-not-found errors. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(_TemplateLoaderUtils.getClassNameForToString(this)); + sb.append("(Map { "); + int cnt = 0; + for (String name : templates.keySet()) { + cnt++; + if (cnt != 1) { + sb.append(", "); + } + if (cnt > 10) { + sb.append("..."); + break; + } + sb.append(StringUtil.jQuote(name)); + sb.append("=..."); + } + if (cnt != 0) { + sb.append(' '); + } + sb.append("})"); + return sb.toString(); + } + + private static class ContentHolder { + private final byte[] content; + private final Source source; + private final long version; + + public ContentHolder(byte[] content, Source source, long version) { + this.content = content; + this.source = source; + this.version = version; + } + + } + + @SuppressWarnings("serial") + private static class Source implements TemplateLoadingSource { + + private final long instanceId; + private final String name; + + public Source(long instanceId, String name) { + this.instanceId = instanceId; + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (instanceId ^ (instanceId >>> 32)); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Source other = (Source) obj; + if (instanceId != other.instanceId) return false; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + throw new IOException(ByteArrayTemplateLoader.class.getName() + + " sources can't be serialized, as they don't support clustering."); + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java new file mode 100644 index 0000000..9cd8ead --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java @@ -0,0 +1,184 @@ +/* + * 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.impl; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.freemarker.core.templateresolver.TemplateLoader; +import org.apache.freemarker.core.templateresolver.TemplateLoadingResult; +import org.apache.freemarker.core.util.NullArgumentException; +import org.apache.freemarker.core.util.StringUtil; + +/** + * A {@link TemplateLoader} that can load templates from the "classpath". Naturally, it can load from jar files, or from + * anywhere where Java can load classes from. Internally, it uses {@link Class#getResource(String)} or + * {@link ClassLoader#getResource(String)} to load templates. + */ +// TODO +public class ClassTemplateLoader extends URLTemplateLoader { + + private final Class<?> resourceLoaderClass; + private final ClassLoader classLoader; + private final String basePackagePath; + + /** + * Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to + * load the resources, and the specified base package path (absolute or relative). + * + * <p> + * Examples: + * <ul> + * <li>Relative base path (will load from the {@code com.example.myapplication.templates} package):<br> + * {@code new ClassTemplateLoader(com.example.myapplication.SomeClass.class, "templates")} + * <li>Absolute base path:<br> + * {@code new ClassTemplateLoader(somepackage.SomeClass.class, "/com/example/myapplication/templates")} + * </ul> + * + * @param resourceLoaderClass + * The class whose {@link Class#getResource(String)} method will be used to load the templates. Be sure + * that you chose a class whose defining class-loader sees the templates. This parameter can't be + * {@code null}. + * @param basePackagePath + * The package that contains the templates, in path ({@code /}-separated) format. If it doesn't start + * with a {@code /} then it's relative to the path (package) of the {@code resourceLoaderClass} class. If + * it starts with {@code /} then it's relative to the root of the package hierarchy. Note that path + * components should be separated by forward slashes independently of the separator character used by the + * underlying operating system. This parameter can't be {@code null}. + * + * @see #ClassTemplateLoader(ClassLoader, String) + */ + public ClassTemplateLoader(Class<?> resourceLoaderClass, String basePackagePath) { + this(resourceLoaderClass, false, null, basePackagePath); + } + + /** + * Similar to {@link #ClassTemplateLoader(Class, String)}, but instead of {@link Class#getResource(String)} it uses + * {@link ClassLoader#getResource(String)}. Because a {@link ClassLoader} isn't bound to any Java package, it + * doesn't mater if the {@code basePackagePath} starts with {@code /} or not, it will be always relative to the root + * of the package hierarchy + */ + public ClassTemplateLoader(ClassLoader classLoader, String basePackagePath) { + this(null, true, classLoader, basePackagePath); + } + + private ClassTemplateLoader(Class<?> resourceLoaderClass, boolean allowNullResourceLoaderClass, + ClassLoader classLoader, String basePackagePath) { + if (!allowNullResourceLoaderClass) { + NullArgumentException.check("resourceLoaderClass", resourceLoaderClass); + } + NullArgumentException.check("basePackagePath", basePackagePath); + + // Either set a non-null resourceLoaderClass or a non-null classLoader, not both: + this.resourceLoaderClass = classLoader == null ? (resourceLoaderClass == null ? this.getClass() + : resourceLoaderClass) : null; + if (this.resourceLoaderClass == null && classLoader == null) { + throw new NullArgumentException("classLoader"); + } + this.classLoader = classLoader; + + String canonBasePackagePath = canonicalizePrefix(basePackagePath); + if (this.classLoader != null && canonBasePackagePath.startsWith("/")) { + canonBasePackagePath = canonBasePackagePath.substring(1); + } + this.basePackagePath = canonBasePackagePath; + } + + private static boolean isSchemeless(String fullPath) { + int i = 0; + int ln = fullPath.length(); + + // Skip a single initial /, as things like "/file:/..." might work: + if (i < ln && fullPath.charAt(i) == '/') i++; + + // Check if there's no ":" earlier than a '/', as the URLClassLoader + // could interpret that as an URL scheme: + while (i < ln) { + char c = fullPath.charAt(i); + if (c == '/') return true; + if (c == ':') return false; + i++; + } + return true; + } + + /** + * Show class name and some details that are useful in template-not-found errors. + */ + @Override + public String toString() { + return _TemplateLoaderUtils.getClassNameForToString(this) + "(" + + (resourceLoaderClass != null + ? "resourceLoaderClass=" + resourceLoaderClass.getName() + : "classLoader=" + StringUtil.jQuote(classLoader)) + + ", basePackagePath" + + "=" + + StringUtil.jQuote(basePackagePath) + + (resourceLoaderClass != null + ? (basePackagePath.startsWith("/") ? "" : " /* relatively to resourceLoaderClass pkg */") + : "" + ) + + ")"; + } + + /** + * See the similar parameter of {@link #ClassTemplateLoader(Class, String)}; {@code null} when other mechanism is + * used to load the resources. + */ + public Class<?> getResourceLoaderClass() { + return resourceLoaderClass; + } + + /** + * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; {@code null} when other mechanism + * is used to load the resources. + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; note that this is a normalized + * version of what was actually passed to the constructor. + */ + public String getBasePackagePath() { + return basePackagePath; + } + + @Override + protected URL getURL(String name) { + String fullPath = basePackagePath + name; + + // Block java.net.URLClassLoader exploits: + if (basePackagePath.equals("/") && !isSchemeless(fullPath)) { + return null; + } + + return resourceLoaderClass != null ? resourceLoaderClass.getResource(fullPath) : classLoader + .getResource(fullPath); + } + + @Override + protected TemplateLoadingResult extractNegativeResult(URLConnection conn) throws IOException { + return null; + } + +} \ No newline at end of file
