client/java: LazyBrowseNode walker for lazy hierarchy browse
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package com.zb.mom.ww.mxgateway.client;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters and shape options for {@link GalaxyRepositoryClient#browse(BrowseChildrenOptions)}.
|
||||||
|
* Mirror of the existing DiscoverHierarchy options for the lazy-browse path.
|
||||||
|
*
|
||||||
|
* <p>All filter fields are AND-combined server-side. Empty / unset fields disable
|
||||||
|
* that filter. The {@code includeAttributes} tri-state uses {@code null} to mean
|
||||||
|
* "let the server use its default"; non-{@code null} forwards the explicit flag.
|
||||||
|
*/
|
||||||
|
public final class BrowseChildrenOptions {
|
||||||
|
private final List<Integer> categoryIds;
|
||||||
|
private final List<String> templateChainContains;
|
||||||
|
private final String tagNameGlob;
|
||||||
|
private final Boolean includeAttributes;
|
||||||
|
private final boolean alarmBearingOnly;
|
||||||
|
private final boolean historizedOnly;
|
||||||
|
|
||||||
|
private BrowseChildrenOptions(Builder b) {
|
||||||
|
this.categoryIds = List.copyOf(b.categoryIds);
|
||||||
|
this.templateChainContains = List.copyOf(b.templateChainContains);
|
||||||
|
this.tagNameGlob = b.tagNameGlob;
|
||||||
|
this.includeAttributes = b.includeAttributes;
|
||||||
|
this.alarmBearingOnly = b.alarmBearingOnly;
|
||||||
|
this.historizedOnly = b.historizedOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return immutable list of category IDs to include; empty disables this filter. */
|
||||||
|
public List<Integer> getCategoryIds() { return categoryIds; }
|
||||||
|
|
||||||
|
/** @return immutable list of template names that must appear in each child's template chain. */
|
||||||
|
public List<String> getTemplateChainContains() { return templateChainContains; }
|
||||||
|
|
||||||
|
/** @return SQL-LIKE-style glob applied to {@code tag_name}; empty disables. */
|
||||||
|
public String getTagNameGlob() { return tagNameGlob; }
|
||||||
|
|
||||||
|
/** @return tri-state override for {@code include_attributes}; {@code null} keeps the server default. */
|
||||||
|
public Boolean getIncludeAttributes() { return includeAttributes; }
|
||||||
|
|
||||||
|
/** @return restrict to alarm-bearing objects. */
|
||||||
|
public boolean isAlarmBearingOnly() { return alarmBearingOnly; }
|
||||||
|
|
||||||
|
/** @return restrict to objects with at least one historized attribute. */
|
||||||
|
public boolean isHistorizedOnly() { return historizedOnly; }
|
||||||
|
|
||||||
|
/** @return a fresh builder. */
|
||||||
|
public static Builder builder() { return new Builder(); }
|
||||||
|
|
||||||
|
/** @return options with every filter disabled and {@code includeAttributes} unset. */
|
||||||
|
public static BrowseChildrenOptions empty() { return builder().build(); }
|
||||||
|
|
||||||
|
/** Fluent builder for {@link BrowseChildrenOptions}. */
|
||||||
|
public static final class Builder {
|
||||||
|
private List<Integer> categoryIds = Collections.emptyList();
|
||||||
|
private List<String> templateChainContains = Collections.emptyList();
|
||||||
|
private String tagNameGlob = "";
|
||||||
|
private Boolean includeAttributes = null;
|
||||||
|
private boolean alarmBearingOnly = false;
|
||||||
|
private boolean historizedOnly = false;
|
||||||
|
|
||||||
|
/** Sets the category-id filter. */
|
||||||
|
public Builder categoryIds(List<Integer> v) {
|
||||||
|
this.categoryIds = v == null ? Collections.emptyList() : v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the template-chain-contains filter. */
|
||||||
|
public Builder templateChainContains(List<String> v) {
|
||||||
|
this.templateChainContains = v == null ? Collections.emptyList() : v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the tag-name glob. */
|
||||||
|
public Builder tagNameGlob(String v) {
|
||||||
|
this.tagNameGlob = v == null ? "" : v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the tri-state {@code includeAttributes} override; {@code null} keeps the server default. */
|
||||||
|
public Builder includeAttributes(Boolean v) {
|
||||||
|
this.includeAttributes = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggles the alarm-bearing-only filter. */
|
||||||
|
public Builder alarmBearingOnly(boolean v) {
|
||||||
|
this.alarmBearingOnly = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggles the historized-only filter. */
|
||||||
|
public Builder historizedOnly(boolean v) {
|
||||||
|
this.historizedOnly = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the immutable options. */
|
||||||
|
public BrowseChildrenOptions build() {
|
||||||
|
return new BrowseChildrenOptions(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -4,6 +4,8 @@ import com.google.common.util.concurrent.FutureCallback;
|
|||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
||||||
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.BrowseChildrenReply;
|
||||||
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.BrowseChildrenRequest;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
||||||
@@ -37,6 +39,7 @@ import javax.net.ssl.SSLException;
|
|||||||
*/
|
*/
|
||||||
public final class GalaxyRepositoryClient implements AutoCloseable {
|
public final class GalaxyRepositoryClient implements AutoCloseable {
|
||||||
private static final int DISCOVER_HIERARCHY_PAGE_SIZE = 5000;
|
private static final int DISCOVER_HIERARCHY_PAGE_SIZE = 5000;
|
||||||
|
private static final int BROWSE_CHILDREN_PAGE_SIZE = 500;
|
||||||
|
|
||||||
private final ManagedChannel ownedChannel;
|
private final ManagedChannel ownedChannel;
|
||||||
private final MxGatewayClientOptions options;
|
private final MxGatewayClientOptions options;
|
||||||
@@ -213,6 +216,98 @@ public final class GalaxyRepositoryClient implements AutoCloseable {
|
|||||||
return discoverHierarchyPageAsync("", new java.util.ArrayList<>(), new java.util.HashSet<>());
|
return discoverHierarchyPageAsync("", new java.util.ArrayList<>(), new java.util.HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-browse entry point: fetches the root layer of the Galaxy hierarchy.
|
||||||
|
* Each returned {@link LazyBrowseNode} can be expanded on demand via
|
||||||
|
* {@link LazyBrowseNode#expand()} to load its direct children.
|
||||||
|
*
|
||||||
|
* @return the root nodes (no parent selector) with default options
|
||||||
|
* @throws MxGatewayException on transport or protocol failure
|
||||||
|
*/
|
||||||
|
public List<LazyBrowseNode> browse() {
|
||||||
|
return browse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-browse entry point with caller-supplied filters / shape.
|
||||||
|
*
|
||||||
|
* @param options filter and shape options; {@code null} means {@link BrowseChildrenOptions#empty()}
|
||||||
|
* @return the root nodes matching the options
|
||||||
|
* @throws MxGatewayException on transport or protocol failure
|
||||||
|
*/
|
||||||
|
public List<LazyBrowseNode> browse(BrowseChildrenOptions options) {
|
||||||
|
BrowseChildrenOptions effective = options == null ? BrowseChildrenOptions.empty() : options;
|
||||||
|
return browseChildrenInner(null, effective);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues a single {@code BrowseChildren} RPC and returns the raw reply.
|
||||||
|
* Callers wanting full control over pagination can drive the loop themselves.
|
||||||
|
*
|
||||||
|
* @param request the request to send
|
||||||
|
* @return the reply
|
||||||
|
* @throws MxGatewayException on transport or protocol failure
|
||||||
|
*/
|
||||||
|
public BrowseChildrenReply browseChildrenRaw(BrowseChildrenRequest request) {
|
||||||
|
try {
|
||||||
|
return rawBlockingStub().browseChildren(request);
|
||||||
|
} catch (RuntimeException error) {
|
||||||
|
if (error instanceof MxGatewayException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw MxGatewayErrors.fromGrpc("galaxy browse children", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drives the BrowseChildren paging loop for a single parent (or roots when
|
||||||
|
* {@code parentGobjectId} is {@code null}). Detects repeated page tokens to
|
||||||
|
* avoid infinite loops on a buggy server.
|
||||||
|
*/
|
||||||
|
List<LazyBrowseNode> browseChildrenInner(Integer parentGobjectId, BrowseChildrenOptions options) {
|
||||||
|
java.util.ArrayList<LazyBrowseNode> nodes = new java.util.ArrayList<>();
|
||||||
|
java.util.HashSet<String> seenPageTokens = new java.util.HashSet<>();
|
||||||
|
String pageToken = "";
|
||||||
|
while (true) {
|
||||||
|
BrowseChildrenRequest.Builder builder = BrowseChildrenRequest.newBuilder()
|
||||||
|
.setPageSize(BROWSE_CHILDREN_PAGE_SIZE)
|
||||||
|
.setPageToken(pageToken)
|
||||||
|
.setAlarmBearingOnly(options.isAlarmBearingOnly())
|
||||||
|
.setHistorizedOnly(options.isHistorizedOnly());
|
||||||
|
if (parentGobjectId != null) {
|
||||||
|
builder.setParentGobjectId(parentGobjectId.intValue());
|
||||||
|
}
|
||||||
|
if (!options.getCategoryIds().isEmpty()) {
|
||||||
|
builder.addAllCategoryIds(options.getCategoryIds());
|
||||||
|
}
|
||||||
|
if (!options.getTemplateChainContains().isEmpty()) {
|
||||||
|
builder.addAllTemplateChainContains(options.getTemplateChainContains());
|
||||||
|
}
|
||||||
|
if (!options.getTagNameGlob().isEmpty()) {
|
||||||
|
builder.setTagNameGlob(options.getTagNameGlob());
|
||||||
|
}
|
||||||
|
if (options.getIncludeAttributes() != null) {
|
||||||
|
builder.setIncludeAttributes(options.getIncludeAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowseChildrenReply reply = browseChildrenRaw(builder.build());
|
||||||
|
|
||||||
|
for (int i = 0; i < reply.getChildrenCount(); i++) {
|
||||||
|
boolean hint = i < reply.getChildHasChildrenCount() && reply.getChildHasChildren(i);
|
||||||
|
nodes.add(new LazyBrowseNode(this, reply.getChildren(i), hint, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
pageToken = reply.getNextPageToken();
|
||||||
|
if (pageToken == null || pageToken.isEmpty()) {
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
if (!seenPageTokens.add(pageToken)) {
|
||||||
|
throw new MxGatewayException(
|
||||||
|
"galaxy browse children returned repeated page token: " + pageToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to {@code WatchDeployEvents} via the async stub and consumes
|
* Subscribes to {@code WatchDeployEvents} via the async stub and consumes
|
||||||
* results through a blocking iterator. Closing the returned stream cancels
|
* results through a blocking iterator. Closing the returned stream cancels
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package com.zb.mom.ww.mxgateway.client;
|
||||||
|
|
||||||
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One node in a lazy-loaded Galaxy browse tree. Holds the underlying
|
||||||
|
* {@link GalaxyObject} and exposes {@link #expand()} to fetch its direct
|
||||||
|
* children on demand. Expansion is one-shot: a second call is a no-op.
|
||||||
|
* Pagination of large sibling sets is handled internally by the client.
|
||||||
|
*/
|
||||||
|
public final class LazyBrowseNode {
|
||||||
|
private final GalaxyRepositoryClient client;
|
||||||
|
private final GalaxyObject object;
|
||||||
|
private final boolean hasChildrenHint;
|
||||||
|
private final BrowseChildrenOptions options;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private List<LazyBrowseNode> children = Collections.emptyList();
|
||||||
|
private boolean isExpanded;
|
||||||
|
|
||||||
|
LazyBrowseNode(
|
||||||
|
GalaxyRepositoryClient client,
|
||||||
|
GalaxyObject object,
|
||||||
|
boolean hasChildrenHint,
|
||||||
|
BrowseChildrenOptions options) {
|
||||||
|
this.client = client;
|
||||||
|
this.object = object;
|
||||||
|
this.hasChildrenHint = hasChildrenHint;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the underlying Galaxy object proto for this node. */
|
||||||
|
public GalaxyObject getObject() {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {@code true} when the server reports this node has at least one matching descendant. */
|
||||||
|
public boolean hasChildrenHint() {
|
||||||
|
return hasChildrenHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return a snapshot of direct children loaded by {@link #expand()}; empty until then. */
|
||||||
|
public List<LazyBrowseNode> getChildren() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return List.copyOf(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {@code true} after the first {@link #expand()} call completes. */
|
||||||
|
public boolean isExpanded() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return isExpanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches direct children from the gateway and populates {@link #getChildren()}.
|
||||||
|
* Idempotent: subsequent calls are no-ops and do not issue a second RPC.
|
||||||
|
*
|
||||||
|
* @throws MxGatewayException on transport or protocol failure
|
||||||
|
*/
|
||||||
|
public void expand() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (isExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<LazyBrowseNode> loaded =
|
||||||
|
client.browseChildrenInner(Integer.valueOf(object.getGobjectId()), options);
|
||||||
|
this.children = loaded;
|
||||||
|
this.isExpanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+210
@@ -8,6 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
|
|
||||||
import com.google.protobuf.Timestamp;
|
import com.google.protobuf.Timestamp;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
import galaxy_repository.v1.GalaxyRepositoryGrpc;
|
||||||
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.BrowseChildrenReply;
|
||||||
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.BrowseChildrenRequest;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyReply;
|
||||||
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DiscoverHierarchyRequest;
|
||||||
@@ -24,6 +26,7 @@ import io.grpc.Server;
|
|||||||
import io.grpc.ServerCall;
|
import io.grpc.ServerCall;
|
||||||
import io.grpc.ServerCallHandler;
|
import io.grpc.ServerCallHandler;
|
||||||
import io.grpc.ServerInterceptor;
|
import io.grpc.ServerInterceptor;
|
||||||
|
import io.grpc.Status;
|
||||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||||
import io.grpc.inprocess.InProcessServerBuilder;
|
import io.grpc.inprocess.InProcessServerBuilder;
|
||||||
import io.grpc.stub.ClientCallStreamObserver;
|
import io.grpc.stub.ClientCallStreamObserver;
|
||||||
@@ -31,9 +34,13 @@ import io.grpc.stub.ClientResponseObserver;
|
|||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -306,6 +313,209 @@ final class GalaxyRepositoryClientTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseNoParentReturnsRoots() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(1, "Plant", true), obj(2, "Other", false)),
|
||||||
|
List.of(true, false),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
List<LazyBrowseNode> roots = client.browse();
|
||||||
|
|
||||||
|
assertEquals(2, roots.size());
|
||||||
|
assertEquals("Plant", roots.get(0).getObject().getTagName());
|
||||||
|
assertTrue(roots.get(0).hasChildrenHint());
|
||||||
|
assertFalse(roots.get(0).isExpanded());
|
||||||
|
assertEquals("Other", roots.get(1).getObject().getTagName());
|
||||||
|
assertFalse(roots.get(1).hasChildrenHint());
|
||||||
|
assertFalse(roots.get(1).isExpanded());
|
||||||
|
assertEquals(1, service.calls.size());
|
||||||
|
assertFalse(service.calls.get(0).hasParentGobjectId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseExpandPopulatesChildrenAndMarksExpanded() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(1, "Plant", true)),
|
||||||
|
List.of(true),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(10, "Line1", false)),
|
||||||
|
List.of(false),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
List<LazyBrowseNode> roots = client.browse();
|
||||||
|
roots.get(0).expand();
|
||||||
|
|
||||||
|
assertTrue(roots.get(0).isExpanded());
|
||||||
|
assertEquals(1, roots.get(0).getChildren().size());
|
||||||
|
assertEquals("Line1", roots.get(0).getChildren().get(0).getObject().getTagName());
|
||||||
|
assertEquals(2, service.calls.size());
|
||||||
|
assertTrue(service.calls.get(1).hasParentGobjectId());
|
||||||
|
assertEquals(1, service.calls.get(1).getParentGobjectId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseExpandIdempotentNoSecondRpc() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(1, "Plant", true)),
|
||||||
|
List.of(true),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(10, "Line1", false)),
|
||||||
|
List.of(false),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
List<LazyBrowseNode> roots = client.browse();
|
||||||
|
roots.get(0).expand();
|
||||||
|
roots.get(0).expand();
|
||||||
|
|
||||||
|
assertEquals(2, service.calls.size());
|
||||||
|
assertEquals(1, roots.get(0).getChildren().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseExpandUnknownParentThrowsGalaxyNotFound() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(1, "Plant", true)),
|
||||||
|
List.of(true),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
service.errors.add(Status.NOT_FOUND.withDescription("Parent not found").asRuntimeException());
|
||||||
|
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
List<LazyBrowseNode> roots = client.browse();
|
||||||
|
|
||||||
|
MxGatewayException error = assertThrows(MxGatewayException.class, () -> roots.get(0).expand());
|
||||||
|
assertTrue(
|
||||||
|
error.getMessage().toLowerCase().contains("not found"),
|
||||||
|
"expected message to mention 'not found', got: " + error.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseExpandMultiPageGathersAllPages() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
// Roots
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(7, "Plant", true)),
|
||||||
|
List.of(true),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
// First child page with a next token
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(70, "ChildA", false), obj(71, "ChildB", false)),
|
||||||
|
List.of(false, false),
|
||||||
|
1L,
|
||||||
|
"7:abc:2"));
|
||||||
|
// Second child page closes the loop
|
||||||
|
service.replies.add(browseReply(
|
||||||
|
List.of(obj(72, "ChildC", false)),
|
||||||
|
List.of(false),
|
||||||
|
1L,
|
||||||
|
""));
|
||||||
|
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
List<LazyBrowseNode> roots = client.browse();
|
||||||
|
roots.get(0).expand();
|
||||||
|
|
||||||
|
assertEquals(3, roots.get(0).getChildren().size());
|
||||||
|
assertEquals(3, service.calls.size());
|
||||||
|
assertEquals("7:abc:2", service.calls.get(2).getPageToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void browseWithFilterForwardsToRequest() throws Exception {
|
||||||
|
BrowseChildrenService service = new BrowseChildrenService();
|
||||||
|
// Default reply is empty; only the request shape matters here.
|
||||||
|
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
|
||||||
|
GalaxyRepositoryClient client = g.client("")) {
|
||||||
|
client.browse(BrowseChildrenOptions.builder()
|
||||||
|
.tagNameGlob("Mixer*")
|
||||||
|
.alarmBearingOnly(true)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(1, service.calls.size());
|
||||||
|
BrowseChildrenRequest request = service.calls.get(0);
|
||||||
|
assertEquals("Mixer*", request.getTagNameGlob());
|
||||||
|
assertTrue(request.getAlarmBearingOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GalaxyObject obj(int id, String tag, boolean isArea) {
|
||||||
|
return GalaxyObject.newBuilder()
|
||||||
|
.setGobjectId(id)
|
||||||
|
.setTagName(tag)
|
||||||
|
.setBrowseName(tag)
|
||||||
|
.setIsArea(isArea)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BrowseChildrenReply browseReply(
|
||||||
|
List<GalaxyObject> children,
|
||||||
|
List<Boolean> childHasChildren,
|
||||||
|
long cacheSequence,
|
||||||
|
String nextPageToken) {
|
||||||
|
BrowseChildrenReply.Builder b = BrowseChildrenReply.newBuilder()
|
||||||
|
.setTotalChildCount(children.size())
|
||||||
|
.setCacheSequence(cacheSequence)
|
||||||
|
.setNextPageToken(nextPageToken);
|
||||||
|
b.addAllChildren(children);
|
||||||
|
b.addAllChildHasChildren(childHasChildren);
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BrowseChildrenService extends TestService {
|
||||||
|
final List<BrowseChildrenRequest> calls =
|
||||||
|
Collections.synchronizedList(new CopyOnWriteArrayList<>());
|
||||||
|
final Queue<BrowseChildrenReply> replies = new ArrayDeque<>();
|
||||||
|
final Queue<Throwable> errors = new ArrayDeque<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void browseChildren(
|
||||||
|
BrowseChildrenRequest request, StreamObserver<BrowseChildrenReply> responseObserver) {
|
||||||
|
calls.add(request);
|
||||||
|
BrowseChildrenReply reply;
|
||||||
|
Throwable err;
|
||||||
|
synchronized (this) {
|
||||||
|
// Prefer queued replies first; once they're exhausted, fall through to any
|
||||||
|
// queued error. This matches the .NET fake's ordering used by parity tests.
|
||||||
|
reply = replies.poll();
|
||||||
|
err = reply == null ? errors.poll() : null;
|
||||||
|
}
|
||||||
|
if (err != null) {
|
||||||
|
responseObserver.onError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (reply == null) {
|
||||||
|
reply = BrowseChildrenReply.getDefaultInstance();
|
||||||
|
}
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private abstract static class TestService extends GalaxyRepositoryGrpc.GalaxyRepositoryImplBase {
|
private abstract static class TestService extends GalaxyRepositoryGrpc.GalaxyRepositoryImplBase {
|
||||||
@Override
|
@Override
|
||||||
public void testConnection(
|
public void testConnection(
|
||||||
|
|||||||
Reference in New Issue
Block a user