Java publishing (#151)

* bindings/java: Add prefix to native methods

* bindings/java: Add javadocs and missing methods to Engine

* Setup publishing uber-JAR via GitHub workflow

* bindings/java: Update README

* bindings/java: Improve native library loading from JAR

* bindings/java: Fix usage of `working-directory`

* bindings/java: Pass required `distribution` parameter to `actions/setup-java@v4`

* bindings/java: Use Corretto distribution

This is because Microsoft doesn't provide JDK8,
see https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-8.

* bindings/java: Install GCC toolchain for `aarch64-unknown-linux-gnu`

* bindings/java: Upload artifacts with different names from each step

* bindings/java: Upload built JARs to GitHub
This commit is contained in:
Burak
2024-02-23 00:00:07 +01:00
committed by GitHub
parent 3a86c83827
commit 22047287b4
9 changed files with 460 additions and 287 deletions

View File

@@ -9,7 +9,7 @@ use jni::JNIEnv;
use regorus::{Engine, Value};
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_newEngine(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
_env: JNIEnv,
_class: JClass,
) -> jlong {
@@ -18,7 +18,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_newEngine(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_addPolicy(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -35,7 +35,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_addPolicy(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_addPolicyFromFile(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -50,7 +50,20 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_addPolicyFromFile(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_addDataJson(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_data();
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJson(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -65,7 +78,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_addDataJson(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_addDataJsonFromFile(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFile(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -80,7 +93,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_addDataJsonFromFile(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_setInputJson(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJson(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -95,7 +108,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_setInputJson(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_setInputJsonFromFile(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromFile(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -110,7 +123,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_setInputJsonFromFile(
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_evalQuery(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
@@ -131,7 +144,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_evalQuery(
}
#[no_mangle]
pub unsafe extern "system" fn Java_com_microsoft_regorus_Engine_destroyEngine(
pub unsafe extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
_env: JNIEnv,
_class: JClass,
engine_ptr: jlong,

View File

@@ -13,77 +13,163 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicReference;
/**
* Regorus Engine.
*/
public class Engine implements AutoCloseable {
// Methods exposed from Rust side, you can run
// `javac -h . src/main/java/com/microsoft/regorus/Engine.java` to update
// expected native header at `bindings/java/com_microsoft_regorus_Engine.h`
// if you update the native API.
private static native long newEngine();
private static native void addPolicy(long enginePtr, String path, String rego);
private static native void addPolicyFromFile(long enginePtr, String path);
private static native void addDataJson(long enginePtr, String data);
private static native void addDataJsonFromFile(long enginePtr, String path);
private static native void setInputJson(long enginePtr, String input);
private static native void setInputJsonFromFile(long enginePtr, String path);
private static native String evalQuery(long enginePtr, String query);
private static native void destroyEngine(long enginePtr);
private static native long nativeNewEngine();
private static native void nativeAddPolicy(long enginePtr, String path, String rego);
private static native void nativeAddPolicyFromFile(long enginePtr, String path);
private static native void nativeClearData(long enginePtr);
private static native void nativeAddDataJson(long enginePtr, String data);
private static native void nativeAddDataJsonFromFile(long enginePtr, String path);
private static native void nativeSetInputJson(long enginePtr, String input);
private static native void nativeSetInputJsonFromFile(long enginePtr, String path);
private static native String nativeEvalQuery(long enginePtr, String query);
private static native void nativeDestroyEngine(long enginePtr);
// Pointer to Engine allocated on Rust's heap, all native methods works on
// engine expects this pointer. It is free'd in `close` method.
private final long enginePtr;
/**
* Creates a new Regorus Engine.
*/
public Engine() {
enginePtr = newEngine();
enginePtr = nativeNewEngine();
}
public void pubAddPolicy(String path, String rego) {
addPolicy(enginePtr, path, rego);
/**
* Adds an inline Rego policy.
*
* @param filename Filename of this Rego policy.
* @param rego Rego policy.
*/
public void addPolicy(String filename, String rego) {
nativeAddPolicy(enginePtr, filename, rego);
}
public void pubAddDataJson(String path) {
addDataJson(enginePtr, path);
/**
* Adds a Rego policy from given path.
*
* @param path Path of the Rego policy.
*/
public void addPolicyFromFile(String path) {
nativeAddPolicyFromFile(enginePtr, path);
}
public void pubSetInputJson(String path) {
setInputJson(enginePtr, path);
/**
* Clears the data document.
*/
public void clearData() {
nativeClearData(enginePtr);
}
public String pubEvalQuery(String path) {
return evalQuery(enginePtr, path);
/**
* Adds inline data document from given JSON.
* The specified data document is merged into existing data document.
* It will throw an error if new data conflicts with the existing document.
*
* Example:
* addDataJson("[]") - Throws as it's not an object.
* addDataJson('{"a": 1}') - Fine
* addDataJson('{"b": 2}') - Fine, now {"a": 1, "b": 2}
* addDataJson('{"b": 3}') - Throws as `b` conflicts.
*
* @see clearData
*
* @throws RuntimeException If data conflicts with the existing document
* or data is not an object.
*
* @param data Inline data document.
*/
public void addDataJson(String data) throws RuntimeException {
nativeAddDataJson(enginePtr, data);
}
/**
* Adds data document from given JSON file.
* The specified data document is merged into existing data document.
* It will throw an error if new data conflicts with the existing document.
*
* @see addDataJson
* @see clearData
*
* @throws RuntimeException If data conflicts with the existing document
* or data is not an object.
*
* @param path Path to JSON data document.
*/
public void addDataJsonFromFile(String path) throws RuntimeException {
nativeAddDataJsonFromFile(enginePtr, path);
}
/**
* Sets inline JSON input.
*
* @param input inline JSON input.
*/
public void setInputJson(String input) {
nativeSetInputJson(enginePtr, input);
}
/**
* Sets JSON input from given path.
*
* @param path Path to JSON input.
*/
public void setInputJsonFromFile(String path) {
nativeSetInputJsonFromFile(enginePtr, path);
}
/**
* Evaluates given Rego query and returns a JSON string as a result.
*
* @param query The Rego query.
*
* @return Query results as a JSON string.
*/
public String evalQuery(String query) {
return nativeEvalQuery(enginePtr, query);
}
@Override
public void close() {
destroyEngine(enginePtr);
nativeDestroyEngine(enginePtr);
}
// Loading native library from jar is adapted from:
// Loading native library from JAR is adapted from:
// https://github.com/apache/opendal/blob/93e5f65bbf30df2fed4bdd95bb0685c73c6418c2/bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java
// https://github.com/apache/opendal/blob/93e5f65bbf30df2fed4bdd95bb0685c73c6418c2/bindings/java/src/main/java/org/apache/opendal/Environment.java
private static final String classifier;
static {
final StringBuilder classifierBuilder = new StringBuilder();
final String os = System.getProperty("os.name").toLowerCase();
if (os.startsWith("windows")) {
classifierBuilder.append("windows");
} else if (os.startsWith("mac")) {
classifierBuilder.append("osx");
} else {
classifierBuilder.append("linux");
}
classifierBuilder.append("-");
// Build a Rust target triple, like: 'aarch64-unknown-linux-gnu'.
final StringBuilder targetTripleBuilder = new StringBuilder();
final String arch = System.getProperty("os.arch").toLowerCase();
if (arch.equals("aarch64")) {
classifierBuilder.append("aarch_64");
targetTripleBuilder.append("aarch64");
} else {
classifierBuilder.append("x86_64");
targetTripleBuilder.append("x86_64");
}
classifier = classifierBuilder.toString();
targetTripleBuilder.append("-");
loadNativeLibrary();
final String os = System.getProperty("os.name").toLowerCase();
if (os.startsWith("windows")) {
targetTripleBuilder.append("pc-windows-msvc");
} else if (os.startsWith("mac")) {
targetTripleBuilder.append("apple-darwin");
} else {
targetTripleBuilder.append("unknown-linux-gnu");
}
loadNativeLibrary(targetTripleBuilder.toString());
}
private static void loadNativeLibrary() {
private static void loadNativeLibrary(String targetTriple) {
try {
// try dynamic library - the search path can be configured via "-Djava.library.path"
System.loadLibrary("regorus_java");
@@ -92,10 +178,14 @@ public class Engine implements AutoCloseable {
// ignore - try from classpath
}
final String libraryPath = bundledLibraryPath();
// Native libraries will be bundles into JARs like:
// `aarch64-apple-darwin/libregorus_java.dylib`
final String libraryName = System.mapLibraryName("regorus_java");
final String libraryPath = "/" + targetTriple + "/" + libraryName;
try (final InputStream is = Engine.class.getResourceAsStream(libraryPath)) {
if (is == null) {
throw new RuntimeException("cannot find " + libraryPath);
throw new RuntimeException("Cannot find " + libraryPath + "\nSee https://github.com/microsoft/regorus/tree/main/bindings/java for help.");
}
final int dot = libraryPath.indexOf('.');
final File tmpFile = File.createTempFile(libraryPath.substring(0, dot), libraryPath.substring(dot));
@@ -106,9 +196,4 @@ public class Engine implements AutoCloseable {
throw new RuntimeException(exception);
}
}
private static String bundledLibraryPath() {
final String libraryName = System.mapLibraryName("regorus_java");
return "/native/" + classifier + "/" + libraryName;
}
}

View File

@@ -17,13 +17,13 @@ public class EngineTest extends TestCase
{
String resJson;
try (Engine engine = new Engine()) {
engine.pubAddPolicy(
engine.addPolicy(
"hello.rego",
"package test\nmessage = concat(\", \", [input.message, data.message])"
);
engine.pubAddDataJson("{\"message\":\"World!\"}");
engine.pubSetInputJson("{\"message\":\"Hello\"}");
resJson = engine.pubEvalQuery("data.test.message");
engine.addDataJson("{\"message\":\"World!\"}");
engine.setInputJson("{\"message\":\"Hello\"}");
resJson = engine.evalQuery("data.test.message");
}
Gson gson = new Gson();