Repository: incubator-freemarker Updated Branches: refs/heads/3 121b11b51 -> 22ecffb4b
Forward ported from 2.3-gae: Resource loading JarURLConnection glitch workarounds. JSP taglib loading thread context class loader usage fix. Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/22ecffb4 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/22ecffb4 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/22ecffb4 Branch: refs/heads/3 Commit: 22ecffb4bc5140b8f300b944dd12976694808fd5 Parents: 121b11b Author: ddekany <[email protected]> Authored: Sat Oct 14 22:09:25 2017 +0200 Committer: ddekany <[email protected]> Committed: Sat Oct 14 22:09:25 2017 +0200 ---------------------------------------------------------------------- .../apache/freemarker/core/Configuration.java | 28 ++--- .../org/apache/freemarker/core/Version.java | 1 + .../core/model/impl/UnsafeMethods.java | 38 ++---- .../freemarker/core/util/_ClassUtils.java | 117 +++++++++++++++++++ .../freemarker/servlet/jsp/TaglibFactory.java | 31 ++--- 5 files changed, 149 insertions(+), 66 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22ecffb4/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java index 9f4d04b..f0f3285 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java @@ -22,7 +22,6 @@ package org.apache.freemarker.core; import static org.apache.freemarker.core.Configuration.ExtendableBuilder.*; import java.io.IOException; -import java.io.InputStream; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -131,7 +130,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; */ public final class Configuration implements TopLevelConfiguration, CustomStateScope { - private static final String VERSION_PROPERTIES_PATH = "org/apache/freemarker/core/version.properties"; + private static final String VERSION_PROPERTIES_PATH = "/org/apache/freemarker/core/version.properties"; private static final Map<String, OutputFormat> STANDARD_OUTPUT_FORMATS; static { @@ -156,24 +155,10 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc private static final Version VERSION; static { try { - Properties vp = new Properties(); - InputStream ins = Configuration.class.getClassLoader() - .getResourceAsStream(VERSION_PROPERTIES_PATH); - if (ins == null) { - throw new RuntimeException("Version file is missing."); - } else { - try { - vp.load(ins); - } finally { - ins.close(); - } - - String versionString = getRequiredVersionProperty(vp, "version"); - - final Boolean gaeCompliant = Boolean.valueOf(getRequiredVersionProperty(vp, "isGAECompliant")); - - VERSION = new Version(versionString, gaeCompliant, null); - } + Properties props = _ClassUtils.loadProperties(Configuration.class, VERSION_PROPERTIES_PATH); + String versionString = getRequiredVersionProperty(props, "version"); + final Boolean gaeCompliant = Boolean.valueOf(getRequiredVersionProperty(props, "isGAECompliant")); + VERSION = new Version(versionString, gaeCompliant, null); } catch (IOException e) { throw new RuntimeException("Failed to load and parse " + VERSION_PROPERTIES_PATH, e); } @@ -777,6 +762,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc return (MarkupOutputFormat) of; } + @Override public Collection<OutputFormat> getRegisteredCustomOutputFormats() { return registeredCustomOutputFormats; } @@ -2215,11 +2201,13 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc localizedTemplateLookupSet = false; } + @Override public Collection<OutputFormat> getRegisteredCustomOutputFormats() { return isRegisteredCustomOutputFormatsSet() ? registeredCustomOutputFormats : getDefaultRegisteredCustomOutputFormats(); } + @Override public boolean isRegisteredCustomOutputFormatsSet() { return registeredCustomOutputFormats != null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22ecffb4/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java index da7e1fc..55793ac 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java @@ -53,6 +53,7 @@ public final class Version implements Serializable { this(stringValue, null, null); } + // TODO [FM3] gaeCompliant should be removed /** * @throws IllegalArgumentException if the version string is malformed */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22ecffb4/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java index f1b54bf..54771cd 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java @@ -19,11 +19,9 @@ package org.apache.freemarker.core.model.impl; -import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -37,7 +35,7 @@ class UnsafeMethods { private static final Logger LOG = LoggerFactory.getLogger(UnsafeMethods.class); private static final String UNSAFE_METHODS_PROPERTIES = "unsafeMethods.properties"; - private static final Set UNSAFE_METHODS = createUnsafeMethodsSet(); + private static final Set<Method> UNSAFE_METHODS = createUnsafeMethodsSet(); private UnsafeMethods() { } @@ -45,37 +43,25 @@ class UnsafeMethods { return UNSAFE_METHODS.contains(method); } - private static Set createUnsafeMethodsSet() { - Properties props = new Properties(); - InputStream in = DefaultObjectWrapper.class.getResourceAsStream(UNSAFE_METHODS_PROPERTIES); - if (in == null) { - throw new IllegalStateException("Class loader resource not found: " - + DefaultObjectWrapper.class.getPackage().getName() + UNSAFE_METHODS_PROPERTIES); - } - String methodSpec = null; + private static Set<Method> createUnsafeMethodsSet() { try { - try { - props.load(in); - } finally { - in.close(); - } - Set set = new HashSet(props.size() * 4 / 3, 1f); - Map primClasses = createPrimitiveClassesMap(); - for (Iterator iterator = props.keySet().iterator(); iterator.hasNext(); ) { - methodSpec = (String) iterator.next(); + Properties props = _ClassUtils.loadProperties(DefaultObjectWrapper.class, UNSAFE_METHODS_PROPERTIES); + Set<Method> set = new HashSet<>(props.size() * 4 / 3, 1f); + Map<String, Class<?>> primClasses = createPrimitiveClassesMap(); + for (Object key : props.keySet()) { try { - set.add(parseMethodSpec(methodSpec, primClasses)); + set.add(parseMethodSpec((String) key, primClasses)); } catch (ClassNotFoundException | NoSuchMethodException e) { LOG.debug("Failed to get unsafe method", e); } } return set; } catch (Exception e) { - throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.getClass().getName() + " " + e.getMessage()); + throw new RuntimeException("Could not load unsafe method set", e); } } - private static Method parseMethodSpec(String methodSpec, Map primClasses) + private static Method parseMethodSpec(String methodSpec, Map<String, Class<?>> primClasses) throws ClassNotFoundException, NoSuchMethodException { int brace = methodSpec.indexOf('('); @@ -88,7 +74,7 @@ class UnsafeMethods { Class[] argTypes = new Class[argcount]; for (int i = 0; i < argcount; i++) { String argClassName = tok.nextToken(); - argTypes[i] = (Class) primClasses.get(argClassName); + argTypes[i] = primClasses.get(argClassName); if (argTypes[i] == null) { argTypes[i] = _ClassUtils.forName(argClassName); } @@ -96,8 +82,8 @@ class UnsafeMethods { return clazz.getMethod(methodName, argTypes); } - private static Map createPrimitiveClassesMap() { - Map map = new HashMap(); + private static Map<String, Class<?>> createPrimitiveClassesMap() { + Map<String, Class<?>> map = new HashMap<>(); map.put("boolean", Boolean.TYPE); map.put("byte", Byte.TYPE); map.put("char", Character.TYPE); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22ecffb4/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ClassUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ClassUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ClassUtils.java index d229c2d..21e62c0 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ClassUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ClassUtils.java @@ -19,6 +19,11 @@ package org.apache.freemarker.core.util; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; + import org.apache.freemarker.core.model.impl.BeanModel; public class _ClassUtils { @@ -164,5 +169,117 @@ public class _ClassUtils { return Number.class.isAssignableFrom(type) || type.isPrimitive() && type != Boolean.TYPE && type != Character.TYPE && type != Void.TYPE; } + + /** + * Very similar to {@link Class#getResourceAsStream(String)}, but throws {@link IOException} instead of returning + * {@code null} if {@code optional} is {@code false}, and attempts to work around "IllegalStateException: zip file + * closed" and similar {@code sun.net.www.protocol.jar.JarURLConnection}-related glitches. These are caused by bugs + * outside of FreeMarker. Note that in cases where the JAR resource becomes broken concurrently, similar errors can + * still occur later when the {@link InputStream} is read ({@link #loadProperties(Class, String)} works that + * around as well). + * + * @return If {@code optional} is {@code false}, it's never {@code null}, otherwise {@code null} indicates that the + * resource doesn't exist. + * @throws IOException + * If the resource wasn't found, or other {@link IOException} occurs. + */ + public static InputStream getReasourceAsStream(Class<?> baseClass, String resource, boolean optional) + throws IOException { + InputStream ins; + try { + // This is how we did this earlier. May uses some JarURLConnection caches, which leads to the problems. + ins = baseClass.getResourceAsStream(resource); + } catch (Exception e) { + // Workaround for "IllegalStateException: zip file closed", and other related exceptions. This happens due + // to bugs outside of FreeMarker, but we try to work it around anyway. + URL url = baseClass.getResource(resource); + ins = url != null ? url.openStream() : null; + } + if (!optional) { + checkInputStreamNotNull(ins, baseClass, resource); + } + return ins; + } + + /** + * Same as {@link #getReasourceAsStream(Class, String, boolean)}, but uses a {@link ClassLoader} directly + * instead of a {@link Class}. + */ + public static InputStream getReasourceAsStream(ClassLoader classLoader, String resource, boolean optional) + throws IOException { + // See source commends in the other overload of this method. + InputStream ins; + try { + ins = classLoader.getResourceAsStream(resource); + } catch (Exception e) { + URL url = classLoader.getResource(resource); + ins = url != null ? url.openStream() : null; + } + if (ins == null && !optional) { + throw new IOException("Class-loader resource not found (shown quoted): " + + _StringUtils.jQuote(resource) + ". The base ClassLoader was: " + classLoader); + } + return ins; + } + + /** + * Loads a class loader resource into a {@link Properties}; tries to work around "zip file closed" and related + * {@code sun.net.www.protocol.jar.JarURLConnection} glitches. + */ + public static Properties loadProperties(Class<?> baseClass, String resource) throws IOException { + Properties props = new Properties(); + + InputStream ins = null; + try { + try { + // This is how we did this earlier. May uses some JarURLConnection caches, which leads to the problems. + ins = baseClass.getResourceAsStream(resource); + } catch (Exception e) { + throw new MaybeZipFileClosedException(); + } + checkInputStreamNotNull(ins, baseClass, resource); + try { + props.load(ins); + } catch (Exception e) { + throw new MaybeZipFileClosedException(); + } finally { + try { + ins.close(); + } catch (Exception e) { + // Do nothing to suppress "ZipFile closed" and related exceptions. + } + ins = null; + } + } catch (MaybeZipFileClosedException e) { + // Workaround for "zip file closed" exception, and other related exceptions. This happens due to bugs + // outside of FreeMarker, but we try to work it around anyway. + URL url = baseClass.getResource(resource); + ins = url != null ? url.openStream() : null; + checkInputStreamNotNull(ins, baseClass, resource); + props.load(ins); + } finally { + if (ins != null) { + try { + ins.close(); + } catch (Exception e) { + // Do nothing to suppress "ZipFile closed" and related exceptions. + } + } + } + return props; + } + + private static void checkInputStreamNotNull(InputStream ins, Class<?> baseClass, String resource) + throws IOException { + if (ins == null) { + throw new IOException("Class-loader resource not found (shown quoted): " + + _StringUtils.jQuote(resource) + ". The base class was " + baseClass.getName() + "."); + } + } + + /** Used internally to work around some JarURLConnection glitches */ + private static class MaybeZipFileClosedException extends Exception { + // + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22ecffb4/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java index 26231f9..9d113bb 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java @@ -239,7 +239,7 @@ public class TaglibFactory implements TemplateHashModel { public TemplateModel get(final String taglibUri) throws TemplateException { synchronized (lock) { { - final Taglib taglib = (Taglib) taglibs.get(taglibUri); + final Taglib taglib = taglibs.get(taglibUri); if (taglib != null) { return taglib; } @@ -296,7 +296,7 @@ public class TaglibFactory implements TemplateHashModel { } if (!normalizedTaglibUri.equals(taglibUri)) { - final Taglib taglib = (Taglib) taglibs.get(normalizedTaglibUri); + final Taglib taglib = taglibs.get(normalizedTaglibUri); if (taglib != null) { return taglib; } @@ -484,7 +484,7 @@ public class TaglibFactory implements TemplateHashModel { } for (int srcIdx = srcIdxStart; srcIdx < metaInfTldSources.size(); srcIdx++) { - MetaInfTldSource miTldSource = (MetaInfTldSource) metaInfTldSources.get(srcIdx); + MetaInfTldSource miTldSource = metaInfTldSources.get(srcIdx); if (miTldSource == WebInfPerLibJarMetaInfTldSource.INSTANCE) { addTldLocationsFromWebInfPerLibJarMetaInfTlds(); @@ -609,7 +609,7 @@ public class TaglibFactory implements TemplateHashModel { } for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) { - final JarEntry curEntry = (JarEntry) entries.nextElement(); + final JarEntry curEntry = entries.nextElement(); final String curEntryPath = normalizeJarEntryPath(curEntry.getName(), false); if (curEntryPath.startsWith(metaInfEntryPath) && curEntryPath.endsWith(".tld")) { addTldLocationFromTld(new ServletContextJarEntryTldLocation(jarResourcePath, curEntryPath)); @@ -700,7 +700,7 @@ public class TaglibFactory implements TemplateHashModel { } for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) { - final JarEntry curEntry = (JarEntry) entries.nextElement(); + final JarEntry curEntry = entries.nextElement(); final String curEntryPath = normalizeJarEntryPath(curEntry.getName(), false); if (curEntryPath.startsWith(baseEntryPath) && curEntryPath.endsWith(".tld")) { @@ -1286,25 +1286,20 @@ public class TaglibFactory implements TemplateHashModel { public InputStream getInputStream() throws IOException { ClassLoader tccl = tryGetThreadContextClassLoader(); if (tccl != null) { - final InputStream in = getClass().getResourceAsStream(resourcePath); - if (in != null) { - return in; + InputStream ins = _ClassUtils.getReasourceAsStream(tccl, resourcePath, true); + if (ins != null) { + return ins; } } - final InputStream in = getClass().getResourceAsStream(resourcePath); - if (in == null) { - throw newResourceNotFoundException(); - } - - return in; + return _ClassUtils.getReasourceAsStream(getClass(), resourcePath, false); } @Override public String getXmlSystemId() throws IOException { ClassLoader tccl = tryGetThreadContextClassLoader(); if (tccl != null) { - final URL url = getClass().getResource(resourcePath); + final URL url = tccl.getResource(resourcePath); if (url != null) { return url.toExternalForm(); } @@ -1314,10 +1309,6 @@ public class TaglibFactory implements TemplateHashModel { return url == null ? null : url.toExternalForm(); } - private IOException newResourceNotFoundException() { - return new IOException("Resource not found: classpath:" + resourcePath); - } - } private abstract class JarEntryTldLocation implements TldLocation { @@ -1509,7 +1500,7 @@ public class TaglibFactory implements TemplateHashModel { @Override public TemplateModel get(String key) { - return (TemplateModel) tagsAndFunctions.get(key); + return tagsAndFunctions.get(key); } @Override
