diff --git a/README.md b/README.md index 9d01d762..1aae6887 100755 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ A file `target/snappy-java-$(version).jar` is the product additionally containin ### Using pure-java Snappy implementation snappy-java can optionally use a pure-java implementation of Snappy based on [aircompressor](https://github.com/airlift/aircompressor/tree/master/src/main/java/io/airlift/compress/snappy). This implementation is selected when no native Snappy library for your platform is found. You can also force using this pure-java implementation by setting a JVM property `org.xerial.snappy.purejava=true` before loading any class of Snappy (e.g., using `-Dorg.xerial.snappy.purejava=true` option when launching JVM). - +The pure-java implementation is also used as a fallback when no native Snappy library can be loaded. You can disable this fallback by setting the JVM property `org.xerial.snappy.purejava.fallback=false` ### Using snappy-java with Tomcat 6 (or higher) Web Server diff --git a/build.sbt b/build.sbt index c431643e..6b9901e4 100644 --- a/build.sbt +++ b/build.sbt @@ -61,6 +61,7 @@ crossPaths := false libraryDependencies ++= Seq( "junit" % "junit" % "4.13.2" % "test", + "org.mockito" % "mockito-inline" % "4.6.1" % "test", "org.codehaus.plexus" % "plexus-classworlds" % "2.4" % "test", "org.xerial.java" % "xerial-core" % "2.1" % "test", "org.wvlet.airframe" %% "airframe-log" % "21.12.1" % "test", diff --git a/src/main/java/org/xerial/snappy/SnappyLoader.java b/src/main/java/org/xerial/snappy/SnappyLoader.java index 31e0e470..1021d11a 100644 --- a/src/main/java/org/xerial/snappy/SnappyLoader.java +++ b/src/main/java/org/xerial/snappy/SnappyLoader.java @@ -78,6 +78,7 @@ public class SnappyLoader public static final String KEY_SNAPPY_LIB_PATH = "org.xerial.snappy.lib.path"; public static final String KEY_SNAPPY_LIB_NAME = "org.xerial.snappy.lib.name"; public static final String KEY_SNAPPY_PUREJAVA = "org.xerial.snappy.purejava"; + public static final String KEY_SNAPPY_PUREJAVA_FALLBACK = "org.xerial.snappy.purejava.fallback"; public static final String KEY_SNAPPY_TEMPDIR = "org.xerial.snappy.tempdir"; public static final String KEY_SNAPPY_USE_SYSTEMLIB = "org.xerial.snappy.use.systemlib"; public static final String KEY_SNAPPY_DISABLE_BUNDLED_LIBS = "org.xerial.snappy.disable.bundled.libs"; // Depreciated, but preserved for backward compatibility @@ -170,7 +171,11 @@ static synchronized SnappyApi loadSnappyApi() } catch(Throwable e) { // Fall-back to pure-java Snappy implementation - setSnappyApi(new PureJavaSnappy()); + if(Boolean.parseBoolean(System.getProperty(KEY_SNAPPY_PUREJAVA_FALLBACK, "true"))) { + setSnappyApi(new PureJavaSnappy()); + } else { + throw e; + } } return snappyApi; } @@ -188,7 +193,8 @@ static synchronized BitShuffleNative loadBitShuffleApi() /** * Load a native library of snappy-java */ - private synchronized static void loadNativeLibrary() + // VisibleForTesting + static synchronized void loadNativeLibrary() { if (!isLoaded) { try { diff --git a/src/test/java/org/xerial/snappy/SnappyLoaderTest.java b/src/test/java/org/xerial/snappy/SnappyLoaderTest.java index 2bc21483..c2c6bc48 100755 --- a/src/test/java/org/xerial/snappy/SnappyLoaderTest.java +++ b/src/test/java/org/xerial/snappy/SnappyLoaderTest.java @@ -27,6 +27,8 @@ import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.xerial.util.FileResource; import org.xerial.util.log.Logger; @@ -35,8 +37,7 @@ import java.net.URL; import java.net.URLClassLoader; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class SnappyLoaderTest { @@ -128,6 +129,53 @@ public void autoLoad() _logger.debug(Snappy.maxCompressedLength(1024)); } + @Test + public void loadSnappySimulateLoadingFailedCheckIfFallbackIsDisabledThrowsException() + { + try(MockedStatic mock = Mockito.mockStatic(SnappyLoader.class, Mockito.CALLS_REAL_METHODS)) { + String mockedErrorMessage = "The static method loadNativeLibrary is mocked and throws an exception"; + mock.when(() -> SnappyLoader.loadNativeLibrary()).thenThrow(new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, mockedErrorMessage)); + + try { + SnappyLoader.setSnappyApi(null); // force reload + System.setProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA, "false"); + System.setProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA_FALLBACK, "false"); + + SnappyLoader.loadSnappyApi(); + + fail("Code should have thrown an exception"); + } catch (SnappyError e) { + assertEquals(SnappyError.class, e.getClass()); + assertTrue(e.getMessage().contains(mockedErrorMessage)); + } finally { + System.clearProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA); + System.clearProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA_FALLBACK); + } + } + } + + @Test + public void loadSnappySimulateLoadingFailedCheckIfFallbackIsUsed() + { + try(MockedStatic mock = Mockito.mockStatic(SnappyLoader.class, Mockito.CALLS_REAL_METHODS)) { + String mockedErrorMessage = "The static method loadNativeLibrary is mocked and throws an exception"; + mock.when(() -> SnappyLoader.loadNativeLibrary()).thenThrow(new SnappyError(SnappyErrorCode.FAILED_TO_LOAD_NATIVE_LIBRARY, mockedErrorMessage)); + + try { + SnappyLoader.setSnappyApi(null); // force reload + System.setProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA, "false"); + System.clearProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA_FALLBACK); // default is true + + SnappyLoader.loadSnappyApi(); + + assertTrue("Fallback to pure java implementation did not work!", SnappyLoader.isPureJava()); + } finally { + System.clearProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA); + System.clearProperty(SnappyLoader.KEY_SNAPPY_PUREJAVA_FALLBACK); + } + } + } + public static void main(String[] args) { // Test for loading native library specified in -Djava.library.path