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
41 changes: 20 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,10 @@ The Engine API provides direct policy evaluation without plugin infrastructure.
(when using FileSystemBundleLoader, the IR plan.json is expected to be in the given path)

```java
import io.github.open-policy-agent.rego.Engine;
import io.github.open-policy-agent.bundle.FileSystemBundleLoader;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.open_policy_agent.opa.rego.Engine;
import io.github.open_policy_agent.opa.bundle.FileSystemBundleLoader;
import java.util.List;
import java.util.Map;

// Build the engine with the example policy bundle
Engine engine = new Engine.Builder()
Expand All @@ -128,11 +127,11 @@ Engine engine = new Engine.Builder()
Engine.PreparedQuery query = engine.prepareForEvaluation().build();

// Evaluate with input - alice is an authorized reader
ObjectMapper mapper = new ObjectMapper();
JsonNode input = mapper.readTree("{\"user\": \"alice\", \"action\": \"read\"}");
List<JsonNode> results = query.eval(input);
Map<String, Object> input = Map.of("user", "alice", "action", "read");
List<Object> results = query.eval(input);

boolean allowed = results.get(0).get("result").asBoolean(); // true
@SuppressWarnings("unchecked")
boolean allowed = (Boolean) ((Map<String, Object>) results.get(0)).get("result"); // true
```

### Opa API (Full Runtime)
Expand All @@ -143,8 +142,8 @@ The Opa API provides a complete OPA runtime with plugin support. Best for produc
#### Opa API with Programmatic Config

```java
import io.github.open-policy-agent.Opa;
import io.github.open-policy-agent.config.Config;
import io.github.open_policy_agent.opa.Opa;
import io.github.open_policy_agent.opa.config.Config;

Config config = new Config()
.addService(new Config.ServiceConfig()
Expand All @@ -170,7 +169,7 @@ boolean allowed = decision.getResult().asBoolean(); // true


```java
import io.github.open-policy-agent.Opa;
import io.github.open_policy_agent.opa.Opa;

// Initialize with a YAML configuration file
Opa opa = new Opa.Builder()
Expand Down Expand Up @@ -373,8 +372,8 @@ Engine.PreparedQuery query = engine.prepareForEvaluation()
.build();

// Evaluate many times
for (JsonNode input : inputs) {
List<JsonNode> results = query.eval(input);
for (Object input : inputs) {
List<Object> results = query.eval(input);
}
```

Expand Down Expand Up @@ -419,7 +418,7 @@ Engine engine = new Engine.Builder()
engine.refresh();

// Next evaluation uses the new policy; data is already live
List<JsonNode> results = engine.evaluate(ctx, input);
List<Object> results = engine.evaluate(ctx, input);
```

### PreparedQuery behavior
Expand All @@ -441,7 +440,7 @@ Engine.PreparedQuery newPq = engine.prepareForEvaluation().build();
Register custom builtin functions to extend policy capabilities:

```java
import io.github.open-policy-agent.ast.types.*;
import io.github.open_policy_agent.opa.ast.types.*;

Engine engine = new Engine.Builder()
.withBundleLoader(new FileSystemBundleLoader("authz", Path.of("/policy")))
Expand Down Expand Up @@ -470,8 +469,8 @@ allow if {
OPA's `print()` builtin is supported for debugging policy evaluation. By default, print output is written via the `Logger` interface. Configure a custom `PrintHook` to redirect output:

```java
import io.github.open-policy-agent.rego.PrintHook;
import io.github.open-policy-agent.logging.Logger;
import io.github.open_policy_agent.opa.rego.PrintHook;
import io.github.open_policy_agent.opa.logging.Logger;

// Use a Logger instance
Logger myLogger = new Logger.StandardLogger();
Expand Down Expand Up @@ -503,12 +502,12 @@ When evaluated, this prints: `evaluating user: alice action: read`
All SDK exceptions extend `OpaException` and support contextual information via `.withContext()`:

```java
import io.github.open-policy-agent.OpaException;
import io.github.open-policy-agent.PolicyNotFoundException;
import io.github.open-policy-agent.EvaluationException;
import io.github.open_policy_agent.opa.OpaException;
import io.github.open_policy_agent.opa.ir.PolicyNotFoundException;
import io.github.open_policy_agent.opa.ir.EvaluationException;

try {
List<JsonNode> results = query.eval(input);
List<Object> results = query.eval(input);
} catch (PolicyNotFoundException e) {
System.err.println("Policy not found: " + e.getMessage());
} catch (EvaluationException e) {
Expand Down
8 changes: 4 additions & 4 deletions opa-builtins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ Note: String builtins (`contains`, `concat`, `split`, `sprintf`, `trim`, etc.) a
Implement the `BuiltinProvider` interface and register via ServiceLoader:

```java
import io.github.openpolicyagent.opa.ast.builtin.BuiltinProvider;
import io.github.openpolicyagent.opa.rego.EvaluationContext;
import io.github.openpolicyagent.opa.ast.types.*;
import io.github.open_policy_agent.opa.ast.builtin.BuiltinProvider;
import io.github.open_policy_agent.opa.rego.EvaluationContext;
import io.github.open_policy_agent.opa.ast.types.*;
import java.util.Map;
import java.util.function.BiFunction;

Expand All @@ -121,7 +121,7 @@ public class MyBuiltinProvider implements BuiltinProvider {
}
```

Register in `META-INF/services/io.github.openpolicyagent.opa.ast.builtin.BuiltinProvider`:
Register in `META-INF/services/io.github.open_policy_agent.opa.ast.builtin.BuiltinProvider`:

```
com.example.MyBuiltinProvider
Expand Down
3 changes: 3 additions & 0 deletions opa-builtins/opa-builtins-json/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
// RegoValueModule (auto-registered via Jackson SPI) provides (de)serializers for the AST
// types so they don't need to carry annotations.
runtimeOnly(project(":opa-jackson"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need this?

}

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ public void serialize(RegoNull value, JsonGenerator gen, SerializerProvider seri
};

static {
JSON_MAPPER = new ObjectMapper();
// findAndRegisterModules picks up RegoValueModule (from opa-jackson) via SPI so
// RegoString/RegoArray/RegoObject etc. (de)serialize without annotations on the AST types.
JSON_MAPPER = new ObjectMapper().findAndRegisterModules();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if it'd be possible to add a simple serialize/deserialize SPI to cover all of the explicit jackson dependencies, or if there are things in here that need that strict coupling?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There does seem to be some strict coupling, so this won't be straight forward change. Maybe a separate PR?


// Register custom serializers for Rego numeric types
// Register custom serializers for Rego numeric types (overrides RegoValueModule defaults).
SimpleModule module = new SimpleModule();
module.addSerializer(RegoDecimal.class, REGO_DECIMAL_SERIALIZER);
module.addSerializer(RegoBigInt.class, REGO_BIG_INT_SERIALIZER);
Expand All @@ -101,7 +103,8 @@ public void serialize(RegoNull value, JsonGenerator gen, SerializerProvider seri

YAML_MAPPER =
new ObjectMapper(
YAMLFactory.builder().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER).build());
YAMLFactory.builder().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER).build())
.findAndRegisterModules();
YAML_MAPPER.registerModule(yamlModule);
}

Expand Down
3 changes: 3 additions & 0 deletions opa-builtins/opa-builtins-token/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ dependencies {
implementation("com.nimbusds:nimbus-jose-jwt:10.5")
implementation("org.bouncycastle:bcpkix-jdk18on:1.82")
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
// RegoValueModule provides Jackson (de)serialization for RegoObject/RegoArray/etc.
// Discovered automatically via Jackson's findAndRegisterModules() SPI.
runtimeOnly(project(":opa-jackson"))
}

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public class TokenBuiltins implements BuiltinProvider {
private static final RegoString CERT_PROPERTY = new RegoString("cert");
private static final RegoString SECRET_PROPERTY = new RegoString("secret");
private static final RegoObject BLANK_OBJECT = new RegoObject();
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
// Auto-register RegoValueModule (and any other Jackson modules on the classpath) via SPI so
// RegoObject (de)serialization works without the AST types carrying Jackson annotations.
private static final ObjectMapper JSON_MAPPER = new ObjectMapper().findAndRegisterModules();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would be nice if we didn't have this explicit Jackson dependency here, and could just SPI-ify it.


static {
Security.addProvider(new BouncyCastleProvider());
Expand Down
32 changes: 16 additions & 16 deletions opa-evaluator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ The evaluator module provides the `Engine` class for direct policy evaluation. I
## Usage

```java
import io.github.openpolicyagent.opa.rego.Engine;
import io.github.openpolicyagent.opa.bundle.FileSystemBundleLoader;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.open_policy_agent.opa.rego.Engine;
import io.github.open_policy_agent.opa.bundle.FileSystemBundleLoader;
import java.util.List;
import java.util.Map;

Engine engine = new Engine.Builder()
.withBundleLoader(new FileSystemBundleLoader("authz", Path.of("policy")))
Expand All @@ -22,14 +21,13 @@ Engine engine = new Engine.Builder()

Engine.PreparedQuery query = engine.prepareForEvaluation().build();

ObjectMapper mapper = new ObjectMapper();
JsonNode input = mapper.readTree("{\"user\": \"alice\"}");
List<JsonNode> results = query.eval(input);
Map<String, Object> input = Map.of("user", "alice");
List<Object> results = query.eval(input);
```

### POJO Input/Output

The Engine supports typed input and output via Jackson:
The Engine supports typed input and output:

```java
List<MyResult> results = engine.prepareForEvaluation()
Expand All @@ -40,27 +38,29 @@ List<MyResult> results = engine.prepareForEvaluation()
### Multiple Queries

```java
Map<String, Object> input = Map.of("user", "alice");

// Default query
Engine.PreparedQuery allowQuery = engine.prepareForEvaluation().build();
List<JsonNode> allowResults = allowQuery.eval(input);
List<Object> allowResults = allowQuery.eval(input);

// Override with a different query
Engine.PreparedQuery denyQuery = engine.prepareForEvaluation()
.withEntrypoint("example/deny")
.build();
List<JsonNode> denyResults = denyQuery.eval(input);
List<Object> denyResults = denyQuery.eval(input);
```

### Metrics and Profiling

```java
import io.github.openpolicyagent.opa.metrics.Metrics;
import io.github.openpolicyagent.opa.tracing.Profiler;
import io.github.open_policy_agent.opa.metrics.Metrics;
import io.github.open_policy_agent.opa.tracing.Profiler;

Metrics metrics = new SimpleMetrics();
Profiler profiler = new Profiler();

List<JsonNode> results = engine.prepareForEvaluation()
List<Object> results = engine.prepareForEvaluation()
.withMetrics(metrics)
.withProfiler(profiler)
.build()
Expand All @@ -70,7 +70,7 @@ List<JsonNode> results = engine.prepareForEvaluation()
### Custom Builtins

```java
import io.github.openpolicyagent.opa.ast.types.*;
import io.github.open_policy_agent.opa.ast.types.*;

Engine engine = new Engine.Builder()
.withBundleLoader(new FileSystemBundleLoader("authz", Path.of("/policy")))
Expand Down Expand Up @@ -99,7 +99,7 @@ public class MyBuiltinProvider implements BuiltinProvider {
}
```

Register in `META-INF/services/io.github.openpolicyagent.opa.ast.builtin.BuiltinProvider`.
Register in `META-INF/services/io.github.open_policy_agent.opa.ast.builtin.BuiltinProvider`.

### PolicyReader SPI

Expand All @@ -122,7 +122,7 @@ The Engine supports hot-reloading of policies and data, following the same seman
engine.refresh(); // picks up new policy from store

// Direct evaluate uses the new policy immediately
List<JsonNode> results = engine.evaluate(ctx, input);
List<Object> results = engine.evaluate(ctx, input);

// Existing PreparedQuery still uses old policy -- re-prepare to pick up changes
Engine.PreparedQuery freshPq = engine.prepareForEvaluation().build();
Expand Down
8 changes: 3 additions & 5 deletions opa-evaluator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ repositories {
}

dependencies {
// Jackson is a compile-only dependency: the Engine JsonNode API, Bundle.manifest,
// Capabilities, and IR class annotations require it at compile time.
// At runtime, jackson-databind is provided transitively via the opa-jackson module.
compileOnly("com.fasterxml.jackson.core:jackson-databind:2.17.0")
Comment thread
sspaink marked this conversation as resolved.

// The evaluator has no direct dependency on a JSON library. JSON IO is provided by external
// modules through SPIs (see Engine javadoc); opa-jackson is one such implementation, used here
// for testing.
testImplementation(project(":opa-jackson"))
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package io.github.open_policy_agent.opa.ast.types;

import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -38,34 +34,6 @@ public boolean contains(RegoValue value) {
}
}

public static RegoArray fromJsonNode(ObjectMapper mapper, JsonNode arrayNode) throws Exception {
RegoArray array = new RegoArray();
for (JsonNode element : arrayNode) {
if (element.isObject()) {
array.addValue(mapper.treeToValue(element, RegoObject.class));
} else if (element.isArray()) {
array.addValue(fromJsonNode(mapper, element));
} else if (element.isTextual()) {
array.addValue(new RegoString(element.asText()));
} else if (element.isNumber()) {
if (element.isIntegralNumber()) {
array.addValue(new RegoBigInt(BigInteger.valueOf(element.asLong())));
} else {
array.addValue(new RegoDecimal(element.doubleValue()));
}
} else if (element.isBoolean()) {
array.addValue(RegoBoolean.of(element.asBoolean()));
} else if (element.isNull()) {
array.addValue(RegoNull.INSTANCE);
} else {
throw new UnsupportedOperationException(
"Unsupported JSON element type: " + element.getNodeType());
}
}
return array;
}

@JsonValue
public List<RegoValue> getValue() {
return values;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.open_policy_agent.opa.ast.types;

import com.fasterxml.jackson.annotation.JsonValue;
import java.math.BigInteger;
import java.util.Objects;

Expand All @@ -19,7 +18,6 @@ public void setValue(BigInteger i) {
this.value = i;
}

@JsonValue
public BigInteger getValue() {
return value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.open_policy_agent.opa.ast.types;

import com.fasterxml.jackson.annotation.JsonValue;

public class RegoBoolean implements RegoValue {

Expand All @@ -13,7 +12,6 @@ private RegoBoolean(boolean value) {
this.value = value;
}

@JsonValue
public boolean getValue() {
return value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.open_policy_agent.opa.ast.types;

import com.fasterxml.jackson.annotation.JsonValue;
import java.math.BigInteger;
import java.util.Objects;

Expand All @@ -22,7 +21,6 @@ public RegoDecimal(Float f) {
this.value = f.doubleValue();
}

@JsonValue
public Double getValue() {
return value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.open_policy_agent.opa.ast.types;

import com.fasterxml.jackson.annotation.JsonValue;
import java.math.BigInteger;
import java.util.Objects;

Expand Down Expand Up @@ -48,7 +47,6 @@ public void setValue(Integer i) {
this.value = i;
}

@JsonValue
public Integer getValue() {
return value;
}
Expand Down
Loading
Loading