Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions exonum-java-binding/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]

### Added
- Verification of native library compatibility when it is first loaded, to detect
possible mismatch between an installed exonum-java application and the version
used in a service project. (#882)
- `Block#isEmpty()`

## [0.6.0]- 2019-05-08
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018 The Exonum Team
* Copyright 2019 The Exonum Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,11 +20,23 @@
import org.apache.logging.log4j.Logger;

/**
* A loader of the native shared library with Exonum framework bindings.
* A loader of the native shared library with Exonum framework bindings. It loads the native
* library and also verifies that it is compatible with the Java classes. The native library
* is compatible iff it has exactly the same version as this Java library. The revision
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: iff -> if

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't: "if and only if"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* from which they were built is currently not checked, but may be in the future (see ECR-3173),
* because the API between Java and native is considered internal and can be changed
* in an incompatible way in any revision.
*
* <p>To have library java_bindings available by its name,
* add a path to the folder containing it to <code>java.library.path</code> property,
* e.g.: <code>java -Djava.library.path=${EXONUM_HOME}/lib/native …</code>
* <p>To enable loading of java_bindings library, add a path to the folder containing it
* to <code>java.library.path</code> property, e.g.:
* <code>java -Djava.library.path=${EXONUM_HOME}/lib/native …</code>
*
* <p>This class is thread-safe.
*
* @see <a href="https://exonum.com/doc/version/0.11/get-started/java-binding/#installation">
* Exonum Java installation instructions</a>
* @see <a href="https://exonum.com/doc/version/0.11/get-started/java-binding/#testing">
* Build configuration to enable integration testing of Java services</a>
*/
public final class LibraryLoader {

Expand All @@ -33,16 +45,62 @@ public final class LibraryLoader {
private static final String DYNAMIC_LIBRARIES_ENV_VAR_WINDOWS = "PATH";
private static final String DYNAMIC_LIBRARIES_ENV_VAR_UNIX = "LD_LIBRARY_PATH";

/**
* The current version of the project. Must be updated on
* <a href="https://wiki.bf.local/display/EJB/Java+Binding+Release+Checklist+Template">
* each release</a>.
*/
private static final String JAVA_BINDING_VERSION = "0.7.0-SNAPSHOT";

// TODO: Remove in ECR-3172
private static final boolean LIBRARY_VERSION_VERIFICATION_ENABLED = false;

private static final Logger logger = LogManager.getLogger(LibraryLoader.class);

private static final LibraryLoader INSTANCE = new LibraryLoader(JAVA_BINDING_VERSION);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about to implement the singleton with lazy loading? It doesn't bring a big overhead but anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it will never be actually "lazy" loaded, if I understand correctly — a class is loaded when its first static symbol is accessed (with some exception for compile-time constants, I believe), and the only publicly accessible symbol is load.

https://docs.oracle.com/javase/specs/jls/se11/html/jls-12.html#jls-12.4.1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, you are right. My doubt was that it'll be loaded in case no integration tests defined.


private final String expectedLibVersion;
private boolean loaded;

/**
* Creates a new library loader.
*
* @param libraryVersion the version of this library to verify that the native library
* is compatible with it
*/
private LibraryLoader(String libraryVersion) {
this.expectedLibVersion = libraryVersion;
this.loaded = false;
}

/**
* Loads the native library with Exonum framework bindings.
*
* @throws LinkageError if the native library cannot be loaded; or if it is incompatible
* with this library version
*/
public static void load() {
loadOnce();
INSTANCE.loadOnce();
}

private static void loadOnce() {
private synchronized void loadOnce() {
if (loaded) {
// It has already been attempted to load the library (successfully or not)
return;
}

try {
// Try to load the library
loadLibrary();

// Check that it has the compatible version
checkLibraryVersion();
} finally {
loaded = true;
}
}

private static void loadLibrary() {
try {
System.loadLibrary(BINDING_LIB_NAME);
} catch (UnsatisfiedLinkError e) {
Expand Down Expand Up @@ -96,5 +154,24 @@ private static String dynamicLibrariesEnvVar() {
}
}

private LibraryLoader() {}
private void checkLibraryVersion() {
if (!LIBRARY_VERSION_VERIFICATION_ENABLED) {
return;
}
String nativeLibVersion = nativeGetLibraryVersion();
if (!expectedLibVersion.equals(nativeLibVersion)) {
String message = String.format(
"Mismatch between versions of Java library and native '%s' library:%n"
+ " Java library version: %s%n"
+ " Native library version: %s%n"
+ "Check that the version of 'exonum-java-binding-core' matches the version of "
+ "the installed 'Exonum Java' application.%n"
+ "See https://exonum.com/doc/version/0.11/get-started/java-binding/#installation",
BINDING_LIB_NAME, expectedLibVersion, nativeLibVersion);
logger.fatal(message);
throw new LinkageError(message);
}
}

private static native String nativeGetLibraryVersion();
}