Add Galaxy repository API and clients
This commit is contained in:
@@ -67,6 +67,99 @@ 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.
|
||||
|
||||
## 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 :mxgateway-cli:run --args="galaxy-test --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
||||
gradle :mxgateway-cli:run --args="galaxy-deploy-time --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
||||
gradle :mxgateway-cli:run --args="galaxy-discover --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
||||
```
|
||||
|
||||
### 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 :mxgateway-cli:run --args="galaxy-watch --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
|
||||
gradle :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:
|
||||
|
||||
+194
@@ -1,5 +1,7 @@
|
||||
package com.dohertylan.mxgateway.cli;
|
||||
|
||||
import com.dohertylan.mxgateway.client.DeployEventStream;
|
||||
import com.dohertylan.mxgateway.client.GalaxyRepositoryClient;
|
||||
import com.dohertylan.mxgateway.client.MxEventStream;
|
||||
import com.dohertylan.mxgateway.client.MxGatewayClient;
|
||||
import com.dohertylan.mxgateway.client.MxGatewayClientOptions;
|
||||
@@ -7,15 +9,21 @@ import com.dohertylan.mxgateway.client.MxGatewayClientVersion;
|
||||
import com.dohertylan.mxgateway.client.MxGatewaySecrets;
|
||||
import com.dohertylan.mxgateway.client.MxGatewaySession;
|
||||
import com.dohertylan.mxgateway.client.MxValues;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyAttribute;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;
|
||||
import com.google.protobuf.Message;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
|
||||
@@ -83,9 +91,195 @@ public final class MxGatewayCli implements Callable<Integer> {
|
||||
commandLine.addSubcommand("write", new WriteCommand(clientFactory));
|
||||
commandLine.addSubcommand("stream-events", new StreamEventsCommand(clientFactory));
|
||||
commandLine.addSubcommand("smoke", new SmokeCommand(clientFactory));
|
||||
commandLine.addSubcommand("galaxy-test", new GalaxyTestConnectionCommand());
|
||||
commandLine.addSubcommand("galaxy-deploy-time", new GalaxyDeployTimeCommand());
|
||||
commandLine.addSubcommand("galaxy-discover", new GalaxyDiscoverCommand());
|
||||
commandLine.addSubcommand("galaxy-watch", new GalaxyWatchCommand());
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
abstract static class GalaxyCommand implements Callable<Integer> {
|
||||
@Mixin
|
||||
CommonOptions common = new CommonOptions();
|
||||
|
||||
@Option(names = "--json", description = "Write JSON output.")
|
||||
boolean json;
|
||||
|
||||
GalaxyRepositoryClient connect() {
|
||||
return GalaxyRepositoryClient.connect(common.resolved().toClientOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "galaxy-test", description = "Calls GalaxyRepository.TestConnection.")
|
||||
static final class GalaxyTestConnectionCommand extends GalaxyCommand {
|
||||
@Override
|
||||
public Integer call() {
|
||||
try (GalaxyRepositoryClient client = connect()) {
|
||||
boolean ok = client.testConnection();
|
||||
PrintWriter out = common.spec.commandLine().getOut();
|
||||
if (json) {
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("command", "galaxy-test");
|
||||
output.put("options", common.redactedJsonMap());
|
||||
output.put("ok", ok);
|
||||
out.println(jsonObject(output));
|
||||
} else {
|
||||
out.println(ok);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "galaxy-deploy-time", description = "Calls GalaxyRepository.GetLastDeployTime.")
|
||||
static final class GalaxyDeployTimeCommand extends GalaxyCommand {
|
||||
@Override
|
||||
public Integer call() {
|
||||
try (GalaxyRepositoryClient client = connect()) {
|
||||
Optional<Instant> result = client.getLastDeployTime();
|
||||
PrintWriter out = common.spec.commandLine().getOut();
|
||||
if (json) {
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("command", "galaxy-deploy-time");
|
||||
output.put("options", common.redactedJsonMap());
|
||||
output.put("present", result.isPresent());
|
||||
output.put("timeOfLastDeploy", result.map(Instant::toString).orElse(""));
|
||||
out.println(jsonObject(output));
|
||||
} else if (result.isPresent()) {
|
||||
out.println(result.get());
|
||||
} else {
|
||||
out.println("(none)");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "galaxy-discover", description = "Calls GalaxyRepository.DiscoverHierarchy.")
|
||||
static final class GalaxyDiscoverCommand extends GalaxyCommand {
|
||||
@Override
|
||||
public Integer call() {
|
||||
try (GalaxyRepositoryClient client = connect()) {
|
||||
List<GalaxyObject> objects = client.discoverHierarchy();
|
||||
PrintWriter out = common.spec.commandLine().getOut();
|
||||
if (json) {
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("command", "galaxy-discover");
|
||||
output.put("options", common.redactedJsonMap());
|
||||
output.put("objects", objects.stream().map(MxGatewayCli::galaxyObjectMap).toList());
|
||||
out.println(jsonObject(output));
|
||||
} else {
|
||||
out.printf("count=%d%n", objects.size());
|
||||
for (GalaxyObject obj : objects) {
|
||||
out.printf(" %s [%s] attrs=%d%n",
|
||||
obj.getTagName(), obj.getBrowseName(), obj.getAttributesCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "galaxy-watch",
|
||||
description = "Streams GalaxyRepository.WatchDeployEvents until cancelled.")
|
||||
static final class GalaxyWatchCommand extends GalaxyCommand {
|
||||
@Option(
|
||||
names = "--last-seen-deploy-time",
|
||||
description =
|
||||
"Optional ISO-8601 instant. When supplied, the bootstrap event is suppressed if the cached"
|
||||
+ " deploy time matches.")
|
||||
String lastSeenDeployTime;
|
||||
|
||||
@Option(names = "--limit", defaultValue = "0", description = "Maximum events to print before exiting.")
|
||||
int limit;
|
||||
|
||||
@Override
|
||||
public Integer call() {
|
||||
Instant after = parseInstant(lastSeenDeployTime);
|
||||
try (GalaxyRepositoryClient client = connect();
|
||||
DeployEventStream events = client.watchDeployEvents(after)) {
|
||||
PrintWriter out = common.spec.commandLine().getOut();
|
||||
Thread shutdownHook = new Thread(events::close, "galaxy-watch-shutdown");
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
try {
|
||||
int count = 0;
|
||||
while (events.hasNext()) {
|
||||
DeployEvent event = events.next();
|
||||
if (json) {
|
||||
out.println(protoJson(event));
|
||||
} else {
|
||||
out.printf(
|
||||
"seq=%d observed=%s deployTime=%s objects=%d attributes=%d%n",
|
||||
event.getSequence(),
|
||||
formatTimestamp(event.getObservedAt()),
|
||||
event.getTimeOfLastDeployPresent()
|
||||
? formatTimestamp(event.getTimeOfLastDeploy())
|
||||
: "(none)",
|
||||
event.getObjectCount(),
|
||||
event.getAttributeCount());
|
||||
}
|
||||
out.flush();
|
||||
count++;
|
||||
if (limit > 0 && count >= limit) {
|
||||
events.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
||||
} catch (IllegalStateException ignored) {
|
||||
// JVM is already shutting down.
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Instant parseInstant(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return Instant.parse(value);
|
||||
}
|
||||
|
||||
private static String formatTimestamp(com.google.protobuf.Timestamp ts) {
|
||||
return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> galaxyObjectMap(GalaxyObject obj) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
values.put("gobjectId", obj.getGobjectId());
|
||||
values.put("tagName", obj.getTagName());
|
||||
values.put("containedName", obj.getContainedName());
|
||||
values.put("browseName", obj.getBrowseName());
|
||||
values.put("parentGobjectId", obj.getParentGobjectId());
|
||||
values.put("isArea", obj.getIsArea());
|
||||
values.put("categoryId", obj.getCategoryId());
|
||||
values.put("hostedByGobjectId", obj.getHostedByGobjectId());
|
||||
values.put("templateChain", new ArrayList<>(obj.getTemplateChainList()));
|
||||
List<Map<String, Object>> attrs = new ArrayList<>();
|
||||
for (GalaxyAttribute attr : obj.getAttributesList()) {
|
||||
Map<String, Object> attrMap = new LinkedHashMap<>();
|
||||
attrMap.put("attributeName", attr.getAttributeName());
|
||||
attrMap.put("fullTagReference", attr.getFullTagReference());
|
||||
attrMap.put("mxDataType", attr.getMxDataType());
|
||||
attrMap.put("dataTypeName", attr.getDataTypeName());
|
||||
attrMap.put("isArray", attr.getIsArray());
|
||||
attrMap.put("arrayDimension", attr.getArrayDimension());
|
||||
attrMap.put("arrayDimensionPresent", attr.getArrayDimensionPresent());
|
||||
attrMap.put("mxAttributeCategory", attr.getMxAttributeCategory());
|
||||
attrMap.put("securityClassification", attr.getSecurityClassification());
|
||||
attrMap.put("isHistorized", attr.getIsHistorized());
|
||||
attrMap.put("isAlarm", attr.getIsAlarm());
|
||||
attrs.add(attrMap);
|
||||
}
|
||||
values.put("attributes", attrs);
|
||||
return values;
|
||||
}
|
||||
|
||||
@Command(name = "version", description = "Prints the Java client version.")
|
||||
public static final class VersionCommand implements Callable<Integer> {
|
||||
@Spec
|
||||
|
||||
@@ -25,6 +25,7 @@ sourceSets {
|
||||
srcDir rootProject.file('../../src/MxGateway.Contracts/Protos')
|
||||
include 'mxaccess_gateway.proto'
|
||||
include 'mxaccess_worker.proto'
|
||||
include 'galaxy_repository.proto'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package com.dohertylan.mxgateway.client;
|
||||
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.stub.ClientCallStreamObserver;
|
||||
import io.grpc.stub.ClientResponseObserver;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
* Iterator-style adaptor over the {@code WatchDeployEvents} server-streaming
|
||||
* RPC. Mirrors {@link MxEventStream}: events arrive on a background gRPC thread
|
||||
* and are buffered in a bounded blocking queue; the iterator drains them.
|
||||
* Closing the stream cancels the underlying gRPC call.
|
||||
*/
|
||||
public final class DeployEventStream implements Iterator<DeployEvent>, AutoCloseable {
|
||||
private static final Object END = new Object();
|
||||
|
||||
private final BlockingQueue<Object> queue;
|
||||
private volatile ClientCallStreamObserver<WatchDeployEventsRequest> requestStream;
|
||||
private volatile boolean closed;
|
||||
private Object next;
|
||||
|
||||
DeployEventStream(int capacity) {
|
||||
queue = new ArrayBlockingQueue<>(capacity);
|
||||
}
|
||||
|
||||
ClientResponseObserver<WatchDeployEventsRequest, DeployEvent> observer() {
|
||||
return new ClientResponseObserver<>() {
|
||||
@Override
|
||||
public void beforeStart(ClientCallStreamObserver<WatchDeployEventsRequest> requestStream) {
|
||||
DeployEventStream.this.requestStream = requestStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(DeployEvent value) {
|
||||
offer(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
if (Status.fromThrowable(error).getCode() == Status.Code.CANCELLED && closed) {
|
||||
offer(END);
|
||||
return;
|
||||
}
|
||||
offer(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
offer(END);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (next == END) {
|
||||
return false;
|
||||
}
|
||||
if (next == null) {
|
||||
next = take();
|
||||
}
|
||||
if (next instanceof RuntimeException runtimeException) {
|
||||
next = END;
|
||||
throw runtimeException;
|
||||
}
|
||||
if (next instanceof Throwable throwable) {
|
||||
next = END;
|
||||
throw new MxGatewayException(
|
||||
"galaxy watch deploy events failed: " + throwable.getMessage(), throwable);
|
||||
}
|
||||
return next != END;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeployEvent next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Object value = next;
|
||||
next = null;
|
||||
return (DeployEvent) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
ClientCallStreamObserver<WatchDeployEventsRequest> stream = requestStream;
|
||||
if (stream != null) {
|
||||
stream.cancel("client cancelled deploy event stream", null);
|
||||
}
|
||||
offer(END);
|
||||
}
|
||||
|
||||
private Object take() {
|
||||
while (true) {
|
||||
try {
|
||||
return queue.take();
|
||||
} catch (InterruptedException error) {
|
||||
Thread.currentThread().interrupt();
|
||||
return new StatusRuntimeException(
|
||||
Status.CANCELLED.withDescription("interrupted while reading deploy events"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void offer(Object value) {
|
||||
Objects.requireNonNull(value, "value");
|
||||
if (value == END) {
|
||||
if (!queue.offer(value)) {
|
||||
queue.clear();
|
||||
queue.offer(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!queue.offer(value)) {
|
||||
ClientCallStreamObserver<WatchDeployEventsRequest> stream = requestStream;
|
||||
if (stream != null) {
|
||||
stream.cancel("client deploy event stream queue overflowed", null);
|
||||
}
|
||||
queue.clear();
|
||||
queue.offer(new MxGatewayException("galaxy watch deploy events queue overflowed"));
|
||||
queue.offer(END);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.dohertylan.mxgateway.client;
|
||||
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
|
||||
import io.grpc.stub.ClientCallStreamObserver;
|
||||
import io.grpc.stub.ClientResponseObserver;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Cancellable handle returned by the async {@code watchDeployEvents} variant.
|
||||
* Mirrors {@link MxGatewayEventSubscription} but for the Galaxy Repository
|
||||
* deploy-event stream.
|
||||
*/
|
||||
public final class DeployEventSubscription implements AutoCloseable {
|
||||
private final AtomicReference<ClientCallStreamObserver<WatchDeployEventsRequest>> requestStream =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean();
|
||||
|
||||
ClientResponseObserver<WatchDeployEventsRequest, DeployEvent> wrap(StreamObserver<DeployEvent> observer) {
|
||||
return new ClientResponseObserver<>() {
|
||||
@Override
|
||||
public void beforeStart(ClientCallStreamObserver<WatchDeployEventsRequest> stream) {
|
||||
requestStream.set(stream);
|
||||
if (cancelled.get()) {
|
||||
stream.cancel("client cancelled deploy event stream", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(DeployEvent value) {
|
||||
observer.onNext(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
observer.onError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
observer.onCompleted();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelled.set(true);
|
||||
ClientCallStreamObserver<WatchDeployEventsRequest> stream = requestStream.get();
|
||||
if (stream != null) {
|
||||
stream.cancel("client cancelled deploy event stream", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
package com.dohertylan.mxgateway.client;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
|
||||
import com.google.protobuf.Timestamp;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientInterceptors;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
|
||||
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import java.time.Instant;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
/**
|
||||
* Thin wrapper around the generated {@link GalaxyRepositoryGrpc} stubs that
|
||||
* exposes the three metadata-only RPCs of the Galaxy Repository service in
|
||||
* idiomatic Java types. Mirrors the constructor and option-handling style of
|
||||
* {@link MxGatewayClient}.
|
||||
*/
|
||||
public final class GalaxyRepositoryClient implements AutoCloseable {
|
||||
private final ManagedChannel ownedChannel;
|
||||
private final MxGatewayClientOptions options;
|
||||
private final GalaxyRepositoryGrpc.GalaxyRepositoryBlockingStub blockingStub;
|
||||
private final GalaxyRepositoryGrpc.GalaxyRepositoryFutureStub futureStub;
|
||||
private final GalaxyRepositoryGrpc.GalaxyRepositoryStub asyncStub;
|
||||
|
||||
private GalaxyRepositoryClient(ManagedChannel channel, MxGatewayClientOptions options) {
|
||||
this.ownedChannel = channel;
|
||||
this.options = options;
|
||||
Channel intercepted = ClientInterceptors.intercept(channel, new MxGatewayAuthInterceptor(options.apiKey()));
|
||||
blockingStub = GalaxyRepositoryGrpc.newBlockingStub(intercepted);
|
||||
futureStub = GalaxyRepositoryGrpc.newFutureStub(intercepted);
|
||||
asyncStub = GalaxyRepositoryGrpc.newStub(intercepted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a client over a caller-managed {@link Channel}. The caller owns
|
||||
* channel lifecycle; {@link #close()} is a no-op for this constructor.
|
||||
*/
|
||||
public GalaxyRepositoryClient(Channel channel, MxGatewayClientOptions options) {
|
||||
this.ownedChannel = null;
|
||||
this.options = Objects.requireNonNull(options, "options");
|
||||
Channel intercepted = ClientInterceptors.intercept(channel, new MxGatewayAuthInterceptor(options.apiKey()));
|
||||
blockingStub = GalaxyRepositoryGrpc.newBlockingStub(intercepted);
|
||||
futureStub = GalaxyRepositoryGrpc.newFutureStub(intercepted);
|
||||
asyncStub = GalaxyRepositoryGrpc.newStub(intercepted);
|
||||
}
|
||||
|
||||
/** Build a new client and own its channel; close shuts the channel down. */
|
||||
public static GalaxyRepositoryClient connect(MxGatewayClientOptions options) {
|
||||
return new GalaxyRepositoryClient(createChannel(options), options);
|
||||
}
|
||||
|
||||
public GalaxyRepositoryGrpc.GalaxyRepositoryBlockingStub rawBlockingStub() {
|
||||
return withDeadline(blockingStub);
|
||||
}
|
||||
|
||||
public GalaxyRepositoryGrpc.GalaxyRepositoryFutureStub rawFutureStub() {
|
||||
return withDeadline(futureStub);
|
||||
}
|
||||
|
||||
public GalaxyRepositoryGrpc.GalaxyRepositoryStub rawAsyncStub() {
|
||||
return asyncStub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the {@code TestConnection} RPC and return the {@code ok} flag.
|
||||
*/
|
||||
public boolean testConnection() {
|
||||
try {
|
||||
TestConnectionReply reply = rawBlockingStub().testConnection(TestConnectionRequest.getDefaultInstance());
|
||||
return reply.getOk();
|
||||
} catch (RuntimeException error) {
|
||||
if (error instanceof MxGatewayException) {
|
||||
throw error;
|
||||
}
|
||||
throw MxGatewayErrors.fromGrpc("galaxy test connection", error);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> testConnectionAsync() {
|
||||
return toCompletable(rawFutureStub().testConnection(TestConnectionRequest.getDefaultInstance()))
|
||||
.thenApply(TestConnectionReply::getOk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the {@code GetLastDeployTime} RPC. Returns {@link Optional#empty()}
|
||||
* when the server reports {@code present=false}.
|
||||
*/
|
||||
public Optional<Instant> getLastDeployTime() {
|
||||
try {
|
||||
GetLastDeployTimeReply reply =
|
||||
rawBlockingStub().getLastDeployTime(GetLastDeployTimeRequest.getDefaultInstance());
|
||||
return mapDeployTime(reply);
|
||||
} catch (RuntimeException error) {
|
||||
if (error instanceof MxGatewayException) {
|
||||
throw error;
|
||||
}
|
||||
throw MxGatewayErrors.fromGrpc("galaxy get last deploy time", error);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<Instant>> getLastDeployTimeAsync() {
|
||||
return toCompletable(rawFutureStub().getLastDeployTime(GetLastDeployTimeRequest.getDefaultInstance()))
|
||||
.thenApply(GalaxyRepositoryClient::mapDeployTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the {@code DiscoverHierarchy} RPC and return the generated
|
||||
* {@link GalaxyObject} messages directly. Callers can read every field of
|
||||
* the proto message without an extra DTO layer.
|
||||
*/
|
||||
public List<GalaxyObject> discoverHierarchy() {
|
||||
try {
|
||||
DiscoverHierarchyReply reply =
|
||||
rawBlockingStub().discoverHierarchy(DiscoverHierarchyRequest.getDefaultInstance());
|
||||
return reply.getObjectsList();
|
||||
} catch (RuntimeException error) {
|
||||
if (error instanceof MxGatewayException) {
|
||||
throw error;
|
||||
}
|
||||
throw MxGatewayErrors.fromGrpc("galaxy discover hierarchy", error);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<List<GalaxyObject>> discoverHierarchyAsync() {
|
||||
return toCompletable(rawFutureStub().discoverHierarchy(DiscoverHierarchyRequest.getDefaultInstance()))
|
||||
.thenApply(DiscoverHierarchyReply::getObjectsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to {@code WatchDeployEvents} via the async stub and consume
|
||||
* results through a blocking iterator. Closing the returned stream cancels
|
||||
* the underlying gRPC call.
|
||||
*
|
||||
* @param lastSeenDeployTime optional. When non-null, the bootstrap event is
|
||||
* suppressed if the cached deploy time matches.
|
||||
*/
|
||||
public DeployEventStream watchDeployEvents(Instant lastSeenDeployTime) {
|
||||
DeployEventStream stream = new DeployEventStream(16);
|
||||
withStreamDeadline(rawAsyncStub()).watchDeployEvents(buildWatchRequest(lastSeenDeployTime), stream.observer());
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator-style alias for {@link #watchDeployEvents(Instant)} matching the
|
||||
* task-spec signature.
|
||||
*/
|
||||
public Iterator<DeployEvent> watchDeployEventsIterator(Instant lastSeenDeployTime) {
|
||||
return watchDeployEvents(lastSeenDeployTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to {@code WatchDeployEvents} via the async stub, dispatching
|
||||
* each event to {@code observer}. The returned subscription is cancellable
|
||||
* and {@link AutoCloseable}.
|
||||
*/
|
||||
public DeployEventSubscription watchDeployEventsAsync(
|
||||
Instant lastSeenDeployTime, StreamObserver<DeployEvent> observer) {
|
||||
Objects.requireNonNull(observer, "observer");
|
||||
DeployEventSubscription subscription = new DeployEventSubscription();
|
||||
withStreamDeadline(rawAsyncStub())
|
||||
.watchDeployEvents(buildWatchRequest(lastSeenDeployTime), subscription.wrap(observer));
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private static WatchDeployEventsRequest buildWatchRequest(Instant lastSeenDeployTime) {
|
||||
WatchDeployEventsRequest.Builder builder = WatchDeployEventsRequest.newBuilder();
|
||||
if (lastSeenDeployTime != null) {
|
||||
builder.setLastSeenDeployTime(Timestamp.newBuilder()
|
||||
.setSeconds(lastSeenDeployTime.getEpochSecond())
|
||||
.setNanos(lastSeenDeployTime.getNano())
|
||||
.build());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private <T extends io.grpc.stub.AbstractStub<T>> T withStreamDeadline(T stub) {
|
||||
if (options.streamTimeout() == null || options.streamTimeout().isNegative()) {
|
||||
return stub;
|
||||
}
|
||||
return stub.withDeadlineAfter(options.streamTimeout().toNanos(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (ownedChannel != null) {
|
||||
ownedChannel.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAndAwaitTermination() throws InterruptedException {
|
||||
if (ownedChannel != null) {
|
||||
ownedChannel.shutdown();
|
||||
if (!ownedChannel.awaitTermination(options.connectTimeout().toMillis(), TimeUnit.MILLISECONDS)) {
|
||||
ownedChannel.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Instant> mapDeployTime(GetLastDeployTimeReply reply) {
|
||||
if (!reply.getPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Timestamp ts = reply.getTimeOfLastDeploy();
|
||||
return Optional.of(Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()));
|
||||
}
|
||||
|
||||
private static ManagedChannel createChannel(MxGatewayClientOptions options) {
|
||||
NettyChannelBuilder builder = NettyChannelBuilder.forTarget(options.endpoint())
|
||||
.maxInboundMessageSize(16 * 1024 * 1024);
|
||||
if (!options.connectTimeout().isNegative()) {
|
||||
builder.withOption(
|
||||
io.grpc.netty.shaded.io.netty.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
Math.toIntExact(options.connectTimeout().toMillis()));
|
||||
}
|
||||
if (options.plaintext()) {
|
||||
builder.usePlaintext();
|
||||
} else if (options.caCertificatePath() != null) {
|
||||
try {
|
||||
builder.sslContext(GrpcSslContexts.forClient()
|
||||
.trustManager(options.caCertificatePath().toFile())
|
||||
.build());
|
||||
} catch (SSLException error) {
|
||||
throw new MxGatewayException("failed to configure galaxy repository TLS", error);
|
||||
}
|
||||
} else {
|
||||
builder.useTransportSecurity();
|
||||
}
|
||||
if (!options.serverNameOverride().isBlank()) {
|
||||
builder.overrideAuthority(options.serverNameOverride());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private <T extends io.grpc.stub.AbstractStub<T>> T withDeadline(T stub) {
|
||||
if (options.callTimeout().isNegative()) {
|
||||
return stub;
|
||||
}
|
||||
return stub.withDeadlineAfter(options.callTimeout().toNanos(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private static <T> CompletableFuture<T> toCompletable(com.google.common.util.concurrent.ListenableFuture<T> source) {
|
||||
CompletableFuture<T> target = new CompletableFuture<>();
|
||||
Futures.addCallback(
|
||||
source,
|
||||
new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(T result) {
|
||||
target.complete(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
if (error instanceof RuntimeException runtimeException) {
|
||||
target.completeExceptionally(MxGatewayErrors.fromGrpc("galaxy async call", runtimeException));
|
||||
return;
|
||||
}
|
||||
target.completeExceptionally(error);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
target.whenComplete((ignoredResult, ignoredError) -> {
|
||||
if (target.isCancelled()) {
|
||||
source.cancel(true);
|
||||
}
|
||||
});
|
||||
return target;
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
package com.dohertylan.mxgateway.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.google.protobuf.Timestamp;
|
||||
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyAttribute;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest;
|
||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class GalaxyRepositoryClientTests {
|
||||
@Test
|
||||
void testConnectionReturnsOkAndSendsAuthMetadata() throws Exception {
|
||||
AtomicReference<String> authorization = new AtomicReference<>();
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void testConnection(
|
||||
TestConnectionRequest request, StreamObserver<TestConnectionReply> responseObserver) {
|
||||
responseObserver.onNext(TestConnectionReply.newBuilder().setOk(true).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, authorization);
|
||||
GalaxyRepositoryClient client = g.client("mxgw_galaxy_secret")) {
|
||||
assertTrue(client.testConnection());
|
||||
assertEquals("Bearer mxgw_galaxy_secret", authorization.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastDeployTimeReturnsEmptyWhenPresentFalse() throws Exception {
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void getLastDeployTime(
|
||||
GetLastDeployTimeRequest request, StreamObserver<GetLastDeployTimeReply> responseObserver) {
|
||||
responseObserver.onNext(
|
||||
GetLastDeployTimeReply.newBuilder().setPresent(false).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
Optional<Instant> result = client.getLastDeployTime();
|
||||
assertFalse(result.isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLastDeployTimeReturnsInstantWhenPresent() throws Exception {
|
||||
Timestamp expected = Timestamp.newBuilder().setSeconds(1_700_000_000L).setNanos(123_000_000).build();
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void getLastDeployTime(
|
||||
GetLastDeployTimeRequest request, StreamObserver<GetLastDeployTimeReply> responseObserver) {
|
||||
responseObserver.onNext(GetLastDeployTimeReply.newBuilder()
|
||||
.setPresent(true)
|
||||
.setTimeOfLastDeploy(expected)
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
Optional<Instant> result = client.getLastDeployTime();
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals(Instant.ofEpochSecond(1_700_000_000L, 123_000_000), result.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoverHierarchyReturnsObjectsAndAttributes() throws Exception {
|
||||
AtomicReference<DiscoverHierarchyRequest> seenRequest = new AtomicReference<>();
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void discoverHierarchy(
|
||||
DiscoverHierarchyRequest request, StreamObserver<DiscoverHierarchyReply> responseObserver) {
|
||||
seenRequest.set(request);
|
||||
responseObserver.onNext(DiscoverHierarchyReply.newBuilder()
|
||||
.addObjects(GalaxyObject.newBuilder()
|
||||
.setGobjectId(7)
|
||||
.setTagName("Pump_001")
|
||||
.setContainedName("Pump")
|
||||
.setBrowseName("Pump")
|
||||
.setParentGobjectId(1)
|
||||
.setIsArea(false)
|
||||
.setCategoryId(3)
|
||||
.setHostedByGobjectId(0)
|
||||
.addTemplateChain("$Pump")
|
||||
.addAttributes(GalaxyAttribute.newBuilder()
|
||||
.setAttributeName("Speed")
|
||||
.setFullTagReference("Pump_001.Speed")
|
||||
.setMxDataType(5)
|
||||
.setDataTypeName("MxFloat")
|
||||
.setIsArray(false)
|
||||
.setIsHistorized(true)))
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
List<GalaxyObject> objects = client.discoverHierarchy();
|
||||
assertEquals(1, objects.size());
|
||||
GalaxyObject only = objects.get(0);
|
||||
assertEquals(7, only.getGobjectId());
|
||||
assertEquals("Pump_001", only.getTagName());
|
||||
assertEquals(1, only.getAttributesCount());
|
||||
assertEquals("Pump_001.Speed", only.getAttributes(0).getFullTagReference());
|
||||
assertTrue(only.getAttributes(0).getIsHistorized());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void watchDeployEventsReceivesEventsInOrder() throws Exception {
|
||||
DeployEvent first = DeployEvent.newBuilder()
|
||||
.setSequence(1)
|
||||
.setObservedAt(Timestamp.newBuilder().setSeconds(1_700_000_000L).build())
|
||||
.setTimeOfLastDeploy(Timestamp.newBuilder().setSeconds(1_699_999_000L).build())
|
||||
.setTimeOfLastDeployPresent(true)
|
||||
.setObjectCount(42)
|
||||
.setAttributeCount(123)
|
||||
.build();
|
||||
DeployEvent second = DeployEvent.newBuilder()
|
||||
.setSequence(2)
|
||||
.setObservedAt(Timestamp.newBuilder().setSeconds(1_700_000_100L).build())
|
||||
.setTimeOfLastDeployPresent(false)
|
||||
.setObjectCount(43)
|
||||
.setAttributeCount(125)
|
||||
.build();
|
||||
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void watchDeployEvents(
|
||||
WatchDeployEventsRequest request, StreamObserver<DeployEvent> responseObserver) {
|
||||
responseObserver.onNext(first);
|
||||
responseObserver.onNext(second);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
try (DeployEventStream stream = client.watchDeployEvents(null)) {
|
||||
assertTrue(stream.hasNext());
|
||||
DeployEvent event1 = stream.next();
|
||||
assertEquals(1L, event1.getSequence());
|
||||
assertEquals(42, event1.getObjectCount());
|
||||
assertTrue(event1.getTimeOfLastDeployPresent());
|
||||
|
||||
assertTrue(stream.hasNext());
|
||||
DeployEvent event2 = stream.next();
|
||||
assertEquals(2L, event2.getSequence());
|
||||
assertFalse(event2.getTimeOfLastDeployPresent());
|
||||
|
||||
assertFalse(stream.hasNext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void watchDeployEventsPropagatesLastSeenDeployTime() throws Exception {
|
||||
AtomicReference<WatchDeployEventsRequest> seen = new AtomicReference<>();
|
||||
Instant lastSeen = Instant.ofEpochSecond(1_700_000_000L, 250_000_000);
|
||||
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void watchDeployEvents(
|
||||
WatchDeployEventsRequest request, StreamObserver<DeployEvent> responseObserver) {
|
||||
seen.set(request);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
try (DeployEventStream stream = client.watchDeployEvents(lastSeen)) {
|
||||
assertFalse(stream.hasNext());
|
||||
}
|
||||
}
|
||||
|
||||
WatchDeployEventsRequest request = seen.get();
|
||||
assertNotNull(request);
|
||||
Timestamp expected = request.getLastSeenDeployTime();
|
||||
assertEquals(lastSeen.getEpochSecond(), expected.getSeconds());
|
||||
assertEquals(lastSeen.getNano(), expected.getNanos());
|
||||
}
|
||||
|
||||
@Test
|
||||
void watchDeployEventsClientCancellationTearsDownCleanly() throws Exception {
|
||||
CountDownLatch cancelObserved = new CountDownLatch(1);
|
||||
|
||||
TestService service = new TestService() {
|
||||
@Override
|
||||
public void watchDeployEvents(
|
||||
WatchDeployEventsRequest request, StreamObserver<DeployEvent> responseObserver) {
|
||||
io.grpc.stub.ServerCallStreamObserver<DeployEvent> serverObserver =
|
||||
(io.grpc.stub.ServerCallStreamObserver<DeployEvent>) responseObserver;
|
||||
serverObserver.setOnCancelHandler(cancelObserved::countDown);
|
||||
|
||||
DeployEvent bootstrap = DeployEvent.newBuilder()
|
||||
.setSequence(1)
|
||||
.setObservedAt(Timestamp.newBuilder().setSeconds(1L).build())
|
||||
.build();
|
||||
responseObserver.onNext(bootstrap);
|
||||
// Server holds the stream open; cancellation must come from the client.
|
||||
}
|
||||
};
|
||||
|
||||
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||
GalaxyRepositoryClient client = g.client("")) {
|
||||
DeployEventStream stream = client.watchDeployEvents(null);
|
||||
assertTrue(stream.hasNext());
|
||||
assertEquals(1L, stream.next().getSequence());
|
||||
|
||||
stream.close();
|
||||
assertTrue(
|
||||
cancelObserved.await(5, TimeUnit.SECONDS),
|
||||
"server should observe client-side cancellation");
|
||||
assertFalse(stream.hasNext());
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class TestService extends GalaxyRepositoryGrpc.GalaxyRepositoryImplBase {
|
||||
@Override
|
||||
public void testConnection(
|
||||
TestConnectionRequest request, StreamObserver<TestConnectionReply> responseObserver) {
|
||||
responseObserver.onNext(TestConnectionReply.newBuilder().setOk(true).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLastDeployTime(
|
||||
GetLastDeployTimeRequest request, StreamObserver<GetLastDeployTimeReply> responseObserver) {
|
||||
responseObserver.onNext(GetLastDeployTimeReply.newBuilder().setPresent(false).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discoverHierarchy(
|
||||
DiscoverHierarchyRequest request, StreamObserver<DiscoverHierarchyReply> responseObserver) {
|
||||
responseObserver.onNext(DiscoverHierarchyReply.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watchDeployEvents(
|
||||
WatchDeployEventsRequest request, StreamObserver<DeployEvent> responseObserver) {
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
private record InProcessGalaxy(Server server, ManagedChannel channel) implements AutoCloseable {
|
||||
static InProcessGalaxy start(
|
||||
GalaxyRepositoryGrpc.GalaxyRepositoryImplBase service, AtomicReference<String> authorization)
|
||||
throws Exception {
|
||||
String serverName = "mxgw-galaxy-java-" + UUID.randomUUID();
|
||||
ServerInterceptor interceptor = new ServerInterceptor() {
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||
ServerCall<ReqT, RespT> call,
|
||||
Metadata headers,
|
||||
ServerCallHandler<ReqT, RespT> next) {
|
||||
authorization.set(headers.get(MxGatewayAuthInterceptor.AUTHORIZATION_HEADER));
|
||||
return next.startCall(call, headers);
|
||||
}
|
||||
};
|
||||
Server server = InProcessServerBuilder.forName(serverName)
|
||||
.directExecutor()
|
||||
.addService(io.grpc.ServerInterceptors.intercept(service, interceptor))
|
||||
.build()
|
||||
.start();
|
||||
ManagedChannel channel = InProcessChannelBuilder.forName(serverName)
|
||||
.directExecutor()
|
||||
.build();
|
||||
return new InProcessGalaxy(server, channel);
|
||||
}
|
||||
|
||||
GalaxyRepositoryClient client(String apiKey) {
|
||||
return new GalaxyRepositoryClient(
|
||||
channel,
|
||||
MxGatewayClientOptions.builder()
|
||||
.endpoint("in-process")
|
||||
.apiKey(apiKey)
|
||||
.plaintext(true)
|
||||
.callTimeout(Duration.ofSeconds(5))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
channel.shutdownNow();
|
||||
server.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
package galaxy_repository.v1;
|
||||
|
||||
import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
@io.grpc.stub.annotations.GrpcGenerated
|
||||
public final class GalaxyRepositoryGrpc {
|
||||
|
||||
private GalaxyRepositoryGrpc() {}
|
||||
|
||||
public static final java.lang.String SERVICE_NAME = "galaxy_repository.v1.GalaxyRepository";
|
||||
|
||||
// Static method descriptors that strictly reflect the proto.
|
||||
private static volatile io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> getTestConnectionMethod;
|
||||
|
||||
@io.grpc.stub.annotations.RpcMethod(
|
||||
fullMethodName = SERVICE_NAME + '/' + "TestConnection",
|
||||
requestType = galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest.class,
|
||||
responseType = galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply.class,
|
||||
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
public static io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> getTestConnectionMethod() {
|
||||
io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> getTestConnectionMethod;
|
||||
if ((getTestConnectionMethod = GalaxyRepositoryGrpc.getTestConnectionMethod) == null) {
|
||||
synchronized (GalaxyRepositoryGrpc.class) {
|
||||
if ((getTestConnectionMethod = GalaxyRepositoryGrpc.getTestConnectionMethod) == null) {
|
||||
GalaxyRepositoryGrpc.getTestConnectionMethod = getTestConnectionMethod =
|
||||
io.grpc.MethodDescriptor.<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply>newBuilder()
|
||||
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "TestConnection"))
|
||||
.setSampledToLocalTracing(true)
|
||||
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest.getDefaultInstance()))
|
||||
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply.getDefaultInstance()))
|
||||
.setSchemaDescriptor(new GalaxyRepositoryMethodDescriptorSupplier("TestConnection"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return getTestConnectionMethod;
|
||||
}
|
||||
|
||||
private static volatile io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> getGetLastDeployTimeMethod;
|
||||
|
||||
@io.grpc.stub.annotations.RpcMethod(
|
||||
fullMethodName = SERVICE_NAME + '/' + "GetLastDeployTime",
|
||||
requestType = galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest.class,
|
||||
responseType = galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply.class,
|
||||
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
public static io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> getGetLastDeployTimeMethod() {
|
||||
io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> getGetLastDeployTimeMethod;
|
||||
if ((getGetLastDeployTimeMethod = GalaxyRepositoryGrpc.getGetLastDeployTimeMethod) == null) {
|
||||
synchronized (GalaxyRepositoryGrpc.class) {
|
||||
if ((getGetLastDeployTimeMethod = GalaxyRepositoryGrpc.getGetLastDeployTimeMethod) == null) {
|
||||
GalaxyRepositoryGrpc.getGetLastDeployTimeMethod = getGetLastDeployTimeMethod =
|
||||
io.grpc.MethodDescriptor.<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply>newBuilder()
|
||||
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetLastDeployTime"))
|
||||
.setSampledToLocalTracing(true)
|
||||
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest.getDefaultInstance()))
|
||||
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply.getDefaultInstance()))
|
||||
.setSchemaDescriptor(new GalaxyRepositoryMethodDescriptorSupplier("GetLastDeployTime"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return getGetLastDeployTimeMethod;
|
||||
}
|
||||
|
||||
private static volatile io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> getDiscoverHierarchyMethod;
|
||||
|
||||
@io.grpc.stub.annotations.RpcMethod(
|
||||
fullMethodName = SERVICE_NAME + '/' + "DiscoverHierarchy",
|
||||
requestType = galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest.class,
|
||||
responseType = galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply.class,
|
||||
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
public static io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> getDiscoverHierarchyMethod() {
|
||||
io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> getDiscoverHierarchyMethod;
|
||||
if ((getDiscoverHierarchyMethod = GalaxyRepositoryGrpc.getDiscoverHierarchyMethod) == null) {
|
||||
synchronized (GalaxyRepositoryGrpc.class) {
|
||||
if ((getDiscoverHierarchyMethod = GalaxyRepositoryGrpc.getDiscoverHierarchyMethod) == null) {
|
||||
GalaxyRepositoryGrpc.getDiscoverHierarchyMethod = getDiscoverHierarchyMethod =
|
||||
io.grpc.MethodDescriptor.<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply>newBuilder()
|
||||
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "DiscoverHierarchy"))
|
||||
.setSampledToLocalTracing(true)
|
||||
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest.getDefaultInstance()))
|
||||
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply.getDefaultInstance()))
|
||||
.setSchemaDescriptor(new GalaxyRepositoryMethodDescriptorSupplier("DiscoverHierarchy"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return getDiscoverHierarchyMethod;
|
||||
}
|
||||
|
||||
private static volatile io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> getWatchDeployEventsMethod;
|
||||
|
||||
@io.grpc.stub.annotations.RpcMethod(
|
||||
fullMethodName = SERVICE_NAME + '/' + "WatchDeployEvents",
|
||||
requestType = galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest.class,
|
||||
responseType = galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent.class,
|
||||
methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
|
||||
public static io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> getWatchDeployEventsMethod() {
|
||||
io.grpc.MethodDescriptor<galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> getWatchDeployEventsMethod;
|
||||
if ((getWatchDeployEventsMethod = GalaxyRepositoryGrpc.getWatchDeployEventsMethod) == null) {
|
||||
synchronized (GalaxyRepositoryGrpc.class) {
|
||||
if ((getWatchDeployEventsMethod = GalaxyRepositoryGrpc.getWatchDeployEventsMethod) == null) {
|
||||
GalaxyRepositoryGrpc.getWatchDeployEventsMethod = getWatchDeployEventsMethod =
|
||||
io.grpc.MethodDescriptor.<galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest, galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent>newBuilder()
|
||||
.setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
|
||||
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "WatchDeployEvents"))
|
||||
.setSampledToLocalTracing(true)
|
||||
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest.getDefaultInstance()))
|
||||
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent.getDefaultInstance()))
|
||||
.setSchemaDescriptor(new GalaxyRepositoryMethodDescriptorSupplier("WatchDeployEvents"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return getWatchDeployEventsMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new async stub that supports all call types for the service
|
||||
*/
|
||||
public static GalaxyRepositoryStub newStub(io.grpc.Channel channel) {
|
||||
io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryStub> factory =
|
||||
new io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryStub>() {
|
||||
@java.lang.Override
|
||||
public GalaxyRepositoryStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryStub(channel, callOptions);
|
||||
}
|
||||
};
|
||||
return GalaxyRepositoryStub.newStub(factory, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blocking-style stub that supports all types of calls on the service
|
||||
*/
|
||||
public static GalaxyRepositoryBlockingV2Stub newBlockingV2Stub(
|
||||
io.grpc.Channel channel) {
|
||||
io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryBlockingV2Stub> factory =
|
||||
new io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryBlockingV2Stub>() {
|
||||
@java.lang.Override
|
||||
public GalaxyRepositoryBlockingV2Stub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryBlockingV2Stub(channel, callOptions);
|
||||
}
|
||||
};
|
||||
return GalaxyRepositoryBlockingV2Stub.newStub(factory, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blocking-style stub that supports unary and streaming output calls on the service
|
||||
*/
|
||||
public static GalaxyRepositoryBlockingStub newBlockingStub(
|
||||
io.grpc.Channel channel) {
|
||||
io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryBlockingStub> factory =
|
||||
new io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryBlockingStub>() {
|
||||
@java.lang.Override
|
||||
public GalaxyRepositoryBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryBlockingStub(channel, callOptions);
|
||||
}
|
||||
};
|
||||
return GalaxyRepositoryBlockingStub.newStub(factory, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ListenableFuture-style stub that supports unary calls on the service
|
||||
*/
|
||||
public static GalaxyRepositoryFutureStub newFutureStub(
|
||||
io.grpc.Channel channel) {
|
||||
io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryFutureStub> factory =
|
||||
new io.grpc.stub.AbstractStub.StubFactory<GalaxyRepositoryFutureStub>() {
|
||||
@java.lang.Override
|
||||
public GalaxyRepositoryFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryFutureStub(channel, callOptions);
|
||||
}
|
||||
};
|
||||
return GalaxyRepositoryFutureStub.newStub(factory, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public interface AsyncService {
|
||||
|
||||
/**
|
||||
*/
|
||||
default void testConnection(galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> responseObserver) {
|
||||
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getTestConnectionMethod(), responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
default void getLastDeployTime(galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> responseObserver) {
|
||||
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetLastDeployTimeMethod(), responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
default void discoverHierarchy(galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> responseObserver) {
|
||||
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getDiscoverHierarchyMethod(), responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Server-stream of deploy events. The server emits the current state immediately
|
||||
* on subscribe (so clients can bootstrap their cache without waiting for the next
|
||||
* deploy), then emits one event each time the gateway's hierarchy cache observes
|
||||
* a new galaxy.time_of_last_deploy. The sequence field is monotonically
|
||||
* increasing per server start; gaps indicate the per-subscriber buffer dropped
|
||||
* older events because the client was too slow.
|
||||
* </pre>
|
||||
*/
|
||||
default void watchDeployEvents(galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> responseObserver) {
|
||||
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getWatchDeployEventsMethod(), responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for the server implementation of the service GalaxyRepository.
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public static abstract class GalaxyRepositoryImplBase
|
||||
implements io.grpc.BindableService, AsyncService {
|
||||
|
||||
@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
|
||||
return GalaxyRepositoryGrpc.bindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub to allow clients to do asynchronous rpc calls to service GalaxyRepository.
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public static final class GalaxyRepositoryStub
|
||||
extends io.grpc.stub.AbstractAsyncStub<GalaxyRepositoryStub> {
|
||||
private GalaxyRepositoryStub(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
protected GalaxyRepositoryStub build(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryStub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void testConnection(galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> responseObserver) {
|
||||
io.grpc.stub.ClientCalls.asyncUnaryCall(
|
||||
getChannel().newCall(getTestConnectionMethod(), getCallOptions()), request, responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void getLastDeployTime(galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> responseObserver) {
|
||||
io.grpc.stub.ClientCalls.asyncUnaryCall(
|
||||
getChannel().newCall(getGetLastDeployTimeMethod(), getCallOptions()), request, responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void discoverHierarchy(galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> responseObserver) {
|
||||
io.grpc.stub.ClientCalls.asyncUnaryCall(
|
||||
getChannel().newCall(getDiscoverHierarchyMethod(), getCallOptions()), request, responseObserver);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Server-stream of deploy events. The server emits the current state immediately
|
||||
* on subscribe (so clients can bootstrap their cache without waiting for the next
|
||||
* deploy), then emits one event each time the gateway's hierarchy cache observes
|
||||
* a new galaxy.time_of_last_deploy. The sequence field is monotonically
|
||||
* increasing per server start; gaps indicate the per-subscriber buffer dropped
|
||||
* older events because the client was too slow.
|
||||
* </pre>
|
||||
*/
|
||||
public void watchDeployEvents(galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest request,
|
||||
io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> responseObserver) {
|
||||
io.grpc.stub.ClientCalls.asyncServerStreamingCall(
|
||||
getChannel().newCall(getWatchDeployEventsMethod(), getCallOptions()), request, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub to allow clients to do synchronous rpc calls to service GalaxyRepository.
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public static final class GalaxyRepositoryBlockingV2Stub
|
||||
extends io.grpc.stub.AbstractBlockingStub<GalaxyRepositoryBlockingV2Stub> {
|
||||
private GalaxyRepositoryBlockingV2Stub(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
protected GalaxyRepositoryBlockingV2Stub build(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryBlockingV2Stub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply testConnection(galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getTestConnectionMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply getLastDeployTime(galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getGetLastDeployTimeMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply discoverHierarchy(galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest request) throws io.grpc.StatusException {
|
||||
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
|
||||
getChannel(), getDiscoverHierarchyMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Server-stream of deploy events. The server emits the current state immediately
|
||||
* on subscribe (so clients can bootstrap their cache without waiting for the next
|
||||
* deploy), then emits one event each time the gateway's hierarchy cache observes
|
||||
* a new galaxy.time_of_last_deploy. The sequence field is monotonically
|
||||
* increasing per server start; gaps indicate the per-subscriber buffer dropped
|
||||
* older events because the client was too slow.
|
||||
* </pre>
|
||||
*/
|
||||
@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918")
|
||||
public io.grpc.stub.BlockingClientCall<?, galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent>
|
||||
watchDeployEvents(galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall(
|
||||
getChannel(), getWatchDeployEventsMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub to allow clients to do limited synchronous rpc calls to service GalaxyRepository.
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public static final class GalaxyRepositoryBlockingStub
|
||||
extends io.grpc.stub.AbstractBlockingStub<GalaxyRepositoryBlockingStub> {
|
||||
private GalaxyRepositoryBlockingStub(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
protected GalaxyRepositoryBlockingStub build(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryBlockingStub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply testConnection(galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
getChannel(), getTestConnectionMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply getLastDeployTime(galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
getChannel(), getGetLastDeployTimeMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply discoverHierarchy(galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingUnaryCall(
|
||||
getChannel(), getDiscoverHierarchyMethod(), getCallOptions(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Server-stream of deploy events. The server emits the current state immediately
|
||||
* on subscribe (so clients can bootstrap their cache without waiting for the next
|
||||
* deploy), then emits one event each time the gateway's hierarchy cache observes
|
||||
* a new galaxy.time_of_last_deploy. The sequence field is monotonically
|
||||
* increasing per server start; gaps indicate the per-subscriber buffer dropped
|
||||
* older events because the client was too slow.
|
||||
* </pre>
|
||||
*/
|
||||
public java.util.Iterator<galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent> watchDeployEvents(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest request) {
|
||||
return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
|
||||
getChannel(), getWatchDeployEventsMethod(), getCallOptions(), request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A stub to allow clients to do ListenableFuture-style rpc calls to service GalaxyRepository.
|
||||
* <pre>
|
||||
* Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||
* database). Lets clients enumerate the deployed object hierarchy and each
|
||||
* object's dynamic attributes so they know what tag references to subscribe
|
||||
* to via the MxAccessGateway service.
|
||||
* </pre>
|
||||
*/
|
||||
public static final class GalaxyRepositoryFutureStub
|
||||
extends io.grpc.stub.AbstractFutureStub<GalaxyRepositoryFutureStub> {
|
||||
private GalaxyRepositoryFutureStub(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
protected GalaxyRepositoryFutureStub build(
|
||||
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||
return new GalaxyRepositoryFutureStub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public com.google.common.util.concurrent.ListenableFuture<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply> testConnection(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest request) {
|
||||
return io.grpc.stub.ClientCalls.futureUnaryCall(
|
||||
getChannel().newCall(getTestConnectionMethod(), getCallOptions()), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public com.google.common.util.concurrent.ListenableFuture<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply> getLastDeployTime(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest request) {
|
||||
return io.grpc.stub.ClientCalls.futureUnaryCall(
|
||||
getChannel().newCall(getGetLastDeployTimeMethod(), getCallOptions()), request);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public com.google.common.util.concurrent.ListenableFuture<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply> discoverHierarchy(
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest request) {
|
||||
return io.grpc.stub.ClientCalls.futureUnaryCall(
|
||||
getChannel().newCall(getDiscoverHierarchyMethod(), getCallOptions()), request);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int METHODID_TEST_CONNECTION = 0;
|
||||
private static final int METHODID_GET_LAST_DEPLOY_TIME = 1;
|
||||
private static final int METHODID_DISCOVER_HIERARCHY = 2;
|
||||
private static final int METHODID_WATCH_DEPLOY_EVENTS = 3;
|
||||
|
||||
private static final class MethodHandlers<Req, Resp> implements
|
||||
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
|
||||
io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
|
||||
io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
|
||||
io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
|
||||
private final AsyncService serviceImpl;
|
||||
private final int methodId;
|
||||
|
||||
MethodHandlers(AsyncService serviceImpl, int methodId) {
|
||||
this.serviceImpl = serviceImpl;
|
||||
this.methodId = methodId;
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("unchecked")
|
||||
public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
|
||||
switch (methodId) {
|
||||
case METHODID_TEST_CONNECTION:
|
||||
serviceImpl.testConnection((galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest) request,
|
||||
(io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply>) responseObserver);
|
||||
break;
|
||||
case METHODID_GET_LAST_DEPLOY_TIME:
|
||||
serviceImpl.getLastDeployTime((galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest) request,
|
||||
(io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply>) responseObserver);
|
||||
break;
|
||||
case METHODID_DISCOVER_HIERARCHY:
|
||||
serviceImpl.discoverHierarchy((galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest) request,
|
||||
(io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply>) responseObserver);
|
||||
break;
|
||||
case METHODID_WATCH_DEPLOY_EVENTS:
|
||||
serviceImpl.watchDeployEvents((galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest) request,
|
||||
(io.grpc.stub.StreamObserver<galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent>) responseObserver);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("unchecked")
|
||||
public io.grpc.stub.StreamObserver<Req> invoke(
|
||||
io.grpc.stub.StreamObserver<Resp> responseObserver) {
|
||||
switch (methodId) {
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) {
|
||||
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
|
||||
.addMethod(
|
||||
getTestConnectionMethod(),
|
||||
io.grpc.stub.ServerCalls.asyncUnaryCall(
|
||||
new MethodHandlers<
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.TestConnectionReply>(
|
||||
service, METHODID_TEST_CONNECTION)))
|
||||
.addMethod(
|
||||
getGetLastDeployTimeMethod(),
|
||||
io.grpc.stub.ServerCalls.asyncUnaryCall(
|
||||
new MethodHandlers<
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.GetLastDeployTimeReply>(
|
||||
service, METHODID_GET_LAST_DEPLOY_TIME)))
|
||||
.addMethod(
|
||||
getDiscoverHierarchyMethod(),
|
||||
io.grpc.stub.ServerCalls.asyncUnaryCall(
|
||||
new MethodHandlers<
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply>(
|
||||
service, METHODID_DISCOVER_HIERARCHY)))
|
||||
.addMethod(
|
||||
getWatchDeployEventsMethod(),
|
||||
io.grpc.stub.ServerCalls.asyncServerStreamingCall(
|
||||
new MethodHandlers<
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.WatchDeployEventsRequest,
|
||||
galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent>(
|
||||
service, METHODID_WATCH_DEPLOY_EVENTS)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static abstract class GalaxyRepositoryBaseDescriptorSupplier
|
||||
implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
|
||||
GalaxyRepositoryBaseDescriptorSupplier() {}
|
||||
|
||||
@java.lang.Override
|
||||
public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
|
||||
return galaxy_repository.v1.GalaxyRepositoryOuterClass.getDescriptor();
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
|
||||
return getFileDescriptor().findServiceByName("GalaxyRepository");
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GalaxyRepositoryFileDescriptorSupplier
|
||||
extends GalaxyRepositoryBaseDescriptorSupplier {
|
||||
GalaxyRepositoryFileDescriptorSupplier() {}
|
||||
}
|
||||
|
||||
private static final class GalaxyRepositoryMethodDescriptorSupplier
|
||||
extends GalaxyRepositoryBaseDescriptorSupplier
|
||||
implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
|
||||
private final java.lang.String methodName;
|
||||
|
||||
GalaxyRepositoryMethodDescriptorSupplier(java.lang.String methodName) {
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
|
||||
return getServiceDescriptor().findMethodByName(methodName);
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
|
||||
|
||||
public static io.grpc.ServiceDescriptor getServiceDescriptor() {
|
||||
io.grpc.ServiceDescriptor result = serviceDescriptor;
|
||||
if (result == null) {
|
||||
synchronized (GalaxyRepositoryGrpc.class) {
|
||||
result = serviceDescriptor;
|
||||
if (result == null) {
|
||||
serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
|
||||
.setSchemaDescriptor(new GalaxyRepositoryFileDescriptorSupplier())
|
||||
.addMethod(getTestConnectionMethod())
|
||||
.addMethod(getGetLastDeployTimeMethod())
|
||||
.addMethod(getDiscoverHierarchyMethod())
|
||||
.addMethod(getWatchDeployEventsMethod())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+8077
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user