From e90ecd208567ae8c978725fdb0cd2e211dbf1263 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 27 Nov 2021 14:21:36 +0000 Subject: [PATCH] Adapted GET /arbitrary/resources endpoint to allow filtering by identifier - If an identifier parameter is missing or empty, it will return an unfiltered list of all possible identifiers. - If an identifier is specified, only resources with a matching identifier will be returned. - If default is set to true, only resources without identifiers will be returned. --- .../qortal/api/resource/AdminResource.java | 1 - .../api/resource/ArbitraryResource.java | 35 +++++++++++++------ .../repository/ArbitraryRepository.java | 2 +- .../hsqldb/HSQLDBArbitraryRepository.java | 25 +++++++++---- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/AdminResource.java b/src/main/java/org/qortal/api/resource/AdminResource.java index 7f2e4d57..ec643d3c 100644 --- a/src/main/java/org/qortal/api/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/resource/AdminResource.java @@ -84,7 +84,6 @@ public class AdminResource { @Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "20")) @Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results, 0 is first entry", schema = @Schema(type = "integer")) @Parameter(in = ParameterIn.QUERY, name = "reverse", description = "Reverse results", schema = @Schema(type = "boolean")) - @Parameter(in = ParameterIn.QUERY, name = "includestatus", description = "Include status", schema = @Schema(type = "boolean")) public String globalParameters() { return ""; } diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 44b8c62e..3386cdbe 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -67,7 +67,10 @@ public class ArbitraryResource { @GET @Path("/resources") @Operation( - summary = "List arbitrary resources available on chain, optionally filtered by service", + summary = "List arbitrary resources available on chain, optionally filtered by service and identifier", + description = "- If an identifier parameter is missing or empty, it will return an unfiltered list of all possible identifiers.\n" + + "- If an identifier is specified, only resources with a matching identifier will be returned.\n" + + "- If default is set to true, only resources without identifiers will be returned.", responses = { @ApiResponse( content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceInfo.class)) @@ -76,19 +79,29 @@ public class ArbitraryResource { ) @ApiErrors({ApiError.REPOSITORY_ISSUE}) public List getResources( - @QueryParam("service") Service service, @Parameter( - ref = "limit" - ) @QueryParam("limit") Integer limit, @Parameter( - ref = "offset" - ) @QueryParam("offset") Integer offset, @Parameter( - ref = "reverse" - ) @QueryParam("reverse") Boolean reverse, @Parameter( - ref = "includestatus" - ) @QueryParam("includestatus") Boolean includeStatus) { + @QueryParam("service") Service service, + @QueryParam("identifier") String identifier, + @Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource, + @Parameter(ref = "limit") @QueryParam("limit") Integer limit, + @Parameter(ref = "offset") @QueryParam("offset") Integer offset, + @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse, + @Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus) { + try (final Repository repository = RepositoryManager.getRepository()) { + // Treat empty identifier as null + if (identifier != null && identifier.isEmpty()) { + identifier = null; + } + + // Ensure that "default" and "identifier" parameters cannot coexist + boolean defaultRes = Boolean.TRUE.equals(defaultResource); + if (defaultRes == true && identifier != null) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "identifier cannot be specified when requesting a default resource"); + } + List resources = repository.getArbitraryRepository() - .getArbitraryResources(service, limit, offset, reverse); + .getArbitraryResources(service, identifier, defaultRes, limit, offset, reverse); if (resources == null) { return new ArrayList<>(); diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index de6bccc5..16a18b8b 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -23,7 +23,7 @@ public interface ArbitraryRepository { public ArbitraryTransactionData getLatestTransaction(String name, Service service, Method method, String identifier) throws DataException; - public List getArbitraryResources(Service service, Integer limit, Integer offset, Boolean reverse) throws DataException; + public List getArbitraryResources(Service service, String identifier, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; public List getArbitraryPeerDataForSignature(byte[] signature) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index 7e82b800..a604dd0e 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -295,16 +295,29 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } @Override - public List getArbitraryResources(Service service, Integer limit, Integer offset, Boolean reverse) throws DataException { + public List getArbitraryResources(Service service, String identifier, + boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); - sql.append("SELECT name, service, identifier FROM ArbitraryTransactions"); + sql.append("SELECT name, service, identifier FROM ArbitraryTransactions WHERE 1=1"); if (service != null) { - sql.append(" WHERE service = "); + sql.append(" AND service = "); sql.append(service.value); } + if (defaultResource) { + // Default resource requested - use NULL identifier + // The AND ? IS NULL AND ? IS NULL is a hack to make use of the identifier params in checkedExecute() + identifier = null; + sql.append(" AND (identifier IS NULL AND ? IS NULL AND ? IS NULL)"); + } + else { + // Non-default resource requested + // Use an exact match identifier, or list all if supplied identifier is null + sql.append(" AND (identifier = ? OR (? IS NULL))"); + } + sql.append(" GROUP BY name, service, identifier ORDER BY name"); if (reverse != null && reverse) { @@ -315,14 +328,14 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { List arbitraryResources = new ArrayList<>(); - try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) { + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), identifier, identifier)) { if (resultSet == null) return null; do { String name = resultSet.getString(1); Service serviceResult = Service.valueOf(resultSet.getInt(2)); - String identifier = resultSet.getString(3); + String identifierResult = resultSet.getString(3); // We should filter out resources without names if (name == null) { @@ -332,7 +345,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { ArbitraryResourceInfo arbitraryResourceInfo = new ArbitraryResourceInfo(); arbitraryResourceInfo.name = name; arbitraryResourceInfo.service = serviceResult; - arbitraryResourceInfo.identifier = identifier; + arbitraryResourceInfo.identifier = identifierResult; arbitraryResources.add(arbitraryResourceInfo); } while (resultSet.next());