http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java new file mode 100644 index 0000000..887bfce --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.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.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.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 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java new file mode 100644 index 0000000..1d0533b --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java @@ -0,0 +1,70 @@ +/* + * 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.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.templateresolver.CacheStorage; +import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize; + +/** + * 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 Configuration#getCacheStorage() + */ +public class StrongCacheStorage implements CacheStorage, CacheStorageWithGetSize { + + private final ConcurrentMap map = new ConcurrentHashMap(); + + @Override + public Object get(Object key) { + return map.get(key); + } + + @Override + public void put(Object key, Object value) { + map.put(key, value); + } + + @Override + public void remove(Object key) { + map.remove(key); + } + + /** + * Returns a close approximation of the number of cache entries. + * + * @since 2.3.21 + */ + @Override + public int getSize() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java new file mode 100644 index 0000000..e9e1c00 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java @@ -0,0 +1,66 @@ +/* + * 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.Serializable; +import java.util.Locale; + +import org.apache.freemarker.core.templateresolver.TemplateLoader; +import org.apache.freemarker.core.templateresolver.TemplateLoadingResult; +import org.apache.freemarker.core.templateresolver.TemplateLoadingSource; +import org.apache.freemarker.core.templateresolver.TemplateLookupContext; + +/** + * Base class for implementing a {@link TemplateLookupContext} that works with {@link TemplateLoader}-s. + */ +public abstract class TemplateLoaderBasedTemplateLookupContext + extends TemplateLookupContext<TemplateLoaderBasedTemplateLookupResult> { + + private final TemplateLoadingSource cachedResultSource; + private final Serializable cachedResultVersion; + + protected TemplateLoaderBasedTemplateLookupContext(String templateName, Locale templateLocale, + Object customLookupCondition, TemplateLoadingSource cachedResultSource, Serializable cachedResultVersion) { + super(templateName, templateLocale, customLookupCondition); + this.cachedResultSource = cachedResultSource; + this.cachedResultVersion = cachedResultVersion; + } + + protected TemplateLoadingSource getCachedResultSource() { + return cachedResultSource; + } + + protected Serializable getCachedResultVersion() { + return cachedResultVersion; + } + + @Override + public final TemplateLoaderBasedTemplateLookupResult createNegativeLookupResult() { + return TemplateLoaderBasedTemplateLookupResult.getNegativeResult(); + } + + /** + * Creates a positive or negative lookup result depending on {@link TemplateLoadingResult#getStatus()}. + */ + protected final TemplateLoaderBasedTemplateLookupResult createLookupResult( + String templateSourceName, TemplateLoadingResult templateLoaderResult) { + return TemplateLoaderBasedTemplateLookupResult.from(templateSourceName, templateLoaderResult); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java new file mode 100644 index 0000000..fe7a54c --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java @@ -0,0 +1,124 @@ +/* + * 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 org.apache.freemarker.core.templateresolver.TemplateLoader; +import org.apache.freemarker.core.templateresolver.TemplateLoadingResult; +import org.apache.freemarker.core.templateresolver.TemplateLoadingResultStatus; +import org.apache.freemarker.core.templateresolver.TemplateLookupResult; +import org.apache.freemarker.core.util._NullArgumentException; + +/** + * Class of {@link TemplateLookupResult} instances created by {@link TemplateLoaderBasedTemplateLookupContext}. To + * invoke instances of this inside your own {@link TemplateLoaderBasedTemplateLookupContext} subclass, call + * {@link TemplateLoaderBasedTemplateLookupContext#createLookupResult(String, TemplateLoadingResult)} and + * {@link TemplateLoaderBasedTemplateLookupContext#createNegativeLookupResult()}. You should not try to invoke instances + * anywhere else. Also, this class deliberately can't be subclassed (except inside FreeMarker). + */ +public abstract class TemplateLoaderBasedTemplateLookupResult extends TemplateLookupResult { + + /** Used internally to get a not-found result (currently just a static singleton). */ + static TemplateLoaderBasedTemplateLookupResult getNegativeResult() { + return NegativeTemplateLookupResult.INSTANCE; + } + + /** Used internally to invoke the appropriate kind of result from the parameters. */ + static TemplateLoaderBasedTemplateLookupResult from(String templateSourceName, TemplateLoadingResult templateLoaderResult) { + return templateLoaderResult.getStatus() != TemplateLoadingResultStatus.NOT_FOUND + ? new PositiveTemplateLookupResult(templateSourceName, templateLoaderResult) + : getNegativeResult(); + } + + private TemplateLoaderBasedTemplateLookupResult() { + // + } + + /** + * Used internally to extract the {@link TemplateLoadingResult}; {@code null} if {@link #isPositive()} is + * {@code false}. + */ + public abstract TemplateLoadingResult getTemplateLoaderResult(); + + private static final class PositiveTemplateLookupResult extends TemplateLoaderBasedTemplateLookupResult { + + 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 + public TemplateLoadingResult getTemplateLoaderResult() { + return templateLoaderResult; + } + + @Override + public boolean isPositive() { + return true; + } + } + + private static final class NegativeTemplateLookupResult extends TemplateLoaderBasedTemplateLookupResult { + + private static final TemplateLoaderBasedTemplateLookupResult.NegativeTemplateLookupResult INSTANCE = new NegativeTemplateLookupResult(); + + private NegativeTemplateLookupResult() { + // nop + } + + @Override + public String getTemplateSourceName() { + return null; + } + + @Override + public TemplateLoadingResult getTemplateLoaderResult() { + return null; + } + + @Override + public boolean isPositive() { + return false; + } + + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java new file mode 100644 index 0000000..dcb9222 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java @@ -0,0 +1,229 @@ +/* + * 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.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.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.templateresolver.TemplateLookupStrategy; +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#getTemplateUpdateDelayMilliseconds()}). 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java new file mode 100644 index 0000000..cbc1080 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java @@ -0,0 +1,58 @@ +/* + * 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.net.URL; + +import org.apache.freemarker.core.templateresolver.TemplateLoadingSource; +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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java new file mode 100644 index 0000000..7511fa3 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java @@ -0,0 +1,43 @@ +/* + * 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 org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.templateresolver.TemplateLoader; + +/** + * 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/package.html ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/package.html new file mode 100644 index 0000000..c595bd8 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/package.html @@ -0,0 +1,26 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> +<p>Template lookup, loading and caching: Standard implementations. This package is part of the +published API, that is, user code can safely depend on it.</p> +</body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/package.html ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/package.html new file mode 100644 index 0000000..dd01586 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/package.html @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<html> +<head> +</head> +<body> +<p>Template lookup, loading, and caching: Base classes/interfaces</p> +</body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/util/BugException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/BugException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/BugException.java new file mode 100644 index 0000000..399778a --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/BugException.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +/** + * An unexpected state was reached that is certainly caused by a bug in FreeMarker. + * + * @since 2.3.21 + */ +public class BugException extends RuntimeException { + + private static final String COMMON_MESSAGE + = "A bug was detected in FreeMarker; please report it with stack-trace"; + + public BugException() { + this((Throwable) null); + } + + public BugException(String message) { + this(message, null); + } + + public BugException(Throwable cause) { + super(COMMON_MESSAGE, cause); + } + + public BugException(String message, Throwable cause) { + super(COMMON_MESSAGE + ": " + message, cause); + } + + public BugException(int value) { + this(String.valueOf(value)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/util/CaptureOutput.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CaptureOutput.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CaptureOutput.java new file mode 100644 index 0000000..93109e0 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CaptureOutput.java @@ -0,0 +1,147 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateTransformModel; +import org.apache.freemarker.core.model.impl.SimpleScalar; + +/** + * A transform that captures the output of a block of FTL code and stores that in a variable. + * + * <p>As this transform is initially present in the shared variable set, you can always + * access it from the templates:</p> + * + * <pre> + * <@capture_output var="captured"> + * ... + * </@capture_output> + * </pre> + * + * <p>And later in the template you can use the captured output:</p> + * + * ${captured} + * + * <p>This transform requires one of three parameters: <code>var</code>, <code>local</code>, or <code>global</code>. + * Each of them specifies the name of the variable that stores the captured output, but the first creates a + * variable in a name-space (as <#assign>), the second creates a macro-local variable (as <#local>), + * and the last creates a global variable (as <#global>). + * </p> + * <p>In the case of an assignment within a namespace, there is an optional parameter + * <code>namespace</code> that indicates in which namespace to do the assignment. + * if this is omitted, the current namespace is used, and this will be, by far, the most + * common usage pattern.</p> + * + * @deprecated Use block-assignments instead, like <code><assign x>...</assign></code>. + */ +@Deprecated +public class CaptureOutput implements TemplateTransformModel { + + @Override + public Writer getWriter(final Writer out, final Map args) throws TemplateModelException { + String errmsg = "Must specify the name of the variable in " + + "which to capture the output with the 'var' or 'local' or 'global' parameter."; + if (args == null) throw new TemplateModelException(errmsg); + + boolean local = false, global = false; + final TemplateModel nsModel = (TemplateModel) args.get("namespace"); + Object varNameModel = args.get("var"); + if (varNameModel == null) { + varNameModel = args.get("local"); + if (varNameModel == null) { + varNameModel = args.get("global"); + global = true; + } else { + local = true; + } + if (varNameModel == null) { + throw new TemplateModelException(errmsg); + } + } + if (args.size() == 2) { + if (nsModel == null) { + throw new TemplateModelException("Second parameter can only be namespace"); + } + if (local) { + throw new TemplateModelException("Cannot specify namespace for a local assignment"); + } + if (global) { + throw new TemplateModelException("Cannot specify namespace for a global assignment"); + } + if (!(nsModel instanceof Environment.Namespace)) { + throw new TemplateModelException("namespace parameter does not specify a namespace. It is a " + nsModel.getClass().getName()); + } + } else if (args.size() != 1) throw new TemplateModelException( + "Bad parameters. Use only one of 'var' or 'local' or 'global' parameters."); + + if (!(varNameModel instanceof TemplateScalarModel)) { + throw new TemplateModelException("'var' or 'local' or 'global' parameter doesn't evaluate to a string"); + } + final String varName = ((TemplateScalarModel) varNameModel).getAsString(); + if (varName == null) { + throw new TemplateModelException("'var' or 'local' or 'global' parameter evaluates to null string"); + } + + final StringBuilder buf = new StringBuilder(); + final Environment env = Environment.getCurrentEnvironment(); + final boolean localVar = local; + final boolean globalVar = global; + + return new Writer() { + + @Override + public void write(char cbuf[], int off, int len) { + buf.append(cbuf, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + SimpleScalar result = new SimpleScalar(buf.toString()); + try { + if (localVar) { + env.setLocalVariable(varName, result); + } else if (globalVar) { + env.setGlobalVariable(varName, result); + } else { + if (nsModel == null) { + env.setVariable(varName, result); + } else { + ((Environment.Namespace) nsModel).put(varName, result); + } + } + } catch (java.lang.IllegalStateException ise) { // if somebody uses 'local' outside a macro + throw new IOException("Could not set variable " + varName + ": " + ise.getMessage()); + } + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java new file mode 100644 index 0000000..11aab33 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java @@ -0,0 +1,35 @@ +/* + * 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.util; + +import org.apache.freemarker.core.ConfigurationException; + +/** + * Interface of builders (used for implementing the builder pattern). + */ +public interface CommonBuilder<ProductT> { + + /** + * Creates an instance of the product class. This is usually a new instance, though if the product is stateless, + * it's possibly a shared object instead of a new one. + */ + ProductT build() throws ConfigurationException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java new file mode 100644 index 0000000..d2e361e --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java @@ -0,0 +1,153 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.WrapperTemplateModel; + +/** + * Utility methods for unwrapping {@link TemplateModel}-s. + */ +public class DeepUnwrap { + private static final Class OBJECT_CLASS = Object.class; + /** + * Unwraps {@link TemplateModel}-s recursively. + * The converting of the {@link TemplateModel} object happens with the following rules: + * <ol> + * <li>If the object implements {@link AdapterTemplateModel}, then the result + * of {@link AdapterTemplateModel#getAdaptedObject(Class)} for <tt>Object.class</tt> is returned. + * <li>If the object implements {@link WrapperTemplateModel}, then the result + * of {@link WrapperTemplateModel#getWrappedObject()} is returned. + * <li>If the object is identical to the null model of the current object + * wrapper, null is returned. + * <li>If the object implements {@link TemplateScalarModel}, then the result + * of {@link TemplateScalarModel#getAsString()} is returned. + * <li>If the object implements {@link TemplateNumberModel}, then the result + * of {@link TemplateNumberModel#getAsNumber()} is returned. + * <li>If the object implements {@link TemplateDateModel}, then the result + * of {@link TemplateDateModel#getAsDate()} is returned. + * <li>If the object implements {@link TemplateBooleanModel}, then the result + * of {@link TemplateBooleanModel#getAsBoolean()} is returned. + * <li>If the object implements {@link TemplateSequenceModel} or + * {@link TemplateCollectionModel}, then a <code>java.util.ArrayList</code> is + * constructed from the subvariables, and each subvariable is unwrapped with + * the rules described here (recursive unwrapping). + * <li>If the object implements {@link TemplateHashModelEx}, then a + * <code>java.util.HashMap</code> is constructed from the subvariables, and each + * subvariable is unwrapped with the rules described here (recursive unwrapping). + * <li>Throw a <code>TemplateModelException</code>, because it doesn't know how to + * unwrap the object. + * </ol> + */ + public static Object unwrap(TemplateModel model) throws TemplateModelException { + return unwrap(model, false); + } + + /** + * Same as {@link #unwrap(TemplateModel)}, but it doesn't throw exception + * if it doesn't know how to unwrap the model, but rather returns it as-is. + * @since 2.3.14 + */ + public static Object permissiveUnwrap(TemplateModel model) throws TemplateModelException { + return unwrap(model, true); + } + + private static Object unwrap(TemplateModel model, boolean permissive) throws TemplateModelException { + Environment env = Environment.getCurrentEnvironment(); + TemplateModel nullModel = null; + if (env != null) { + ObjectWrapper wrapper = env.getObjectWrapper(); + if (wrapper != null) { + nullModel = wrapper.wrap(null); + } + } + return unwrap(model, nullModel, permissive); + } + + private static Object unwrap(TemplateModel model, TemplateModel nullModel, boolean permissive) throws TemplateModelException { + if (model instanceof AdapterTemplateModel) { + return ((AdapterTemplateModel) model).getAdaptedObject(OBJECT_CLASS); + } + if (model instanceof WrapperTemplateModel) { + return ((WrapperTemplateModel) model).getWrappedObject(); + } + if (model == nullModel) { + return null; + } + if (model instanceof TemplateScalarModel) { + return ((TemplateScalarModel) model).getAsString(); + } + if (model instanceof TemplateNumberModel) { + return ((TemplateNumberModel) model).getAsNumber(); + } + if (model instanceof TemplateDateModel) { + return ((TemplateDateModel) model).getAsDate(); + } + if (model instanceof TemplateBooleanModel) { + return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); + } + if (model instanceof TemplateSequenceModel) { + TemplateSequenceModel seq = (TemplateSequenceModel) model; + ArrayList list = new ArrayList(seq.size()); + for (int i = 0; i < seq.size(); ++i) { + list.add(unwrap(seq.get(i), nullModel, permissive)); + } + return list; + } + if (model instanceof TemplateCollectionModel) { + TemplateCollectionModel coll = (TemplateCollectionModel) model; + ArrayList list = new ArrayList(); + TemplateModelIterator it = coll.iterator(); + while (it.hasNext()) { + list.add(unwrap(it.next(), nullModel, permissive)); + } + return list; + } + if (model instanceof TemplateHashModelEx) { + TemplateHashModelEx hash = (TemplateHashModelEx) model; + HashMap map = new HashMap(); + TemplateModelIterator keys = hash.keys().iterator(); + while (keys.hasNext()) { + String key = (String) unwrap(keys.next(), nullModel, permissive); + map.put(key, unwrap(hash.get(key), nullModel, permissive)); + } + return map; + } + if (permissive) { + return model; + } + throw new TemplateModelException("Cannot deep-unwrap model of type " + model.getClass().getName()); + } +} \ No newline at end of file
