From 82bd8198e2cf752369e07439f86fb9d47097d4aa Mon Sep 17 00:00:00 2001 From: kalynstricklin Date: Mon, 12 Oct 2026 17:22:10 -0500 Subject: [PATCH 1/3] initial deployment additions to connected systems --- .../consys/client/ConSysApiClient.java | 77 +++++++++++++++++++ .../consys/client/ConSysApiClientModule.java | 61 +++++++++++++-- 2 files changed, 131 insertions(+), 7 deletions(-) diff --git a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java index 4cbb853a1..39030e058 100644 --- a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java +++ b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java @@ -61,9 +61,12 @@ import org.sensorhub.api.data.IObsData; import org.sensorhub.api.procedure.IProcedureWithDesc; import org.sensorhub.api.semantic.IDerivedProperty; +import org.sensorhub.api.system.IDeploymentWithDesc; import org.sensorhub.api.system.ISystemWithDesc; import org.sensorhub.impl.common.IdEncodersBase32; import org.sensorhub.impl.service.consys.ResourceParseException; +import org.sensorhub.impl.service.consys.deployment.DeploymentBindingGeoJson; +import org.sensorhub.impl.service.consys.deployment.DeploymentBindingSmlJson; import org.sensorhub.impl.service.consys.feature.FoiBindingGeoJson; import org.sensorhub.impl.service.consys.obs.DataStreamBindingJson; import org.sensorhub.impl.service.consys.obs.DataStreamSchemaBindingOmJson; @@ -526,6 +529,80 @@ protected void endJsonCollection(JsonWriter writer, Collection lin } } + /*-------------*/ + /* Deployments */ + /*-------------*/ + + public CompletableFuture getDeploymentByUid(String uid, ResourceFormat format) + { + return sendGetRequest(endpoint.resolve(DEPLOYMENTS_COLLECTION + "?id=" + urlQueryEncode(uid)), format, body -> { + try + { + var ctx = new RequestContext(body); + + // use modified binding since the response contains a feature collection + var binding = new DeploymentBindingGeoJson(ctx, null, null, true) { + protected JsonReader getJsonReader(InputStream is) throws IOException + { + var reader = super.getJsonReader(is); + skipToCollectionItems(reader); + return reader; + } + }; + + return binding.deserialize(); + } + catch (IOException e) + { + e.printStackTrace(); + throw new CompletionException(e); + } + }); + } + + public CompletableFuture addDeployment(IDeploymentWithDesc deployment) + { + try + { + var buffer = new ByteArrayOutputStream(); + var ctx = new RequestContext(buffer); + + var binding = new DeploymentBindingSmlJson(ctx, null, false); + binding.serialize(null, deployment, false); + + return sendPostRequest( + endpoint.resolve(DEPLOYMENTS_COLLECTION), + ResourceFormat.SML_JSON, + buffer.toByteArray()); + } + catch (IOException e) + { + throw new IllegalStateException("Error initializing deployment binding", e); + } + } + + + public CompletableFuture updateDeployment(String deploymentID, IDeploymentWithDesc deployment) + { + try + { + var buffer = new ByteArrayOutputStream(); + var ctx = new RequestContext(buffer); + + var binding = new DeploymentBindingSmlJson(ctx, null, false); + binding.serialize(null, deployment, false); + + return sendPutRequest( + endpoint.resolve(DEPLOYMENTS_COLLECTION + "/" + deploymentID), + ResourceFormat.SML_JSON, + buffer.toByteArray()); + } + catch (IOException e) + { + throw new IllegalStateException("Error initializing deployment binding", e); + } + } + /*-------------------*/ /* Sampling Features */ /*-------------------*/ diff --git a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java index f34de8ac9..125398e2c 100644 --- a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java +++ b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java @@ -14,13 +14,7 @@ import org.sensorhub.api.datastore.system.SystemFilter; import org.sensorhub.api.event.EventUtils; import org.sensorhub.api.feature.FoiAddedEvent; -import org.sensorhub.api.system.ISystemWithDesc; -import org.sensorhub.api.system.SystemAddedEvent; -import org.sensorhub.api.system.SystemChangedEvent; -import org.sensorhub.api.system.SystemEnabledEvent; -import org.sensorhub.api.system.SystemRemovedEvent; -import org.sensorhub.api.system.SystemDisabledEvent; -import org.sensorhub.api.system.SystemEvent; +import org.sensorhub.api.system.*; import org.sensorhub.impl.module.AbstractModule; import org.sensorhub.impl.security.ClientAuth; import org.sensorhub.impl.service.consys.resource.ResourceFormat; @@ -41,6 +35,7 @@ public class ConSysApiClientModule extends AbstractModule String apiEndpointUrl; ConSysApiClient client; Map registeredSystems; + Map registeredDeployments; Map registeredFeatureIDs; NavigableMap dataStreams; Flow.Subscription registrySubscription; @@ -53,6 +48,13 @@ public static class SystemRegInfo private ISystemWithDesc system; } + public static class DeploymentRegInfo + { + private String deploymentID; + private BigId internalID; + private IDeploymentWithDesc deployment; + } + public static class StreamInfo { private IDataStreamInfo dataStream; @@ -66,6 +68,7 @@ public static class StreamInfo public ConSysApiClientModule() { this.registeredSystems = new ConcurrentHashMap<>(); + this.registeredDeployments = new ConcurrentHashMap<>(); this.registeredFeatureIDs = new ConcurrentHashMap<>(); this.dataStreams = new ConcurrentSkipListMap<>(); } @@ -130,6 +133,10 @@ protected void doStart() throws SensorHubException { registerSystemDataStreams(systemRegInfo); }); + dataBaseView.getDeploymentStore().selectEntries( + dataBaseView.getDeploymentStore().selectAllFilter()) + .forEach(entry -> registerDeployment(entry.getKey().getInternalID(), entry.getValue())); + dataBaseView.getFoiStore().selectEntries( dataBaseView.getFoiStore().selectAllFilter()) .forEach(entry -> registerSamplingFeature(entry.getKey(), entry.getValue())); @@ -207,6 +214,24 @@ private String tryUpdateSystem(ISystemWithDesc system) return null; } + private String tryUpdateDeployment(IDeploymentWithDesc deployment) + { + try { + var oldDeployment = client.getDeploymentByUid(deployment.getUniqueIdentifier(), ResourceFormat.JSON).get(); + if (oldDeployment != null) { + var deploymentID = oldDeployment.getId(); + var responseCode = client.updateDeployment(deploymentID, deployment).get(); + if (responseCode != HttpURLConnection.HTTP_NO_CONTENT) + throw new ClientException("Failed to update resource: " + + apiEndpointUrl + ConSysApiClient.DEPLOYMENTS_COLLECTION + "/" + deploymentID); + return deploymentID; + } + } catch (ExecutionException | InterruptedException | ClientException e) { + throw new RuntimeException(e); + } + return null; + } + private String tryUpdateSamplingFeature(IFeature feature) { try { @@ -260,6 +285,18 @@ protected SystemRegInfo registerSystem(BigId systemInternalID, ISystemWithDesc s } } + protected DeploymentRegInfo registerDeployment(BigId deploymentInternalID, IDeploymentWithDesc deployment) { + try { + String deploymentID = tryUpdateDeployment(deployment); + if(deploymentID == null) + deploymentID = client.addDeployment(deployment).get(); + + return registerDeploymentInfo(deploymentID, deploymentInternalID, deployment); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + protected void registerSamplingFeature(BigId featureInternalId, String systemId, IFeature feature) { try { @@ -312,6 +349,16 @@ private SystemRegInfo registerSystemInfo(String systemID, BigId systemInternalID return systemRegInfo; } + private DeploymentRegInfo registerDeploymentInfo(String deploymentID, BigId deploymentInternalID, IDeploymentWithDesc deployment) { + DeploymentRegInfo deploymentRegInfo = new DeploymentRegInfo(); + deploymentRegInfo.deploymentID = deploymentID; + deploymentRegInfo.internalID = deploymentInternalID; + deploymentRegInfo.deployment = deployment; + + registeredDeployments.put(deploymentInternalID, deploymentRegInfo); + return deploymentRegInfo; + } + protected List registerSystemDataStreams(SystemRegInfo system) { List addedStreams = new ArrayList<>(); From 41f4b0893ffa43414ce6ed6326f700ab224a945c Mon Sep 17 00:00:00 2001 From: kalynstricklin Date: Wed, 14 Oct 2026 13:22:35 -0500 Subject: [PATCH 2/3] add getDeploymentById --- .../service/consys/client/ConSysApiClient.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java index 39030e058..0c4cb8973 100644 --- a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java +++ b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClient.java @@ -533,6 +533,23 @@ protected void endJsonCollection(JsonWriter writer, Collection lin /* Deployments */ /*-------------*/ + public CompletableFuture getDeploymentById(String id, ResourceFormat format) + { + return sendGetRequest(endpoint.resolve(DEPLOYMENTS_COLLECTION + "/" + urlPathEncode(id)), format, body -> { + try + { + var ctx = new RequestContext(body); + var binding = new DeploymentBindingGeoJson(ctx, null, null, true); + return binding.deserialize(); + } + catch (IOException e) + { + e.printStackTrace(); + throw new CompletionException(e); + } + }); + } + public CompletableFuture getDeploymentByUid(String uid, ResourceFormat format) { return sendGetRequest(endpoint.resolve(DEPLOYMENTS_COLLECTION + "?id=" + urlQueryEncode(uid)), format, body -> { From 5bacc5da9500c176606d3bba2e678cfa29750165 Mon Sep 17 00:00:00 2001 From: kalynstricklin Date: Thu, 15 Oct 2026 09:22:40 -0500 Subject: [PATCH 3/3] Add deployment test and fix wildcard imports --- .../consys/client/ConSysApiClientModule.java | 9 +- .../consys/client/TestClientDeployments.java | 145 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 sensorhub-service-consys/src/test/java/org/sensorhub/impl/service/consys/client/TestClientDeployments.java diff --git a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java index 125398e2c..227cd6232 100644 --- a/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java +++ b/sensorhub-service-consys/src/main/java/org/sensorhub/impl/service/consys/client/ConSysApiClientModule.java @@ -14,7 +14,14 @@ import org.sensorhub.api.datastore.system.SystemFilter; import org.sensorhub.api.event.EventUtils; import org.sensorhub.api.feature.FoiAddedEvent; -import org.sensorhub.api.system.*; +import org.sensorhub.api.system.ISystemWithDesc; +import org.sensorhub.api.system.SystemAddedEvent; +import org.sensorhub.api.system.SystemChangedEvent; +import org.sensorhub.api.system.SystemEnabledEvent; +import org.sensorhub.api.system.SystemRemovedEvent; +import org.sensorhub.api.system.SystemDisabledEvent; +import org.sensorhub.api.system.SystemEvent; +import org.sensorhub.api.system.IDeploymentWithDesc; import org.sensorhub.impl.module.AbstractModule; import org.sensorhub.impl.security.ClientAuth; import org.sensorhub.impl.service.consys.resource.ResourceFormat; diff --git a/sensorhub-service-consys/src/test/java/org/sensorhub/impl/service/consys/client/TestClientDeployments.java b/sensorhub-service-consys/src/test/java/org/sensorhub/impl/service/consys/client/TestClientDeployments.java new file mode 100644 index 000000000..276e360a2 --- /dev/null +++ b/sensorhub-service-consys/src/test/java/org/sensorhub/impl/service/consys/client/TestClientDeployments.java @@ -0,0 +1,145 @@ +/***************************** BEGIN LICENSE BLOCK *************************** + +The contents of this file are subject to the Mozilla Public License, v. 2.0. +If a copy of the MPL was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the License. + +Copyright (C) 2026 Sensia Software LLC. All Rights Reserved. + +******************************* END LICENSE BLOCK ***************************/ + +package org.sensorhub.impl.service.consys.client; + +import com.google.common.base.Strings; +import org.junit.Test; +import org.sensorhub.api.system.IDeploymentWithDesc; +import org.sensorhub.impl.service.consys.resource.ResourceFormat; +import org.sensorhub.impl.service.consys.sensorml.DeploymentAdapter; +import org.vast.sensorML.SMLHelper; + +import java.net.HttpURLConnection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + + +public class TestClientDeployments extends TestClientBase +{ + + public TestClientDeployments() {} + + TestClientDeployments(String apiRootUrl) { this.apiRootUrl = apiRootUrl; } + + + @Test + public void testAddMultipleDeployments() throws Exception { + addDeployment(createDeployment(1), true); + addDeployment(createDeployment(10), true); + } + + @Test + public void testAddDeploymentAndGetById() throws Exception { + var deployment = createDeployment(1); + var id = addDeployment(deployment, true); + + var result = ConSysApiClient + .newBuilder(apiRootUrl) + .build() + .getDeploymentById(id, ResourceFormat.SML_JSON).get(); + + assertEquals(id, result.getId()); + assertEquals(deployment.getUniqueIdentifier(), result.getUniqueIdentifier()); + assertEquals(deployment.getName(), result.getName()); + assertEquals(deployment.getDescription(), result.getDescription()); + } + + @Test + public void testAddDeploymentAndGetByUid() throws Exception { + var deployment = createDeployment(1); + var id = addDeployment(deployment, true); + + var r = ConSysApiClient + .newBuilder(apiRootUrl) + .build() + .getDeploymentByUid(deployment.getUniqueIdentifier(), ResourceFormat.SML_JSON).get(); + + assertEquals(id, r.getId()); + assertEquals(deployment.getUniqueIdentifier(), r.getUniqueIdentifier()); + assertEquals(deployment.getName(), r.getName()); + assertEquals(deployment.getDescription(), r.getDescription()); + } + + + @Test + public void testUpdateDeployment() throws Exception + { + var deployment = createDeployment(2); + var id = addDeployment(deployment, false); + + var updated = createDeployment(2); + updated.getFullDescription().setName("Updated deployment name"); + updated.getFullDescription().setDescription("Updated deployment description"); + + var statusCode = ConSysApiClient + .newBuilder(apiRootUrl) + .build() + .updateDeployment(id, updated).get().intValue(); + + assertEquals(HttpURLConnection.HTTP_NO_CONTENT, statusCode); + + var result = ConSysApiClient + .newBuilder(apiRootUrl) + .build() + .getDeploymentById(id, ResourceFormat.SML_JSON).get(); + + assertEquals(id, result.getId()); + assertEquals(updated.getUniqueIdentifier(), result.getUniqueIdentifier()); + assertEquals("Updated deployment name", result.getName()); + assertEquals("Updated deployment description", result.getDescription()); + checkDeploymentIdExists(id); + } + + + private IDeploymentWithDesc createDeployment(int num) + { + var deploymentUid = getDeploymentUid(num); + + var deployment = new SMLHelper().createDeployment() + .uniqueID(deploymentUid) + .name("Test Deployment #" + num) + .description("Test deployment #" + num + " created using Java API client") + .build(); + return new DeploymentAdapter(deployment); + } + + private String addDeployment(IDeploymentWithDesc deployment, boolean checkHead) throws Exception + { + var id = ConSysApiClient + .newBuilder(apiRootUrl) + .build() + .addDeployment(deployment).get(); + + assertFalse(Strings.isNullOrEmpty(id)); + + if (checkHead) + checkDeploymentIdExists(id); + + return id; + } + + protected String getDeploymentUid(int num) + { + return "urn:osh:test:deployment:" + num; + } + + + protected void checkDeploymentIdExists(String deploymentId) throws Exception + { + checkUriExists(apiRootUrl + "/" + DEPLOYMENT_COLLECTION + "/" + deploymentId); + } + +}