Skip to content

Commit 32cb86a

Browse files
committed
feat(sdk): add Java conformance and examples
1 parent e7da158 commit 32cb86a

24 files changed

Lines changed: 1138 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,59 @@ jobs:
2222
steps:
2323
- uses: actions/checkout@v7
2424

25+
- name: Check out ODP specifications
26+
uses: actions/checkout@v7
27+
with:
28+
repository: offering-protocol/odp-specs
29+
path: .conformance/odp-specs
30+
2531
- uses: actions/setup-java@v6
2632
with:
2733
distribution: temurin
2834
java-version: ${{ matrix.java }}
2935
cache: maven
3036

3137
- run: ./mvnw --batch-mode --no-transfer-progress verify
38+
39+
- name: Run shared conformance harness
40+
run: ./scripts/run-conformance.sh
41+
env:
42+
ODP_SPECS_DIR: .conformance/odp-specs
43+
44+
- name: Upload conformance reports
45+
if: matrix.java == '17'
46+
uses: actions/upload-artifact@v7
47+
with:
48+
name: odp-java-conformance
49+
path: .conformance/reports/*.json
50+
if-no-files-found: error
51+
retention-days: 7
52+
53+
- name: Check out Node.js reference implementation
54+
if: matrix.java == '17'
55+
uses: actions/checkout@v7
56+
with:
57+
repository: offering-protocol/odp-node
58+
path: .conformance/odp-node
59+
60+
- name: Set up pnpm
61+
if: matrix.java == '17'
62+
uses: pnpm/action-setup@v6
63+
with:
64+
version: 11.1.3
65+
66+
- name: Set up Node.js
67+
if: matrix.java == '17'
68+
uses: actions/setup-node@v6
69+
with:
70+
node-version: 22
71+
72+
- name: Install Node.js reference implementation
73+
if: matrix.java == '17'
74+
run: pnpm --dir .conformance/odp-node install --frozen-lockfile
75+
76+
- name: Run Node.js interoperability
77+
if: matrix.java == '17'
78+
run: ./scripts/run-node-interoperability.sh
79+
env:
80+
ODP_NODE_DIR: .conformance/odp-node

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
.conformance/
23
.idea/
34
.project
45
.classpath

‎DEVELOPMENT.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ Format Java sources with:
2222
./mvnw spotless:apply
2323
```
2424

25+
Run the shared protocol conformance harness with:
26+
27+
```sh
28+
ODP_SPECS_DIR=/path/to/odp-specs ./scripts/run-conformance.sh
29+
```
30+
31+
Run cross-language interoperability against the Node.js reference Service with:
32+
33+
```sh
34+
ODP_NODE_DIR=/path/to/odp-node ./scripts/run-node-interoperability.sh
35+
```
36+
2537
## Module boundaries
2638

2739
`odp-core` owns transport-independent protocol behavior. `odp-directory`, `odp-agent`, and

‎README.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ directory behavior.
2727

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

30+
## Examples
31+
32+
Run the small Service and Agent examples in separate terminals:
33+
34+
```sh
35+
./scripts/run-small-service.sh
36+
./scripts/run-agent-example.sh
37+
```
38+
39+
The Agent example explicitly uses a mock directory assembled from reachable Service origins. It
40+
then performs live Service inspection, Offering listing, and full Offering retrieval. See
41+
[examples/README.md](./examples/README.md) for the complete walkthrough.
42+
3043
## Development
3144

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

57+
Generate Agent and Service conformance reports with:
58+
59+
```sh
60+
ODP_SPECS_DIR=/path/to/odp-specs ./scripts/run-conformance.sh
61+
```
62+
63+
Run the Java Agent against the Node.js reference Service with:
64+
65+
```sh
66+
ODP_NODE_DIR=/path/to/odp-node ./scripts/run-node-interoperability.sh
67+
```
68+
69+
The shared harness writes release evidence to `.conformance/reports/`.
70+
4471
See [DEVELOPMENT.md](./DEVELOPMENT.md) for repository conventions and
4572
[`odp-specs`](https://github.com/offering-protocol/odp-specs) for the normative draft, schemas,
4673
examples, and test vectors.

‎examples/README.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Runnable examples
2+
3+
The examples demonstrate the two ODP integration roles with the public Java modules.
4+
5+
## Small Service
6+
7+
The small Service keeps a Collection and two Offerings in memory. It publishes a Service Document,
8+
supports listing and searching Offerings, returns full Offering details, and exposes a free download
9+
Action.
10+
11+
```sh
12+
./scripts/run-small-service.sh
13+
```
14+
15+
The Service listens on `http://127.0.0.1:4103` by default. Set `PORT` to use another port.
16+
17+
## Agent discovery
18+
19+
The Agent example composes a mock directory from reachable local Services, prints every inspected
20+
Service Document, lists terse Offerings, and retrieves each full Offering. The mock directory is
21+
example infrastructure; it does not call the canonical ODP directory.
22+
23+
Start the small Service in another terminal, then run:
24+
25+
```sh
26+
./scripts/run-agent-example.sh
27+
```
28+
29+
Without arguments, the mock directory checks ports 4101, 4102, and 4103. Pass one or more Service
30+
origins to use a different set:
31+
32+
```sh
33+
./scripts/run-agent-example.sh https://service.example
34+
```

‎examples/pom.xml‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.offeringprotocol</groupId>
9+
<artifactId>odp-java</artifactId>
10+
<version>0.1.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>odp-examples</artifactId>
14+
<name>ODP Java Examples</name>
15+
<description>Runnable Agent and Service examples for ODP Java.</description>
16+
17+
<properties>
18+
<automatic.module.name>org.offeringprotocol.odp.examples</automatic.module.name>
19+
<maven.deploy.skip>true</maven.deploy.skip>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>${project.groupId}</groupId>
25+
<artifactId>odp-agent</artifactId>
26+
<version>${project.version}</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>${project.groupId}</groupId>
30+
<artifactId>odp-service</artifactId>
31+
<version>${project.version}</version>
32+
</dependency>
33+
</dependencies>
34+
35+
<build>
36+
<plugins>
37+
<plugin>
38+
<groupId>org.codehaus.mojo</groupId>
39+
<artifactId>exec-maven-plugin</artifactId>
40+
</plugin>
41+
</plugins>
42+
</build>
43+
</project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.offeringprotocol.odp.examples;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import org.offeringprotocol.odp.core.OdpJson;
7+
8+
/** Runnable two-stage Agent discovery example using a mock directory. */
9+
public final class AgentDiscovery {
10+
private AgentDiscovery() {}
11+
12+
public static void main(String[] arguments) {
13+
List<String> candidates = arguments.length == 0
14+
? List.of("http://127.0.0.1:4101", "http://127.0.0.1:4102", "http://127.0.0.1:4103")
15+
: Arrays.asList(arguments);
16+
MockDirectory directory = MockDirectory.discover(candidates);
17+
System.out.printf( // NOPMD - Console output is the example's user interface.
18+
"Mock directory contains %d reachable ODP Service(s).%n", directory.size());
19+
List<org.offeringprotocol.odp.agent.OdpServiceClient> services = new ArrayList<>(directory.search("odp"));
20+
if (services.isEmpty()) {
21+
services.addAll(directory.search(""));
22+
}
23+
for (var service : services) {
24+
print("ODP Service Document", service.inspection().document());
25+
var page = service.listOfferings("terse", null, null);
26+
print("Terse Offering list", page);
27+
for (var offering : page.items()) {
28+
print("Full Offering " + offering.id(), service.getOffering(offering.id(), "full", null));
29+
}
30+
}
31+
}
32+
33+
private static void print(String label, Object value) {
34+
System.out.printf( // NOPMD - Console output is the example's user interface.
35+
"%n%s:%n%s%n", label, OdpJson.write(value));
36+
}
37+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.offeringprotocol.odp.examples;
2+
3+
import java.net.URI;
4+
import java.util.ArrayList;
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import org.offeringprotocol.odp.agent.OdpServiceClient;
9+
10+
/** Reachable Service index used only by the Agent example. */
11+
final class MockDirectory {
12+
private final Map<String, OdpServiceClient> services;
13+
14+
private MockDirectory(Map<String, OdpServiceClient> services) {
15+
this.services = Map.copyOf(services);
16+
}
17+
18+
static MockDirectory discover(List<String> candidates) {
19+
Map<String, OdpServiceClient> services = new LinkedHashMap<>();
20+
for (String candidate : candidates) {
21+
try {
22+
OdpServiceClient client = OdpServiceClient.create(URI.create(candidate));
23+
services.put(client.inspection().serviceOrigin(), client);
24+
} catch (RuntimeException ignored) {
25+
// Unreachable candidates do not appear in the mock directory.
26+
}
27+
}
28+
if (services.isEmpty()) {
29+
throw new IllegalStateException("No configured ODP Services are reachable");
30+
}
31+
return new MockDirectory(services);
32+
}
33+
34+
List<OdpServiceClient> search(String query) {
35+
String normalized = query.toLowerCase(java.util.Locale.ROOT);
36+
List<OdpServiceClient> matches = new ArrayList<>();
37+
for (OdpServiceClient client : services.values()) {
38+
var document = client.inspection().document();
39+
String metadata = document.name() + " " + document.description() + " "
40+
+ String.join(" ", document.keywords() == null ? List.of() : document.keywords());
41+
if (metadata.toLowerCase(java.util.Locale.ROOT).contains(normalized)) {
42+
matches.add(client);
43+
}
44+
}
45+
return List.copyOf(matches);
46+
}
47+
48+
int size() {
49+
return services.size();
50+
}
51+
}

0 commit comments

Comments
 (0)