4a19854eb9
Add a "High-level walker" subsection under each client's "Browsing lazily" section showing idiomatic use of LazyBrowseNode (browse + expand, idempotency note, redeploy refresh pattern).
291 lines
13 KiB
Markdown
291 lines
13 KiB
Markdown
# Java Client
|
|
|
|
The Java client workspace contains the MXAccess Gateway client library,
|
|
generated protobuf/gRPC bindings, a Picocli test CLI project, and JUnit tests.
|
|
|
|
## Layout
|
|
|
|
```text
|
|
clients/java/
|
|
settings.gradle
|
|
build.gradle
|
|
src/main/generated/
|
|
zb-mom-ww-mxgateway-client/
|
|
zb-mom-ww-mxgateway-cli/
|
|
```
|
|
|
|
`zb-mom-ww-mxgateway-client` generates Java protobuf and gRPC sources from
|
|
`../../src/ZB.MOM.WW.MxGateway.Contracts/Protos`. The Gradle protobuf plugin writes those
|
|
generated sources under `src/main/generated`, which matches the client proto
|
|
manifest in `../proto/proto-inputs.json`. Do not edit generated files by hand.
|
|
|
|
`zb-mom-ww-mxgateway-client` exposes `MxGatewayClientOptions`, `MxGatewayClient`,
|
|
`MxGatewaySession`, value/status helpers, typed gateway exceptions, raw
|
|
generated stubs, and generated protobuf messages for parity tests.
|
|
|
|
`zb-mom-ww-mxgateway-cli` depends on `zb-mom-ww-mxgateway-client` and provides
|
|
the `mxgw-java` application entry point. The CLI supports version, session,
|
|
command, event streaming, write, and smoke-test commands with deterministic
|
|
JSON output.
|
|
|
|
## Regenerating Protobuf Bindings
|
|
|
|
Run generation from `clients/java` after the shared `.proto` files or Java
|
|
output path changes:
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-client:generateProto
|
|
```
|
|
|
|
## Client Usage
|
|
|
|
Create a client with explicit transport and auth options:
|
|
|
|
```java
|
|
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
|
|
.endpoint("localhost:5000")
|
|
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
|
|
.plaintext(true)
|
|
.build();
|
|
|
|
try (MxGatewayClient client = MxGatewayClient.connect(options);
|
|
MxGatewaySession session = client.openSession("java-client")) {
|
|
int serverHandle = session.register("java-client");
|
|
int itemHandle = session.addItem(serverHandle, "TestObject.TestInt");
|
|
session.advise(serverHandle, itemHandle);
|
|
session.write(serverHandle, itemHandle, MxValues.int32Value(123), 0);
|
|
}
|
|
```
|
|
|
|
Use `rawBlockingStub`, `rawFutureStub`, `rawAsyncStub`, `openSessionRaw`,
|
|
`closeSessionRaw`, `invoke`, and raw session helper methods when tests need the
|
|
underlying protobuf messages. `MxGatewayCommandException` and
|
|
`MxAccessException` preserve the raw `MxCommandReply` when the gateway returns a
|
|
data-bearing MXAccess failure.
|
|
|
|
`MxEventStream` implements `Iterator<MxEvent>` and `AutoCloseable`. Closing it
|
|
cancels the underlying gRPC stream. Canceling or timing out a Java client call
|
|
only stops the client from waiting; it does not abort an in-flight MXAccess COM
|
|
call on the worker STA.
|
|
|
|
For alarms, `MxGatewayClient` exposes `queryActiveAlarms` (one-shot snapshot),
|
|
`streamAlarms` (returns an `MxGatewayAlarmFeedSubscription` whose iterator
|
|
yields alarm-feed messages from the gateway's central monitor), and
|
|
`acknowledgeAlarm` (ack by full alarm reference with an optional comment and
|
|
ack target). Close the subscription to cancel the underlying gRPC stream.
|
|
|
|
## Galaxy Repository Browse
|
|
|
|
The Galaxy Repository service is a separate metadata-only gRPC service exposed
|
|
by the gateway. It lets clients enumerate the deployed Galaxy object hierarchy
|
|
and the dynamic attributes on each object so they know which tag references to
|
|
subscribe to via the MXAccess Gateway service. It uses the same API-key auth as
|
|
the gateway and requires the `metadata:read` scope.
|
|
|
|
`GalaxyRepositoryClient` mirrors the `MxGatewayClient` pattern (caller-managed
|
|
or owned channel, `MxGatewayClientOptions`, blocking + async variants). Three
|
|
RPCs are exposed:
|
|
|
|
```java
|
|
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
|
|
.endpoint("localhost:5000")
|
|
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
|
|
.plaintext(true)
|
|
.build();
|
|
|
|
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options)) {
|
|
boolean ok = galaxy.testConnection();
|
|
Optional<Instant> lastDeploy = galaxy.getLastDeployTime();
|
|
List<GalaxyObject> hierarchy = galaxy.discoverHierarchy();
|
|
}
|
|
```
|
|
|
|
`getLastDeployTime` returns `Optional.empty()` when the server reports
|
|
`present=false`. `discoverHierarchy` returns the generated `GalaxyObject` proto
|
|
messages directly so callers can read all fields (including the nested
|
|
`GalaxyAttribute` list) without an extra DTO layer.
|
|
|
|
The CLI exposes matching subcommands: `galaxy-test`, `galaxy-deploy-time`,
|
|
`galaxy-discover`, and `galaxy-watch`. They take the same `--endpoint`,
|
|
`--api-key-env`, `--plaintext`, `--ca-file`, `--server-name-override`,
|
|
`--timeout`, and `--json` options as the gateway commands.
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-test --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-deploy-time --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-discover --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
|
```
|
|
|
|
### Browsing lazily
|
|
|
|
For UI trees or OPC UA bridges, use `browseChildren` to walk one level at a
|
|
time instead of loading the full hierarchy with `discoverHierarchy`. Pass a
|
|
default request for root objects; subsequent calls set `parentGobjectId`,
|
|
`parentTagName`, or `parentContainedPath`. Filter fields match
|
|
`DiscoverHierarchy`. Each response pairs `getChildrenList()` with
|
|
`getChildHasChildrenList()` so you know which nodes to expand. See
|
|
[Galaxy Repository](../../docs/GalaxyRepository.md#browsechildren) for full
|
|
request and filter semantics. This snippet documents the API as it appears once
|
|
the Java client is regenerated on the Windows host.
|
|
|
|
```java
|
|
BrowseChildrenReply reply = galaxy.browseChildren(
|
|
BrowseChildrenRequest.newBuilder().build());
|
|
|
|
List<GalaxyObject> children = reply.getChildrenList();
|
|
List<Boolean> hasChildren = reply.getChildHasChildrenList();
|
|
for (int i = 0; i < children.size(); i++) {
|
|
System.out.printf("%s expand=%b%n", children.get(i).getTagName(), hasChildren.get(i));
|
|
}
|
|
```
|
|
|
|
#### High-level walker
|
|
|
|
For UI trees, the client provides a `LazyBrowseNode` walker that handles
|
|
sibling pagination and the `child_has_children` hint for you:
|
|
|
|
```java
|
|
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
|
|
.endpoint("localhost:5000")
|
|
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
|
|
.plaintext(true)
|
|
.build();
|
|
|
|
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options)) {
|
|
List<LazyBrowseNode> roots = galaxy.browse();
|
|
for (LazyBrowseNode root : roots) {
|
|
if (root.hasChildrenHint()) {
|
|
root.expand();
|
|
}
|
|
for (LazyBrowseNode child : root.getChildren()) {
|
|
String kind = child.hasChildrenHint() ? "has children" : "leaf";
|
|
System.out.println(child.getObject().getTagName() + " (" + kind + ")");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`expand` is idempotent — calling it twice fires only one RPC,
|
|
and is safe under concurrent callers. To refresh after a Galaxy redeploy, call
|
|
`browse` again from the root.
|
|
|
|
### Watching deploy events
|
|
|
|
`GalaxyRepository.WatchDeployEvents` is a server-streaming RPC: the gateway
|
|
sends a bootstrap `DeployEvent` immediately on subscribe and then one event
|
|
each time it observes a new `galaxy.time_of_last_deploy`. The `sequence` field
|
|
is monotonic per server start; gaps mean the per-subscriber buffer dropped
|
|
older events because the consumer was too slow.
|
|
|
|
The client exposes both an iterator-style adaptor over the async stub and an
|
|
observer-callback variant. Both honour the channel-level `streamTimeout`.
|
|
|
|
```java
|
|
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options);
|
|
DeployEventStream events = galaxy.watchDeployEvents(/* lastSeenDeployTime */ null)) {
|
|
while (events.hasNext()) {
|
|
DeployEvent event = events.next();
|
|
// event.getSequence(), event.getObservedAt(),
|
|
// event.getTimeOfLastDeploy() / getTimeOfLastDeployPresent(),
|
|
// event.getObjectCount(), event.getAttributeCount()
|
|
}
|
|
}
|
|
```
|
|
|
|
Pass an `Instant` for `lastSeenDeployTime` to suppress the bootstrap event when
|
|
the cached deploy time matches what the caller already has. `DeployEventStream`
|
|
implements `Iterator<DeployEvent>` and `AutoCloseable`; closing it cancels the
|
|
underlying gRPC call.
|
|
|
|
For callback delivery (e.g. when the consumer wants to drive a queue or
|
|
reactive pipeline), use the async variant:
|
|
|
|
```java
|
|
DeployEventSubscription subscription = galaxy.watchDeployEventsAsync(
|
|
lastSeen,
|
|
new StreamObserver<>() {
|
|
@Override public void onNext(DeployEvent value) { /* ... */ }
|
|
@Override public void onError(Throwable t) { /* ... */ }
|
|
@Override public void onCompleted() { /* ... */ }
|
|
});
|
|
// later:
|
|
subscription.cancel(); // or subscription.close()
|
|
```
|
|
|
|
The matching CLI subcommand streams events until cancelled (Ctrl+C) and prints
|
|
one line per event in text mode or one JSON object per event with `--json`:
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-watch --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-watch --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --last-seen-deploy-time 2026-04-28T18:30:00Z --limit 5"
|
|
```
|
|
|
|
## CLI Usage
|
|
|
|
Run the CLI through Gradle:
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="version --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="open-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --client-session-name java-cli --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="register --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --client-name java-cli --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="add-item --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item TestObject.TestInt --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="advise --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item-handle 1 --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="write --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123 --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="stream-events --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --limit 1 --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="stream-alarms --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --filter-prefix Galaxy --limit 1 --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="acknowledge-alarm --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --reference \"\\Galaxy\Area001.Pump001.PumpFault\" --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestObject.TestInt --json"
|
|
```
|
|
|
|
The CLI accepts `--api-key`, `--api-key-env`, `--plaintext`, `--ca-file`,
|
|
`--server-name-override`, `--timeout`, and `--json` on gateway commands. JSON
|
|
output redacts API keys.
|
|
|
|
Use TLS options for a secured gateway:
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint mxgateway.example.local:5001 --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item TestObject.TestInt --json"
|
|
```
|
|
|
|
## Build And Test
|
|
|
|
Run the Java checks from `clients/java`:
|
|
|
|
```powershell
|
|
gradle test
|
|
```
|
|
|
|
The build uses the Java 21 Gradle toolchain, compiles generated protobuf/gRPC
|
|
code, and runs JUnit 5 tests for the client wrapper, shared behavior fixtures,
|
|
in-process gRPC behavior, stream cancellation, and CLI parser/output behavior.
|
|
|
|
## Packaging
|
|
|
|
Create local library and CLI artifacts from `clients/java`:
|
|
|
|
```powershell
|
|
gradle :zb-mom-ww-mxgateway-client:jar :zb-mom-ww-mxgateway-cli:installDist
|
|
```
|
|
|
|
The library jar is under `zb-mom-ww-mxgateway-client/build/libs`. The installed CLI
|
|
distribution is under `zb-mom-ww-mxgateway-cli/build/install/zb-mom-ww-mxgateway-cli`.
|
|
|
|
## Integration Checks
|
|
|
|
Run live checks only when a gateway and MXAccess-backed worker are available:
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_INTEGRATION = '1'
|
|
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
|
|
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
|
|
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Client Packaging](../../docs/ClientPackaging.md)
|
|
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
|
|
- [Java Client Detailed Design](./JavaClientDesign.md)
|
|
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
|