]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Fixed org.simantics.lz4 to use bundle data area when running in OSGi 49/1149/3
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 27 Oct 2017 13:06:18 +0000 (16:06 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 27 Oct 2017 13:10:15 +0000 (16:10 +0300)
Previously org.simantics.lz4 would always extract the native LZ4 dynamic
library to java.io.tmpdir which is plain stupid. Now when running in
OSGi environment, we simply extract the file once to the data area and
reuse it from there.

Further development could even use the MD5 sum of the lz4 DLL to extract
every version of the DLL only once to java.io.tmpdir but I'm saving this
for later.

refs #7578

Change-Id: I50566999749b20cca6d56ab52e59101ec3a7b6f6

bundles/org.simantics.lz4/META-INF/MANIFEST.MF
bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4Factory.java
bundles/org.simantics.lz4/src/net/jpountz/util/ChecksumUtil.java [new file with mode: 0644]
bundles/org.simantics.lz4/src/net/jpountz/util/Native.java
bundles/org.simantics.lz4/src/net/jpountz/util/NativeParameters.java [new file with mode: 0644]
bundles/org.simantics.lz4/src/net/jpountz/xxhash/XXHashFactory.java
bundles/org.simantics.lz4/src/org/simantics/lz4/bundle/Activator.java

index 1c9ad100103c5cc51d1cb98444cd52ae80654163..08edb0c2192b6570b30203a95bfc2a2d088f6e7c 100644 (file)
@@ -14,3 +14,4 @@ Require-Bundle: org.eclipse.osgi,
  org.eclipse.osgi.services;bundle-version="3.3.0"
 Service-Component: OSGI-INF/component.xml
 Bundle-ActivationPolicy: lazy
+Bundle-Activator: org.simantics.lz4.bundle.Activator
index 7d1cf271e7abff138db6d84e9e7622440afb7843..668d65801381cadb89daa64c4b46a969b042f3b8 100644 (file)
@@ -132,8 +132,9 @@ public final class LZ4Factory {
    * using this method.
    */
   public static LZ4Factory fastestInstance() {
-    if (Native.isLoaded()
-        || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader()) {
+    if (!Native.failedToLoad()
+        && (Native.isLoaded()
+            || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader())) {
       try {
         return nativeInstance();
       } catch (Throwable t) {
diff --git a/bundles/org.simantics.lz4/src/net/jpountz/util/ChecksumUtil.java b/bundles/org.simantics.lz4/src/net/jpountz/util/ChecksumUtil.java
new file mode 100644 (file)
index 0000000..c047f21
--- /dev/null
@@ -0,0 +1,49 @@
+package net.jpountz.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public final class ChecksumUtil {
+
+    private static byte[] computeSum(InputStream in) throws IOException {
+        if (in == null)
+            throw new IllegalArgumentException("Input cannot be null!");
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new Error("MD5 digest must be supported by JVM");
+        }
+        byte[] data = new byte[64 * 1024];
+
+        while (true) {
+            int read = in.read(data);
+            if (read == -1) {
+                return md.digest();
+            }
+            md.update(data, 0, read);
+        }
+    }
+
+    public static byte[] computeSum(Path p) throws IOException {
+        try (InputStream in = Files.newInputStream(p)) {
+            return computeSum(in);
+        }
+    }
+
+    public static byte[] computeSum(URL url) throws IOException {
+        try (InputStream in = url.openStream()) {
+            return computeSum(in);
+        }
+    }
+
+}
index c793f89fd1049157dae96b0304a189bb325655f7..b14b0728f0118bf1056a45b0d5eb24b8135f581e 100644 (file)
@@ -1,6 +1,4 @@
-package net.jpountz.util;
-
-/*
+/**
  * Licensed 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
@@ -12,12 +10,18 @@ package net.jpountz.util;
  * 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.
+ *
+ * Modified by Tuukka Lehtonen - Semantum Oy
+ *  - allow extracting native library to designated directory
  */
+package net.jpountz.util;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
 
 /** FOR INTERNAL USE ONLY */
 public enum Native {
@@ -62,61 +66,72 @@ public enum Native {
   }
 
   private static boolean loaded = false;
+  private static boolean failedToLoad = false;
 
   public static synchronized boolean isLoaded() {
     return loaded;
   }
 
+  public static boolean failedToLoad() {
+    return failedToLoad;
+  }
+
   public static synchronized void load() {
-    if (loaded) {
+    load(NativeParameters.extractionPath);
+  }
+
+  public static synchronized void load(Path extractionPath) {
+    if (loaded)
       return;
-    }
+    if (failedToLoad)
+      throw new UnsatisfiedLinkError("Native LZ4 dynamic library loading failed already. Not retrying.");
+
     String resourceName = resourceName();
-    InputStream is = Native.class.getResourceAsStream(resourceName);
-    if (is == null) {
+    URL libraryUrl = Native.class.getResource(resourceName);
+    if (libraryUrl == null)
       throw new UnsupportedOperationException("Unsupported OS/arch, cannot find " + resourceName + ". Please try building from source.");
-    }
-    File tempLib;
+
     try {
-      tempLib = File.createTempFile("liblz4-java", "." + os().libExtension);
-      // copy to tempLib
-      FileOutputStream out = new FileOutputStream(tempLib);
+      Path tempFile = null, out;
+      boolean extract = true;
+
       try {
-        byte[] buf = new byte[4096];
-        while (true) {
-          int read = is.read(buf);
-          if (read == -1) {
-            break;
+        if (extractionPath == null) {
+          out = tempFile = Files.createTempFile("liblz4-java", "." + os().libExtension);
+        } else {
+          out = extractionPath.resolve("liblz4-java." + os().libExtension);
+          if (Files.exists(out)) {
+            byte[] sourceHash = ChecksumUtil.computeSum(libraryUrl);
+            byte[] targetHash = ChecksumUtil.computeSum(out);
+            extract = !Arrays.equals(sourceHash, targetHash);
           }
-          out.write(buf, 0, read);
         }
-        try {
-          out.close();
-          out = null;
-        } catch (IOException e) {
-          // ignore
-        }
-        System.load(tempLib.getAbsolutePath());
+        if (extract)
+          copy(libraryUrl, out);
+        System.load(out.toString());
         loaded = true;
       } finally {
-        try {
-          if (out != null) {
-            out.close();
-          }
-        } catch (IOException e) {
-          // ignore
-        }
-        if (tempLib != null && tempLib.exists()) {
+        if (tempFile != null && Files.exists(tempFile)) {
           if (!loaded) {
-            tempLib.delete();
+            Files.deleteIfExists(tempFile);
           } else {
-            // try to delete on exit, does it work on Windows?
-            tempLib.deleteOnExit();
+            // This probably doesn't work on Windows at all.
+            tempFile.toFile().deleteOnExit();
           }
         }
       }
+    } catch (UnsatisfiedLinkError e) {
+      failedToLoad = true;
+      throw e;
     } catch (IOException e) {
-        throw new ExceptionInInitializerError("Cannot unpack liblz4-java");
+      failedToLoad = true;
+      throw new ExceptionInInitializerError("Cannot unpack liblz4-java");
+    }
+  }
+
+  private static long copy(URL url, Path to) throws IOException {
+    try (InputStream in = url.openStream()) {
+      return Files.copy(in, to);
     }
   }
 
diff --git a/bundles/org.simantics.lz4/src/net/jpountz/util/NativeParameters.java b/bundles/org.simantics.lz4/src/net/jpountz/util/NativeParameters.java
new file mode 100644 (file)
index 0000000..a009f96
--- /dev/null
@@ -0,0 +1,25 @@
+package net.jpountz.util;
+
+import java.nio.file.Path;
+
+import org.simantics.lz4.bundle.Activator;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class NativeParameters {
+
+    /**
+     * The path that the native LZ4 dynamic library shall be extracted to if
+     * <code>non-null</code>. If <code>null</code>, class {@link Native} will pick a
+     * temporary file from the default temporary directory to extract the library
+     * to and load it from there.
+     *
+     * <p>
+     * This value must be set before {@link Native#load(Path)} is invoked.
+     *
+     * @see Activator
+     */
+    public static Path extractionPath;
+
+}
index ab03dff1fcd81eb07b7a52f2ae92a499f600e63a..d9b199bd5fea4327b489ee9c38b2f4147336598e 100644 (file)
@@ -126,8 +126,9 @@ public final class XXHashFactory {
    * using this method.
    */
   public static XXHashFactory fastestInstance() {
-    if (Native.isLoaded()
-        || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader()) {
+    if (!Native.failedToLoad()
+        && (Native.isLoaded()
+            || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader())) {
       try {
         return nativeInstance();
       } catch (Throwable t) {
index 38f3c3bd1527d7e1332f3bab392c6d338029923a..3b1c07576421d5ceca612b1d3f7709f2431b1f5a 100644 (file)
@@ -1,22 +1,26 @@
 package org.simantics.lz4.bundle;
 
-import net.jpountz.util.Native;
+import java.io.File;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
+import net.jpountz.util.Native;
+import net.jpountz.util.NativeParameters;
+
 public class Activator implements BundleActivator {
 
     public static final String PLUGIN_ID = "org.simantics.lz4";
-    
+
     @Override
     public void start(BundleContext context) throws Exception {
+        File dataArea = context.getDataFile("");
+        NativeParameters.extractionPath = dataArea != null ? dataArea.toPath() : null;
         Native.load();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        
     }
 
 }