From ec2c82bad3ddb65bf45cb1e35da996620f5a7304 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Fri, 27 Oct 2017 16:06:18 +0300 Subject: [PATCH] Fixed org.simantics.lz4 to use bundle data area when running in OSGi 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 --- .../org.simantics.lz4/META-INF/MANIFEST.MF | 1 + .../src/net/jpountz/lz4/LZ4Factory.java | 5 +- .../src/net/jpountz/util/ChecksumUtil.java | 49 ++++++++++ .../src/net/jpountz/util/Native.java | 93 +++++++++++-------- .../net/jpountz/util/NativeParameters.java | 25 +++++ .../src/net/jpountz/xxhash/XXHashFactory.java | 5 +- .../org/simantics/lz4/bundle/Activator.java | 10 +- 7 files changed, 142 insertions(+), 46 deletions(-) create mode 100644 bundles/org.simantics.lz4/src/net/jpountz/util/ChecksumUtil.java create mode 100644 bundles/org.simantics.lz4/src/net/jpountz/util/NativeParameters.java diff --git a/bundles/org.simantics.lz4/META-INF/MANIFEST.MF b/bundles/org.simantics.lz4/META-INF/MANIFEST.MF index 1c9ad1001..08edb0c21 100644 --- a/bundles/org.simantics.lz4/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.lz4/META-INF/MANIFEST.MF @@ -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 diff --git a/bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4Factory.java b/bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4Factory.java index 7d1cf271e..668d65801 100644 --- a/bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4Factory.java +++ b/bundles/org.simantics.lz4/src/net/jpountz/lz4/LZ4Factory.java @@ -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 index 000000000..c047f2122 --- /dev/null +++ b/bundles/org.simantics.lz4/src/net/jpountz/util/ChecksumUtil.java @@ -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); + } + } + +} diff --git a/bundles/org.simantics.lz4/src/net/jpountz/util/Native.java b/bundles/org.simantics.lz4/src/net/jpountz/util/Native.java index c793f89fd..b14b0728f 100644 --- a/bundles/org.simantics.lz4/src/net/jpountz/util/Native.java +++ b/bundles/org.simantics.lz4/src/net/jpountz/util/Native.java @@ -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 index 000000000..a009f9633 --- /dev/null +++ b/bundles/org.simantics.lz4/src/net/jpountz/util/NativeParameters.java @@ -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 + * non-null. If null, class {@link Native} will pick a + * temporary file from the default temporary directory to extract the library + * to and load it from there. + * + *

+ * This value must be set before {@link Native#load(Path)} is invoked. + * + * @see Activator + */ + public static Path extractionPath; + +} diff --git a/bundles/org.simantics.lz4/src/net/jpountz/xxhash/XXHashFactory.java b/bundles/org.simantics.lz4/src/net/jpountz/xxhash/XXHashFactory.java index ab03dff1f..d9b199bd5 100644 --- a/bundles/org.simantics.lz4/src/net/jpountz/xxhash/XXHashFactory.java +++ b/bundles/org.simantics.lz4/src/net/jpountz/xxhash/XXHashFactory.java @@ -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) { diff --git a/bundles/org.simantics.lz4/src/org/simantics/lz4/bundle/Activator.java b/bundles/org.simantics.lz4/src/org/simantics/lz4/bundle/Activator.java index 38f3c3bd1..3b1c07576 100644 --- a/bundles/org.simantics.lz4/src/org/simantics/lz4/bundle/Activator.java +++ b/bundles/org.simantics.lz4/src/org/simantics/lz4/bundle/Activator.java @@ -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 { - } } -- 2.43.2