From 7ceb17ff4088c07c45e4d53003821d20eaf78bb3 Mon Sep 17 00:00:00 2001 From: george_dimitropoulos <67735908+gdimitropoulos-sotec@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:20:17 +0100 Subject: [PATCH] [#3517] Support searching for gateway devices Implement gateway filter for JDBC (H2 & Postgresql) device registry: - add optional parameter "isGateway" - functionality for listing only devices or gateways - add documentation Also-by: Matthias Feurer Signed-off-by: Georgios Dimitropoulos --- .../PubSubBasedInternalCommandSender.java | 5 +- .../util/RegistryManagementConstants.java | 7 +- .../service/http/AbstractHttpEndpoint.java | 11 +- .../store/device/TableManagementStore.java | 162 ++++++++++++------ .../base/jdbc/store/device/base.h2.sql.yaml | 56 +++++- .../store/device/base.postgresql.sql.yaml | 62 ++++++- .../AbstractDeviceManagementService.java | 10 +- ...elegatingDeviceManagementHttpEndpoint.java | 9 +- .../device/DeviceManagementService.java | 5 +- ...ractDeviceManagementSearchDevicesTest.java | 129 +++++++------- ...atingDeviceManagementHttpEndpointTest.java | 6 +- .../impl/DeviceManagementServiceImpl.java | 5 +- ...Test.java => BaseRegistryServiceTest.java} | 0 ...asedDeviceManagementSearchDevicesTest.java | 82 +++++++-- .../MongoDbBasedDeviceManagementService.java | 3 +- .../api/management/device-registry-v1.yaml | 20 ++- .../content/user-guide/device-registry.md | 9 +- 17 files changed, 409 insertions(+), 172 deletions(-) rename services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/{RegistryServiceTest.java => BaseRegistryServiceTest.java} (100%) diff --git a/clients/command-pubsub/src/main/java/org/eclipse/hono/client/command/pubsub/PubSubBasedInternalCommandSender.java b/clients/command-pubsub/src/main/java/org/eclipse/hono/client/command/pubsub/PubSubBasedInternalCommandSender.java index bd5a9cd69e..d93904f33e 100644 --- a/clients/command-pubsub/src/main/java/org/eclipse/hono/client/command/pubsub/PubSubBasedInternalCommandSender.java +++ b/clients/command-pubsub/src/main/java/org/eclipse/hono/client/command/pubsub/PubSubBasedInternalCommandSender.java @@ -102,10 +102,7 @@ private Map getAttributes(final PubSubBasedCommand command) { attributes.put(PubSubMessageHelper.PUBSUB_PROPERTY_PROJECT_ID, projectId); attributes.put(PubSubMessageHelper.PUBSUB_PROPERTY_RESPONSE_REQUIRED, !command.isOneWay()); Optional.ofNullable(command.getGatewayId()).ifPresent( - id -> { - attributes.put(MessageHelper.APP_PROPERTY_GATEWAY_ID, id); - attributes.put(MessageHelper.APP_PROPERTY_CMD_VIA, id); - }); + id -> attributes.put(MessageHelper.APP_PROPERTY_GATEWAY_ID, id)); return attributes; } } diff --git a/core/src/main/java/org/eclipse/hono/util/RegistryManagementConstants.java b/core/src/main/java/org/eclipse/hono/util/RegistryManagementConstants.java index 94771ffec6..47c79e2c0c 100644 --- a/core/src/main/java/org/eclipse/hono/util/RegistryManagementConstants.java +++ b/core/src/main/java/org/eclipse/hono/util/RegistryManagementConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2021 Contributors to the Eclipse Foundation + * Copyright (c) 2019, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -118,6 +118,11 @@ public final class RegistryManagementConstants extends RequestResponseApiConstan */ public static final String PARAM_SORT_JSON = "sortJson"; + /** + * The name of the boolean filter query parameter for searching gateways or only devices. + */ + public static final String PARAM_IS_GATEWAY = "isGateway"; + // DEVICES diff --git a/service-base/src/main/java/org/eclipse/hono/service/http/AbstractHttpEndpoint.java b/service-base/src/main/java/org/eclipse/hono/service/http/AbstractHttpEndpoint.java index 908662e03f..87ae4c3dd7 100644 --- a/service-base/src/main/java/org/eclipse/hono/service/http/AbstractHttpEndpoint.java +++ b/service-base/src/main/java/org/eclipse/hono/service/http/AbstractHttpEndpoint.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -83,6 +83,15 @@ public abstract class AbstractHttpEndpoint ex } }; + /** + * A function that tries to parse a string into an Optional boolean. + * It takes a valid boolean string value (e.g "true", "false") and returns an Optional of boolean type, + * if input string can be parsed as boolean else Optional.empty. + */ + protected static final Function> CONVERTER_BOOLEAN = s -> { + return Strings.isNullOrEmpty(s) ? Optional.empty() : Optional.of(Boolean.valueOf(s)); + }; + /** * The configuration properties for this endpoint. */ diff --git a/services/base-jdbc/src/main/java/org/eclipse/hono/service/base/jdbc/store/device/TableManagementStore.java b/services/base-jdbc/src/main/java/org/eclipse/hono/service/base/jdbc/store/device/TableManagementStore.java index 5cb64508bf..595d846a1a 100644 --- a/services/base-jdbc/src/main/java/org/eclipse/hono/service/base/jdbc/store/device/TableManagementStore.java +++ b/services/base-jdbc/src/main/java/org/eclipse/hono/service/base/jdbc/store/device/TableManagementStore.java @@ -97,9 +97,13 @@ public class TableManagementStore extends AbstractDeviceStore { private final Statement updateDeviceVersionStatement; private final Statement countDevicesOfTenantStatement; - private final Statement countDevicesWithFilter; + private final Statement countDevicesWithFilterStatement; + private final Statement countGatewaysOfTenantStatement; + private final Statement countOnlyDevicesOfTenantStatement; - private final Statement findDevicesStatement; + private final Statement findDevicesOfTenantStatement; + private final Statement findGatewaysOfTenantStatement; + private final Statement findOnlyDevicesOfTenantStatement; private final Statement findDevicesOfTenantWithFilterStatement; /** @@ -204,20 +208,48 @@ public TableManagementStore(final JDBCClient client, final Tracer tracer, final .validateParameters( TENANT_ID); - this.countDevicesWithFilter = cfg + this.countGatewaysOfTenantStatement = cfg + .getRequiredStatement("countGatewaysOfTenant") + .validateParameters( + TENANT_ID, + DEVICE_ID); + + this.countOnlyDevicesOfTenantStatement = cfg + .getRequiredStatement("countOnlyDevicesOfTenant") + .validateParameters( + TENANT_ID, + DEVICE_ID); + + this.countDevicesWithFilterStatement = cfg .getRequiredStatement("countDevicesOfTenantWithFilter") .validateParameters( TENANT_ID, FIELD, VALUE); - this.findDevicesStatement = cfg + this.findDevicesOfTenantStatement = cfg .getRequiredStatement("findDevicesOfTenant") .validateParameters( TENANT_ID, PAGE_SIZE, PAGE_OFFSET); + this.findOnlyDevicesOfTenantStatement = cfg + .getRequiredStatement("findOnlyDevicesOfTenant") + .validateParameters( + TENANT_ID, + DEVICE_ID, + PAGE_SIZE, + PAGE_OFFSET); + + this.findGatewaysOfTenantStatement = cfg + .getRequiredStatement("findGatewaysOfTenant") + .validateParameters( + TENANT_ID, + DEVICE_ID, + PAGE_SIZE, + PAGE_OFFSET); + this.findDevicesOfTenantWithFilterStatement = cfg .getRequiredStatement("findDevicesOfTenantWithFilter") .validateParameters( @@ -228,46 +260,6 @@ public TableManagementStore(final JDBCClient client, final Tracer tracer, final PAGE_OFFSET); } - private static Future checkUpdateOutcome(final UpdateResult updateResult) { - - if (updateResult.getUpdated() < 0) { - // conflict - log.debug("Optimistic lock broke"); - return Future.failedFuture(new OptimisticLockingException()); - } - - return Future.succeededFuture(); - - } - - private static Future extractVersionForUpdate(final ResultSet device, final Optional resourceVersion) { - final Optional version = device.getRows(true).stream().map(o -> o.getString(VERSION)).findAny(); - - if (version.isEmpty()) { - log.debug("No version or no row found -> entity not found"); - return Future.failedFuture(new EntityNotFoundException()); - } - - final var currentVersion = version.get(); - - return resourceVersion - // if we expect a certain version - .>map(expected -> { - // check ... - if (expected.equals(currentVersion)) { - // version matches, continue with current version - return Future.succeededFuture(currentVersion); - } else { - // version does not match, abort - return Future.failedFuture(new OptimisticLockingException()); - } - } - ) - // if we don't expect a version, continue with the current - .orElseGet(() -> Future.succeededFuture(currentVersion)); - - } - /** * Read a device and lock it for updates. *

@@ -850,6 +842,46 @@ private Future recoverNotFound(final Span span, final Throwable err, fina } } + private static Future checkUpdateOutcome(final UpdateResult updateResult) { + + if (updateResult.getUpdated() < 0) { + // conflict + log.debug("Optimistic lock broke"); + return Future.failedFuture(new OptimisticLockingException()); + } + + return Future.succeededFuture(); + + } + + private static Future extractVersionForUpdate(final ResultSet device, final Optional resourceVersion) { + final Optional version = device.getRows(true).stream().map(o -> o.getString("version")).findAny(); + + if (version.isEmpty()) { + log.debug("No version or no row found -> entity not found"); + return Future.failedFuture(new EntityNotFoundException()); + } + + final var currentVersion = version.get(); + + return resourceVersion + // if we expect a certain version + .>map(expected -> { + // check ... + if (expected.equals(currentVersion)) { + // version matches, continue with current version + return Future.succeededFuture(currentVersion); + } else { + // version does not match, abort + return Future.failedFuture(new OptimisticLockingException()); + } + } + ) + // if we don't expect a version, continue with the current + .orElseGet(() -> Future.succeededFuture(currentVersion)); + + } + /** * Get all credentials for a device. *

@@ -925,25 +957,43 @@ private List parseCredentials(final ResultSet result) { * @param pageSize The page size. * @param pageOffset The page offset. * @param filters The list of filters (currently only the first value of the list is used). + * Will be ignored if parameter isGateway is being used. + * @param isGateway Optional filter for searching only gateways or only devices. + * If given parameter is Optional.empty() result will contain both gateways and devices. * @param spanContext The span to contribute to. * @return A future containing devices. */ - public Future> findDevices(final String tenantId, final int pageSize, final int pageOffset, final List filters, + public Future> findDevices(final String tenantId, final int pageSize, final int pageOffset, final List filters, final Optional isGateway, final SpanContext spanContext) { - final var filter = filters.stream().findFirst(); + final Statement findDeviceSqlStatement; + final Statement countStatement; + final String field; + final String value; + + if (isGateway.isPresent()) { + field = ""; + value = ""; - final String field = filter.map(filter1 -> filter1.getField().toString().replace("/", "")).orElse(""); - final var value = filter.map(filter1 -> - filter1.getValue().toString() - .replace("/", "") - .replace("*", "%") - .replace("?", "_") - ).orElse(""); + findDeviceSqlStatement = isGateway.get() ? this.findGatewaysOfTenantStatement : this.findOnlyDevicesOfTenantStatement; + countStatement = isGateway.get() ? this.countGatewaysOfTenantStatement : this.countOnlyDevicesOfTenantStatement; + } else { + final var filter = filters.stream().findFirst(); + + field = filter.map(filter1 -> filter1.getField().toString().replace("/", "")).orElse(""); + value = filter.map(filter1 -> + filter1.getValue().toString() + .replace("/", "") + .replace("*", "%") + .replace("?", "_") + ).orElse(""); + + + findDeviceSqlStatement = (filter.isPresent()) ? findDevicesOfTenantWithFilterStatement : this.findDevicesOfTenantStatement; + countStatement = (filter.isPresent()) ? countDevicesWithFilterStatement : this.countDevicesOfTenantStatement; + } - final Statement findDeviceSqlStatement = (filter.isPresent()) ? findDevicesOfTenantWithFilterStatement : this.findDevicesStatement; - final Statement countStatement = (filter.isPresent()) ? countDevicesWithFilter : this.countDevicesOfTenantStatement; final var expanded = findDeviceSqlStatement.expand(map -> { map.put(TENANT_ID, tenantId); @@ -985,5 +1035,3 @@ public Future> findDevices(final String tenantId, fin .onComplete(x -> span.finish()); } } - - diff --git a/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.h2.sql.yaml b/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.h2.sql.yaml index bf59f21565..7a826f3c26 100644 --- a/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.h2.sql.yaml +++ b/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.h2.sql.yaml @@ -1,18 +1,36 @@ - countDevicesOfTenantWithFilter: | - SELECT COUNT(*) AS deviceCount FROM %1$s - WHERE + SELECT COUNT(*) AS deviceCount FROM %1$s + WHERE tenant_id=:tenant_id AND LOCATE(CONCAT_WS(':', :field, REPLACE(:value, '"')), REPLACE(data, '"')) OR REPLACE(data, '"') LIKE CONCAT('%%', :field, ':', REPLACE(:value, '"')) +countGatewaysOfTenant: | + SELECT COUNT(*) AS deviceCount + FROM %1$s + WHERE + tenant_id=:tenant_id + AND + LOCATE(CONCAT(device_id, '|'), + (SELECT CONCAT(REPLACE(group_concat(DISTINCT ids separator '|'), ',', '|'), '|') FROM + (SELECT DISTINCT REGEXP_REPLACE(REGEXP_SUBSTR(DATA, '"via":\[.*?\]' ), '"via":\[|\]|"', '') as ids FROM %1$s WHERE tenant_id=:tenant_id ))) > 0 + +countOnlyDevicesOfTenant: | + SELECT COUNT(*) AS deviceCount + FROM %1$s + WHERE + tenant_id=:tenant_id + AND + LOCATE(CONCAT(device_id, '|'), + (SELECT CONCAT(REPLACE(group_concat(DISTINCT ids separator '|'), ',', '|'), '|') FROM + (SELECT DISTINCT REGEXP_REPLACE(REGEXP_SUBSTR(DATA, '"via":\[.*?\]' ), '"via":\[|\]|"', '') as ids FROM %1$s WHERE tenant_id=:tenant_id ))) = 0 findDevicesOfTenantWithFilter: | - SELECT * - FROM %s - WHERE + SELECT * + FROM %1$s + WHERE tenant_id=:tenant_id AND LOCATE(CONCAT_WS(':', :field, REPLACE(:value, '"')), REPLACE(data, '"')) @@ -21,3 +39,29 @@ findDevicesOfTenantWithFilter: | ORDER BY device_id ASC LIMIT :page_size OFFSET :page_offset + +findGatewaysOfTenant: | + SELECT * + FROM %1$s + WHERE + tenant_id=:tenant_id + AND + LOCATE(CONCAT(device_id, '|'), + (SELECT CONCAT(REPLACE(group_concat(DISTINCT ids separator '|'), ',', '|'), '|') FROM + (SELECT DISTINCT REPLACE(REGEXP_REPLACE(REGEXP_SUBSTR(DATA, '"via":\[.*?\]' ), '"via":\[|\]|"', ''), '\') as ids FROM %1$s WHERE tenant_id=:tenant_id ))) > 0 + ORDER BY device_id ASC + LIMIT :page_size + OFFSET :page_offset + +findOnlyDevicesOfTenant: | + SELECT * + FROM %1$s + WHERE + tenant_id=:tenant_id + AND + LOCATE(CONCAT(device_id, '|'), + (SELECT CONCAT(REPLACE(group_concat(DISTINCT ids separator '|'), ',', '|'), '|') FROM + (SELECT DISTINCT REGEXP_REPLACE(REGEXP_SUBSTR(DATA, '"via":\[.*?\]' ), '"via":\[|\]|"', '') as ids FROM %1$s WHERE tenant_id=:tenant_id ))) = 0 + ORDER BY device_id ASC + LIMIT :page_size + OFFSET :page_offset diff --git a/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.postgresql.sql.yaml b/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.postgresql.sql.yaml index 22f7b02532..3984d17fe6 100644 --- a/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.postgresql.sql.yaml +++ b/services/base-jdbc/src/main/resources/org/eclipse/hono/service/base/jdbc/store/device/base.postgresql.sql.yaml @@ -55,17 +55,39 @@ resolveGroups: | group_id in (select unnest((string_to_array(:group_ids,','))::varchar[])) countDevicesOfTenantWithFilter: | - SELECT COUNT(*) AS deviceCount FROM %1$s - WHERE + SELECT COUNT(*) AS deviceCount FROM %1$s + WHERE tenant_id=:tenant_id AND data->>:field LIKE CAST(:value AS VARCHAR) OR jsonb_extract_path(data,'ext')->>:field LIKE CAST(:value as varchar) +countGatewaysOfTenant: | + SELECT COUNT(*) AS deviceCount FROM %1$s + WHERE + tenant_id=:tenant_id + AND + device_id IN ( + SELECT replace(dr.device_id::text, '"', '') FROM ( + SELECT jsonb_array_elements(data -> 'via') AS device_id FROM device_registrations + WHERE tenant_id=:tenant_id AND data ->> 'via' != '') + as dr) + +countOnlyDevicesOfTenant: | + SELECT COUNT(*) AS deviceCount FROM %1$s + WHERE + tenant_id=:tenant_id + AND + device_id NOT IN ( + SELECT replace(dr.device_id::text, '"', '') FROM ( + SELECT jsonb_array_elements(data -> 'via') AS device_id FROM device_registrations + WHERE tenant_id=:tenant_id AND data ->> 'via' != '') + as dr) + findDevicesOfTenantWithFilter: | - SELECT * - FROM %s - WHERE + SELECT * + FROM %s + WHERE tenant_id=:tenant_id AND data->>:field LIKE CAST(:value AS VARCHAR) OR @@ -73,3 +95,33 @@ findDevicesOfTenantWithFilter: | ORDER BY device_id ASC LIMIT :page_size OFFSET :page_offset + +findGatewaysOfTenant: | + SELECT * + FROM %s + WHERE + tenant_id=:tenant_id + AND + device_id IN ( + SELECT replace(dr.device_id::text, '"', '') FROM ( + SELECT jsonb_array_elements(data -> 'via') AS device_id FROM device_registrations + WHERE tenant_id=:tenant_id AND data ->> 'via' != '') + as dr) + ORDER BY device_id ASC + LIMIT :page_size + OFFSET :page_offset + +findOnlyDevicesOfTenant: | + SELECT * + FROM %s + WHERE + tenant_id=:tenant_id + AND + device_id NOT IN ( + SELECT replace(dr.device_id::text, '"', '') FROM ( + SELECT jsonb_array_elements(data -> 'via') AS device_id FROM device_registrations + WHERE tenant_id=:tenant_id AND data ->> 'via' != '') + as dr) + ORDER BY device_id ASC + LIMIT :page_size + OFFSET :page_offset diff --git a/services/device-registry-base/src/main/java/org/eclipse/hono/deviceregistry/service/device/AbstractDeviceManagementService.java b/services/device-registry-base/src/main/java/org/eclipse/hono/deviceregistry/service/device/AbstractDeviceManagementService.java index a6e3b34e14..e82b05bc7f 100644 --- a/services/device-registry-base/src/main/java/org/eclipse/hono/deviceregistry/service/device/AbstractDeviceManagementService.java +++ b/services/device-registry-base/src/main/java/org/eclipse/hono/deviceregistry/service/device/AbstractDeviceManagementService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2020, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -213,7 +213,7 @@ protected Future> processDeleteDevicesOfTenant(final String tenantI /** * Finds devices for search criteria. *

- * This method is invoked by {@link #searchDevices(String, int, int, List, List, Span)} after all parameter checks + * This method is invoked by {@link #searchDevices(String, int, int, List, List, Optional, Span)} after all parameter checks * have succeeded. *

* This default implementation returns a future failed with a {@link org.eclipse.hono.client.ServerErrorException} @@ -225,6 +225,8 @@ protected Future> processDeleteDevicesOfTenant(final String tenantI * retrieve the whole result set page by page. * @param filters A list of filters. The filters are predicates that objects in the result set must match. * @param sortOptions A list of sort options. The sortOptions specify properties to sort the result set by. + * @param isGateway Optional filter for searching only gateways or only devices. + * If given parameter is Optional.empty() result will contain both gateways and devices. * @param span The active OpenTracing span to use for tracking this operation. *

* Implementations must not invoke the {@link Span#finish()} nor the {@link Span#finish(long)} @@ -242,6 +244,7 @@ protected Future>> processSearchDevic final int pageOffset, final List filters, final List sortOptions, + final Optional isGateway, final Span span) { return Future.failedFuture(new ServerErrorException( @@ -408,6 +411,7 @@ public final Future>> searchDevices( final int pageOffset, final List filters, final List sortOptions, + final Optional isGateway, final Span span) { Objects.requireNonNull(tenantId); @@ -429,7 +433,7 @@ public final Future>> searchDevices( tenantId, result.getStatus(), "tenant does not exist")) - : processSearchDevices(tenantId, pageSize, pageOffset, filters, sortOptions, span)) + : processSearchDevices(tenantId, pageSize, pageOffset, filters, sortOptions, isGateway, span)) .recover(t -> DeviceRegistryUtils.mapError(t, tenantId)); } } diff --git a/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpoint.java b/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpoint.java index 09e45625ed..35f3673a01 100644 --- a/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpoint.java +++ b/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpoint.java @@ -185,8 +185,14 @@ private void doSearchDevices(final RoutingContext ctx) { RegistryManagementConstants.PARAM_FILTER_JSON, Filter.class); final Future> sortOptions = decodeJsonFromRequestParameter(ctx, RegistryManagementConstants.PARAM_SORT_JSON, Sort.class); + final Future> isGateway = getRequestParameter( + ctx, + RegistryManagementConstants.PARAM_IS_GATEWAY, + Optional.empty(), + CONVERTER_BOOLEAN, + value -> true); - Future.all(pageSize, pageOffset, filters, sortOptions) + Future.all(pageSize, pageOffset, filters, sortOptions, isGateway) .onSuccess(ok -> TracingHelper.TAG_TENANT_ID.set(span, tenantId)) .compose(ok -> getService().searchDevices( tenantId, @@ -194,6 +200,7 @@ private void doSearchDevices(final RoutingContext ctx) { pageOffset.result(), filters.result(), sortOptions.result(), + isGateway.result(), span)) .onSuccess(operationResult -> writeResponse(ctx, operationResult, span)) .onFailure(t -> failRequest(ctx, t, span)) diff --git a/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DeviceManagementService.java b/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DeviceManagementService.java index 3717ca7e51..02a5058550 100644 --- a/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DeviceManagementService.java +++ b/services/device-registry-base/src/main/java/org/eclipse/hono/service/management/device/DeviceManagementService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2021 Contributors to the Eclipse Foundation + * Copyright (c) 2016, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -102,6 +102,8 @@ public interface DeviceManagementService { * Implementations must not invoke the {@link Span#finish()} nor the {@link Span#finish(long)} * methods. However,implementations may log (error) events on this span, set tags and use this span * as the parent for additional spans created as part of this method's execution. + * @param isGateway Optional filter for searching only gateways or only devices. + * If given parameter is Optional.empty() result will contain both gateways and devices. * @return A future indicating the outcome of the operation. *

* The future will be succeeded with a result containing the matching devices. Otherwise, the future will @@ -118,6 +120,7 @@ default Future>> searchDevices( final int pageOffset, final List filters, final List sortOptions, + final Optional isGateway, final Span span) { return Future.failedFuture(new ServerErrorException( diff --git a/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/AbstractDeviceManagementSearchDevicesTest.java b/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/AbstractDeviceManagementSearchDevicesTest.java index aab5a6ee72..4f6e384135 100644 --- a/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/AbstractDeviceManagementSearchDevicesTest.java +++ b/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/AbstractDeviceManagementSearchDevicesTest.java @@ -70,6 +70,7 @@ default void testSearchDevicesWhenNoDevicesAreFound(final VertxTestContext ctx) pageOffset, List.of(filter), List.of(), + Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.failing(t -> { ctx.verify(() -> Assertions.assertServiceInvocationException(t, HttpURLConnection.HTTP_NOT_FOUND)); @@ -94,7 +95,7 @@ default void testSearchDevicesWithAFilterSucceeds(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(false))) .compose(ok -> getDeviceManagementService() .searchDevices(tenantId, pageSize, pageOffset, List.of(filter), List.of(), - NoopSpan.INSTANCE)) + Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -124,20 +125,20 @@ default void testSearchDevicesWithMultipleFiltersSucceeds(final VertxTestContext createDevices(tenantId, Map.of( "testDevice1", new Device().setEnabled(true).setVia(List.of("gw-1")), "testDevice2", new Device().setEnabled(false))) - .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, List.of(filter1, filter2, filter3), - List.of(), NoopSpan.INSTANCE)) - .onComplete(ctx.succeeding(s -> { - ctx.verify(() -> { - assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); - - final SearchResult searchResult = s.getPayload(); - assertThat(searchResult.getTotal()).isEqualTo(1); - assertThat(searchResult.getResult()).hasSize(1); - assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice1"); - }); - ctx.completeNow(); - })); + .compose(ok -> getDeviceManagementService() + .searchDevices(tenantId, pageSize, pageOffset, List.of(filter1, filter2, filter3), + List.of(), Optional.empty(), NoopSpan.INSTANCE)) + .onComplete(ctx.succeeding(s -> { + ctx.verify(() -> { + assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); + + final SearchResult searchResult = s.getPayload(); + assertThat(searchResult.getTotal()).isEqualTo(1); + assertThat(searchResult.getResult()).hasSize(1); + assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice1"); + }); + ctx.completeNow(); + })); } /** @@ -158,7 +159,7 @@ default void testSearchDevicesWithPageSize(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(true))) .compose(ok -> getDeviceManagementService() .searchDevices(tenantId, pageSize, pageOffset, List.of(filter), List.of(), - NoopSpan.INSTANCE)) + Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -196,6 +197,7 @@ default void testSearchDevicesWithPageOffset(final VertxTestContext ctx) { 0, List.of(filter), List.of(), + Optional.empty(), NoopSpan.INSTANCE)) .compose(response -> { ctx.verify(() -> { @@ -217,6 +219,7 @@ default void testSearchDevicesWithPageOffset(final VertxTestContext ctx) { 1, List.of(filter), List.of(), + Optional.empty(), NoopSpan.INSTANCE); }) .onComplete(ctx.succeeding(s -> { @@ -253,7 +256,7 @@ default void testSearchDevicesWithSortOption(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(true).setExtensions(Map.of("id", "bbb")))) .compose(ok -> getDeviceManagementService() .searchDevices(tenantId, pageSize, pageOffset, List.of(filter), List.of(sortOption), - NoopSpan.INSTANCE)) + Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -291,6 +294,7 @@ default void testSearchDevicesSortsResultById(final VertxTestContext ctx) { 0, List.of(), List.of(), + Optional.empty(), NoopSpan.INSTANCE)) .compose(response -> { ctx.verify(() -> { @@ -309,7 +313,8 @@ default void testSearchDevicesSortsResultById(final VertxTestContext ctx) { pageSize, 1, List.of(), - List.of(), + List.of(), + Optional.empty(), NoopSpan.INSTANCE); }) .onComplete(ctx.succeeding(s -> { @@ -341,31 +346,24 @@ default void testSearchDevicesWithWildCardToMatchMultipleCharacters(final VertxT final Filter filter2 = new Filter("/ext/value", "test$1*e"); final Sort sortOption = new Sort("/id"); - createDevices( - tenantId, - Map.of( - "testDevice", new Device(), - "testDevice-1", new Device().setExtensions(Map.of("value", "test$1Value")), - "testDevice-2", new Device().setExtensions(Map.of("value", "test$2Value"))) - ) - .compose(ok -> getDeviceManagementService().searchDevices( - tenantId, - pageSize, - pageOffset, - List.of(filter1, filter2), - List.of(sortOption), - NoopSpan.INSTANCE)) - .onComplete(ctx.succeeding(s -> { - ctx.verify(() -> { - assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); - - final SearchResult searchResult = s.getPayload(); - assertThat(searchResult.getTotal()).isEqualTo(1); - assertThat(searchResult.getResult()).hasSize(1); - assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice-1"); - }); - ctx.completeNow(); - })); + createDevices(tenantId, Map.of( + "testDevice", new Device(), + "testDevice-1", new Device().setExtensions(Map.of("value", "test$1Value")), + "testDevice-2", new Device().setExtensions(Map.of("value", "test$2Value")))) + .compose(ok -> getDeviceManagementService() + .searchDevices(tenantId, pageSize, pageOffset, List.of(filter1, filter2), + List.of(sortOption), Optional.empty(), NoopSpan.INSTANCE) + .onComplete(ctx.succeeding(s -> { + ctx.verify(() -> { + assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); + + final SearchResult searchResult = s.getPayload(); + assertThat(searchResult.getTotal()).isEqualTo(1); + assertThat(searchResult.getResult()).hasSize(1); + assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice-1"); + }); + ctx.completeNow(); + }))); } /** @@ -383,32 +381,25 @@ default void testSearchDevicesWithCardToMatchSingleCharacter(final VertxTestCont final Filter filter2 = new Filter("/ext/value", "test$?Value"); final Sort sortOption = new Sort("/id"); - createDevices( - tenantId, - Map.of( - "testDevice-x", new Device().setExtensions(Map.of("value", "test$Value")), - "testDevice-1", new Device().setExtensions(Map.of("value", "test$1Value")), - "testDevice-2", new Device().setExtensions(Map.of("value", "test$2Value"))) - ) - .compose(ok -> getDeviceManagementService().searchDevices( - tenantId, - pageSize, - pageOffset, - List.of(filter1, filter2), - List.of(sortOption), - NoopSpan.INSTANCE)) - .onComplete(ctx.succeeding(s -> { - ctx.verify(() -> { - assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); - - final SearchResult searchResult = s.getPayload(); - assertThat(searchResult.getTotal()).isEqualTo(2); - assertThat(searchResult.getResult()).hasSize(2); - assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice-1"); - assertThat(searchResult.getResult().get(1).getId()).isEqualTo("testDevice-2"); - }); - ctx.completeNow(); - })); + createDevices(tenantId, Map.of( + "testDevice-x", new Device().setExtensions(Map.of("value", "test$Value")), + "testDevice-1", new Device().setExtensions(Map.of("value", "test$1Value")), + "testDevice-2", new Device().setExtensions(Map.of("value", "test$2Value")))) + .compose(ok -> getDeviceManagementService() + .searchDevices(tenantId, pageSize, pageOffset, List.of(filter1, filter2), + List.of(sortOption), Optional.empty(), NoopSpan.INSTANCE) + .onComplete(ctx.succeeding(s -> { + ctx.verify(() -> { + assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); + + final SearchResult searchResult = s.getPayload(); + assertThat(searchResult.getTotal()).isEqualTo(2); + assertThat(searchResult.getResult()).hasSize(2); + assertThat(searchResult.getResult().get(0).getId()).isEqualTo("testDevice-1"); + assertThat(searchResult.getResult().get(1).getId()).isEqualTo("testDevice-2"); + }); + ctx.completeNow(); + }))); } /** diff --git a/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpointTest.java b/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpointTest.java index c4570e8805..ea4361c2a7 100644 --- a/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpointTest.java +++ b/services/device-registry-base/src/test/java/org/eclipse/hono/service/management/device/DelegatingDeviceManagementHttpEndpointTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, 2021 Contributors to the Eclipse Foundation + * Copyright (c) 2020, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -91,6 +91,7 @@ public void setUp() { anyInt(), any(List.class), any(List.class), + any(Optional.class), any(Span.class))) .thenReturn(Future.succeededFuture(OperationResult.empty(HttpURLConnection.HTTP_OK))); final var endpoint = new DelegatingDeviceManagementHttpEndpoint<>(vertx, service); @@ -300,6 +301,7 @@ public void testSearchDevicesUsesDefaultSearchCriteria() { eq(DelegatingDeviceManagementHttpEndpoint.DEFAULT_PAGE_OFFSET), argThat(List::isEmpty), argThat(List::isEmpty), + any(Optional.class), any(Span.class)); } @@ -351,6 +353,7 @@ public void testSearchDevicesSucceedsWithSearchCriteria() { Direction.DESC == sortOption.getDirection(); } }), + any(Optional.class), any(Span.class)); } @@ -419,6 +422,7 @@ private void testSearchDevicesFailsForMalformedSearchCriteria(final MultiMap par anyInt(), any(List.class), any(List.class), + any(Optional.class), any(Span.class)); } diff --git a/services/device-registry-jdbc/src/main/java/org/eclipse/hono/deviceregistry/jdbc/impl/DeviceManagementServiceImpl.java b/services/device-registry-jdbc/src/main/java/org/eclipse/hono/deviceregistry/jdbc/impl/DeviceManagementServiceImpl.java index aa04d2b163..d8a569ffec 100644 --- a/services/device-registry-jdbc/src/main/java/org/eclipse/hono/deviceregistry/jdbc/impl/DeviceManagementServiceImpl.java +++ b/services/device-registry-jdbc/src/main/java/org/eclipse/hono/deviceregistry/jdbc/impl/DeviceManagementServiceImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2020, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -156,12 +156,13 @@ protected Future>> processSearchDevic final int pageOffset, final List filters, final List sortOptions, + final Optional isGateway, final Span span) { Objects.requireNonNull(tenantId); Objects.requireNonNull(span); - return store.findDevices(tenantId, pageSize, pageOffset, filters, span.context()) + return store.findDevices(tenantId, pageSize, pageOffset, filters, isGateway, span.context()) .map(result -> OperationResult.ok( HttpURLConnection.HTTP_OK, result, diff --git a/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/RegistryServiceTest.java b/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/BaseRegistryServiceTest.java similarity index 100% rename from services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/RegistryServiceTest.java rename to services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/BaseRegistryServiceTest.java diff --git a/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/JdbcBasedDeviceManagementSearchDevicesTest.java b/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/JdbcBasedDeviceManagementSearchDevicesTest.java index 1bdb12bb5c..e038a96ab7 100644 --- a/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/JdbcBasedDeviceManagementSearchDevicesTest.java +++ b/services/device-registry-jdbc/src/test/java/org/eclipse/hono/deviceregistry/jdbc/impl/JdbcBasedDeviceManagementSearchDevicesTest.java @@ -39,7 +39,7 @@ class JdbcBasedDeviceManagementSearchDevicesTest extends AbstractJdbcRegistryTes * Creates a set of devices. * * @param tenantId The tenant identifier. - * @param devices The devices to create. + * @param devices The devices to create. * @return A succeeded future if all devices have been created successfully. */ Future createDevices(final String tenantId, final Map devices) { @@ -74,6 +74,7 @@ void testSearchDevicesWhenNoDevicesAreFound(final VertxTestContext ctx) { 0, List.of(), List.of(), + Optional.empty(), NoopSpan.INSTANCE) .onComplete(ctx.failing(t -> { ctx.verify(() -> Assertions.assertServiceInvocationException(t, HttpURLConnection.HTTP_NOT_FOUND)); @@ -98,7 +99,7 @@ void testSearchDevicesWithPageSize(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(true), "testDevice3", new Device().setEnabled(true))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -128,7 +129,7 @@ void testSearchDevicesWithPageOffset(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(true), "testDevice3", new Device().setEnabled(true))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -153,7 +154,7 @@ void testSearchAllDevices(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(true), "testDevice3", new Device().setEnabled(true))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -168,7 +169,7 @@ void testSearchAllDevices(final VertxTestContext ctx) { } @Test - void TestSearchDevicesWithViaFilter(final VertxTestContext ctx) { + void testSearchDevicesWithViaFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 3; final int pageOffset = 0; @@ -183,7 +184,7 @@ void TestSearchDevicesWithViaFilter(final VertxTestContext ctx) { "testDevice_Gateway", new Device(), "testDevice_Gateway2", new Device().setEnabled(true))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -197,7 +198,7 @@ void TestSearchDevicesWithViaFilter(final VertxTestContext ctx) { } @Test - void TestSearchDevicesWithBooleanFilter(final VertxTestContext ctx) { + void testSearchDevicesWithBooleanFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 10; final int pageOffset = 0; @@ -211,7 +212,7 @@ void TestSearchDevicesWithBooleanFilter(final VertxTestContext ctx) { "testDevice3", new Device().setEnabled(true).setVia(List.of("testDevice2")), "testDevice4", new Device().setEnabled(true).setVia(List.of("testDevice1")))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -227,7 +228,7 @@ void TestSearchDevicesWithBooleanFilter(final VertxTestContext ctx) { @Test - void TestSearchDevicesWithIntegerFilter(final VertxTestContext ctx) { + void testSearchDevicesWithIntegerFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 10; final int pageOffset = 0; @@ -240,7 +241,7 @@ void TestSearchDevicesWithIntegerFilter(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(false).putExtension("count", 8), "testDevice3", new Device().setEnabled(true).putExtension("count", 12))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -255,7 +256,7 @@ void TestSearchDevicesWithIntegerFilter(final VertxTestContext ctx) { @Test - void TestSearchDevicesWithStringFilter(final VertxTestContext ctx) { + void testSearchDevicesWithStringFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 10; final int pageOffset = 0; @@ -268,7 +269,7 @@ void TestSearchDevicesWithStringFilter(final VertxTestContext ctx) { "testDevice2", new Device().setEnabled(false).putExtension("type", "type_2"), "testDevice3", new Device().setEnabled(true).putExtension("type", "type_1"))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -283,7 +284,7 @@ void TestSearchDevicesWithStringFilter(final VertxTestContext ctx) { @Test - void TestSearchDevicesWithStringAllCharsWildcardsFilter(final VertxTestContext ctx) { + void testSearchDevicesWithStringAllCharsWildcardsFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 10; final int pageOffset = 0; @@ -296,7 +297,7 @@ void TestSearchDevicesWithStringAllCharsWildcardsFilter(final VertxTestContext c "testDevice2", new Device().setEnabled(false).putExtension("type", "type2"), "testDevice3", new Device().setEnabled(true).putExtension("type", "type1"))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); @@ -311,7 +312,7 @@ void TestSearchDevicesWithStringAllCharsWildcardsFilter(final VertxTestContext c } @Test - void TestSearchDevicesWithStringOneCharWildcardsFilter(final VertxTestContext ctx) { + void testSearchDevicesWithStringOneCharWildcardsFilter(final VertxTestContext ctx) { final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); final int pageSize = 10; final int pageOffset = 0; @@ -324,7 +325,56 @@ void TestSearchDevicesWithStringOneCharWildcardsFilter(final VertxTestContext ct "testDevice2", new Device().setEnabled(false).putExtension("type", "type2"), "testDevice3", new Device().setEnabled(true).putExtension("type", "type11"))) .compose(ok -> getDeviceManagementService() - .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), NoopSpan.INSTANCE)) + .searchDevices(tenantId, pageSize, pageOffset, filters, List.of(), Optional.empty(), NoopSpan.INSTANCE)) + .onComplete(ctx.succeeding(s -> { + ctx.verify(() -> { + assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); + assertThat(s.getPayload().getTotal()).isEqualTo(2); + assertThat(s.getPayload().getResult()).hasSize(2); + assertThat(s.getPayload().getResult().get(0).getId()).isEqualTo("testDevice1"); + assertThat(s.getPayload().getResult().get(1).getId()).isEqualTo("testDevice3"); + }); + ctx.completeNow(); + })); + } + + + @Test + void testSearchAllGateways(final VertxTestContext ctx) { + final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); + final int pageSize = 10; + final int pageOffset = 0; + + createDevices(tenantId, Map.of( + "testDevice1", new Device().setVia(List.of("testDevice2")), + "testDevice2", new Device(), + "testDevice3", new Device().setVia(List.of("testDevice2")))) + .compose(ok -> getDeviceManagementService() + .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), Optional.of(true), NoopSpan.INSTANCE)) + .onComplete(ctx.succeeding(s -> { + ctx.verify(() -> { + assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); + assertThat(s.getPayload().getTotal()).isEqualTo(1); + assertThat(s.getPayload().getResult()).hasSize(1); + assertThat(s.getPayload().getResult().get(0).getId()).isEqualTo("testDevice2"); + }); + ctx.completeNow(); + })); + } + + + @Test + void testSearchOnlyDevices(final VertxTestContext ctx) { + final String tenantId = DeviceRegistryUtils.getUniqueIdentifier(); + final int pageSize = 10; + final int pageOffset = 0; + + createDevices(tenantId, Map.of( + "testDevice1", new Device().setVia(List.of("testDevice2")), + "testDevice2", new Device(), + "testDevice3", new Device().setVia(List.of("testDevice2")))) + .compose(ok -> getDeviceManagementService() + .searchDevices(tenantId, pageSize, pageOffset, List.of(), List.of(), Optional.of(false), NoopSpan.INSTANCE)) .onComplete(ctx.succeeding(s -> { ctx.verify(() -> { assertThat(s.getStatus()).isEqualTo(HttpURLConnection.HTTP_OK); diff --git a/services/device-registry-mongodb/src/main/java/org/eclipse/hono/deviceregistry/mongodb/service/MongoDbBasedDeviceManagementService.java b/services/device-registry-mongodb/src/main/java/org/eclipse/hono/deviceregistry/mongodb/service/MongoDbBasedDeviceManagementService.java index edb7d59cca..c2f074d47d 100644 --- a/services/device-registry-mongodb/src/main/java/org/eclipse/hono/deviceregistry/mongodb/service/MongoDbBasedDeviceManagementService.java +++ b/services/device-registry-mongodb/src/main/java/org/eclipse/hono/deviceregistry/mongodb/service/MongoDbBasedDeviceManagementService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -139,6 +139,7 @@ protected Future>> processSearchDevic final int pageOffset, final List filters, final List sortOptions, + final Optional isGateway, final Span span) { Objects.requireNonNull(tenantId); diff --git a/site/documentation/content/api/management/device-registry-v1.yaml b/site/documentation/content/api/management/device-registry-v1.yaml index e724739073..997643ad6d 100644 --- a/site/documentation/content/api/management/device-registry-v1.yaml +++ b/site/documentation/content/api/management/device-registry-v1.yaml @@ -370,6 +370,7 @@ paths: - $ref: '#/components/parameters/pageOffset' - $ref: '#/components/parameters/filterJson' - $ref: '#/components/parameters/sortJson' + - $ref: '#/components/parameters/isGateway' responses: 200: description: operation successful @@ -1688,8 +1689,8 @@ components: name: filterJson in: query description: | - A predicate that objects in the result set must match. If this parameter is specified multiple - times, objects in the result set must match all predicates. + A predicate that objects in the result set must match (will be ignored if parameter isGateway is being used). + If this parameter is specified multiple times, objects in the result set must match all predicates. The predicate is specified as a string representing a JSON object complying with the following schema ``` @@ -1772,6 +1773,21 @@ components: value: "{\"field\":\"/ext/brand\"}" sort descending: value: "{\"field\":\"/status/created\",\"direction\":\"desc\"}" + isGateway: + name: isGateway + in: query + description: | + Optional boolean filter parameter for searching only gateways or only devices. + If not used then result will contain both gateways and devices. + required: false + schema: + type: boolean + examples: + search only devices: + value: "isGateway=false" + search only gateways: + value: "isGateway=true" + responses: diff --git a/site/documentation/content/user-guide/device-registry.md b/site/documentation/content/user-guide/device-registry.md index 0d2369d6ff..cba11f8aba 100644 --- a/site/documentation/content/user-guide/device-registry.md +++ b/site/documentation/content/user-guide/device-registry.md @@ -60,7 +60,7 @@ The tenants in the registry can be managed using the Device Registry Management The JDBC based registry implementation does not support the following features: * Tenants can be retrieved using the [search tenants]({{< relref "/api/management#tenants/searchTenants" >}}) - operation defined by the Device Registry Management API, but the *sortJson* query parameter is + operation defined by the Device Registry Management API, but the *filterJson* and *sortJson* query parameters are (currently) being ignored. The result set will always be sorted by the tenant Id in ascending order. * The *alias* and *trust-anchor-group* properties defined on a tenant are being ignored by the registry. Consequently, multiple tenants can not be configured to use the same trust anchor(s). @@ -145,10 +145,15 @@ The JDBC based registry implementation does not support the following features: Registry Management API. The *filterJson* query parameter currently only allows one filter expression per request, the *sortJson* query parameter is (currently) being ignored. The result set will always be sorted by the device Id in ascending order. + +The Mongo DB based registry implementation does not support the following features: + +* Registration information can be retrieved using the + [search devices]({{< relref "/api/management#devices/searchDevicesForTenant" >}}) operation defined by the Device + Registry Management API. The *isGateway* query parameter is (currently) being ignored. {{% /notice %}} ### Managing Credentials The device's credentials can be managed using the Device Registry Management API's [credentials related resources]({{< relref "/api/management#credentials" >}}). -