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
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,59 @@ jobs:
steps:
- uses: actions/checkout@v7

- name: Check out ODP specifications
uses: actions/checkout@v7
with:
repository: offering-protocol/odp-specs
path: .conformance/odp-specs

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven

- run: ./mvnw --batch-mode --no-transfer-progress verify

- name: Run shared conformance harness
run: ./scripts/run-conformance.sh
env:
ODP_SPECS_DIR: .conformance/odp-specs

- name: Upload conformance reports
if: matrix.java == '17'
uses: actions/upload-artifact@v7
with:
name: odp-java-conformance
path: .conformance/reports/*.json
if-no-files-found: error
retention-days: 7

- name: Check out Node.js reference implementation
if: matrix.java == '17'
uses: actions/checkout@v7
with:
repository: offering-protocol/odp-node
path: .conformance/odp-node

- name: Set up pnpm
if: matrix.java == '17'
uses: pnpm/action-setup@v6
with:
version: 11.1.3

- name: Set up Node.js
if: matrix.java == '17'
uses: actions/setup-node@v6
with:
node-version: 22

- name: Install Node.js reference implementation
if: matrix.java == '17'
run: pnpm --dir .conformance/odp-node install --frozen-lockfile

- name: Run Node.js interoperability
if: matrix.java == '17'
run: ./scripts/run-node-interoperability.sh
env:
ODP_NODE_DIR: .conformance/odp-node
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
.conformance/
.idea/
.project
.classpath
Expand Down
12 changes: 12 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ Format Java sources with:
./mvnw spotless:apply
```

Run the shared protocol conformance harness with:

```sh
ODP_SPECS_DIR=/path/to/odp-specs ./scripts/run-conformance.sh
```

Run cross-language interoperability against the Node.js reference Service with:

```sh
ODP_NODE_DIR=/path/to/odp-node ./scripts/run-node-interoperability.sh
```

## Module boundaries

`odp-core` owns transport-independent protocol behavior. `odp-directory`, `odp-agent`, and
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ directory behavior.

All artifacts use the Maven group `org.offeringprotocol` and require Java 17 or newer.

## Examples

Run the small Service and Agent examples in separate terminals:

```sh
./scripts/run-small-service.sh
./scripts/run-agent-example.sh
```

The Agent example explicitly uses a mock directory assembled from reachable Service origins. It
then performs live Service inspection, Offering listing, and full Offering retrieval. See
[examples/README.md](./examples/README.md) for the complete walkthrough.

## Development

The Maven Wrapper provides the complete merge gate:
Expand All @@ -41,6 +54,20 @@ Format Java sources with:
./mvnw spotless:apply
```

Generate Agent and Service conformance reports with:

```sh
ODP_SPECS_DIR=/path/to/odp-specs ./scripts/run-conformance.sh
```

Run the Java Agent against the Node.js reference Service with:

```sh
ODP_NODE_DIR=/path/to/odp-node ./scripts/run-node-interoperability.sh
```

The shared harness writes release evidence to `.conformance/reports/`.

See [DEVELOPMENT.md](./DEVELOPMENT.md) for repository conventions and
[`odp-specs`](https://github.com/offering-protocol/odp-specs) for the normative draft, schemas,
examples, and test vectors.
Expand Down
34 changes: 34 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Runnable examples

The examples demonstrate the two ODP integration roles with the public Java modules.

## Small Service

The small Service keeps a Collection and two Offerings in memory. It publishes a Service Document,
supports listing and searching Offerings, returns full Offering details, and exposes a free download
Action.

```sh
./scripts/run-small-service.sh
```

The Service listens on `http://127.0.0.1:4103` by default. Set `PORT` to use another port.

## Agent discovery

The Agent example composes a mock directory from reachable local Services, prints every inspected
Service Document, lists terse Offerings, and retrieves each full Offering. The mock directory is
example infrastructure; it does not call the canonical ODP directory.

Start the small Service in another terminal, then run:

```sh
./scripts/run-agent-example.sh
```

Without arguments, the mock directory checks ports 4101, 4102, and 4103. Pass one or more Service
origins to use a different set:

```sh
./scripts/run-agent-example.sh https://service.example
```
43 changes: 43 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.offeringprotocol</groupId>
<artifactId>odp-java</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>

<artifactId>odp-examples</artifactId>
<name>ODP Java Examples</name>
<description>Runnable Agent and Service examples for ODP Java.</description>

<properties>
<automatic.module.name>org.offeringprotocol.odp.examples</automatic.module.name>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>odp-agent</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>odp-service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.offeringprotocol.odp.examples;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.offeringprotocol.odp.core.OdpJson;

/** Runnable two-stage Agent discovery example using a mock directory. */
public final class AgentDiscovery {
private AgentDiscovery() {}

public static void main(String[] arguments) {
List<String> candidates = arguments.length == 0
? List.of("http://127.0.0.1:4101", "http://127.0.0.1:4102", "http://127.0.0.1:4103")
: Arrays.asList(arguments);
MockDirectory directory = MockDirectory.discover(candidates);
System.out.printf( // NOPMD - Console output is the example's user interface.
"Mock directory contains %d reachable ODP Service(s).%n", directory.size());
List<org.offeringprotocol.odp.agent.OdpServiceClient> services = new ArrayList<>(directory.search("odp"));
if (services.isEmpty()) {
services.addAll(directory.search(""));
}
for (var service : services) {
print("ODP Service Document", service.inspection().document());
var page = service.listOfferings("terse", null, null);
print("Terse Offering list", page);
for (var offering : page.items()) {
print("Full Offering " + offering.id(), service.getOffering(offering.id(), "full", null));
}
}
}

private static void print(String label, Object value) {
System.out.printf( // NOPMD - Console output is the example's user interface.
"%n%s:%n%s%n", label, OdpJson.write(value));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.offeringprotocol.odp.examples;

import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.offeringprotocol.odp.agent.OdpServiceClient;

/** Reachable Service index used only by the Agent example. */
final class MockDirectory {
private final Map<String, OdpServiceClient> services;

private MockDirectory(Map<String, OdpServiceClient> services) {
this.services = Map.copyOf(services);
}

static MockDirectory discover(List<String> candidates) {
Map<String, OdpServiceClient> services = new LinkedHashMap<>();
for (String candidate : candidates) {
try {
OdpServiceClient client = OdpServiceClient.create(URI.create(candidate));
services.put(client.inspection().serviceOrigin(), client);
} catch (RuntimeException ignored) {
// Unreachable candidates do not appear in the mock directory.
}
}
if (services.isEmpty()) {
throw new IllegalStateException("No configured ODP Services are reachable");
}
return new MockDirectory(services);
}

List<OdpServiceClient> search(String query) {
String normalized = query.toLowerCase(java.util.Locale.ROOT);
List<OdpServiceClient> matches = new ArrayList<>();
for (OdpServiceClient client : services.values()) {
var document = client.inspection().document();
String metadata = document.name() + " " + document.description() + " "
+ String.join(" ", document.keywords() == null ? List.of() : document.keywords());
if (metadata.toLowerCase(java.util.Locale.ROOT).contains(normalized)) {
matches.add(client);
}
}
return List.copyOf(matches);
}

int size() {
return services.size();
}
}
Loading