You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/10/14 20:09:30 UTC

incubator-freemarker git commit: Forward ported from 2.3-gae: Resource loading JarURLConnection glitch workarounds. JSP taglib loading thread context class loader usage fix.

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 <dd...@apache.org>
Authored: Sat Oct 14 22:09:25 2017 +0200
Committer: ddekany <dd...@apache.org>
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