From 2850bd0b46c14f39a5cca2a5244ac869d8d8958f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Thu, 2 Dec 2021 19:41:07 +0000 Subject: [PATCH] Added new GET /arbitrary/resources/names endpoint to fetch resources grouped by name. --- .../api/resource/ArbitraryResource.java | 87 ++++++++++++++++--- .../arbitrary/ArbitraryResourceNameInfo.java | 17 ++++ .../repository/ArbitraryRepository.java | 5 +- .../hsqldb/HSQLDBArbitraryRepository.java | 81 +++++++++++++++-- 4 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/qortal/data/arbitrary/ArbitraryResourceNameInfo.java diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index ed86470d..70a97656 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -40,6 +40,7 @@ import org.qortal.arbitrary.misc.Service; import org.qortal.controller.Controller; import org.qortal.data.account.AccountData; import org.qortal.data.arbitrary.ArbitraryResourceInfo; +import org.qortal.data.arbitrary.ArbitraryResourceNameInfo; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.TransactionData; @@ -101,28 +102,73 @@ public class ArbitraryResource { } List resources = repository.getArbitraryRepository() - .getArbitraryResources(service, identifier, defaultRes, limit, offset, reverse); + .getArbitraryResources(service, identifier, null, defaultRes, limit, offset, reverse); if (resources == null) { return new ArrayList<>(); } - if (includeStatus == null || includeStatus == false) { - return resources; + if (includeStatus != null && includeStatus == true) { + resources = this.addStatusToResources(resources); } - // Determine and add the status of each resource - List updatedResources = new ArrayList<>(); - for (ArbitraryResourceInfo resourceInfo : resources) { - ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME, - resourceInfo.service, resourceInfo.identifier); - ArbitraryResourceSummary summary = resource.getSummary(); - if (summary != null) { - resourceInfo.status = summary.status; + return resources; + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @GET + @Path("/resources/names") + @Operation( + summary = "List arbitrary resources available on chain, grouped by creator's name", + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceInfo.class)) + ) + } + ) + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + public List getResourcesGroupedByName( + @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 creatorNames = repository.getArbitraryRepository() + .getArbitraryResourceCreatorNames(service, identifier, defaultRes, limit, offset, reverse); + + for (ArbitraryResourceNameInfo creatorName : creatorNames) { + String name = creatorName.name; + if (name != null) { + List resources = repository.getArbitraryRepository() + .getArbitraryResources(service, identifier, name, defaultRes, null, null, reverse); + + if (includeStatus != null && includeStatus == true) { + resources = this.addStatusToResources(resources); + } + creatorName.resources = resources; } - updatedResources.add(resourceInfo); } - return updatedResources; + + return creatorNames; } catch (DataException e) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); @@ -665,4 +711,19 @@ public class ArbitraryResource { ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier); return resource.getSummary(); } + + private List addStatusToResources(List resources) { + // Determine and add the status of each resource + List updatedResources = new ArrayList<>(); + for (ArbitraryResourceInfo resourceInfo : resources) { + ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME, + resourceInfo.service, resourceInfo.identifier); + ArbitraryResourceSummary summary = resource.getSummary(); + if (summary != null) { + resourceInfo.status = summary.status; + } + updatedResources.add(resourceInfo); + } + return updatedResources; + } } diff --git a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceNameInfo.java b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceNameInfo.java new file mode 100644 index 00000000..b9be8034 --- /dev/null +++ b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceNameInfo.java @@ -0,0 +1,17 @@ +package org.qortal.data.arbitrary; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.util.ArrayList; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class ArbitraryResourceNameInfo { + + public String name; + public List resources = new ArrayList<>(); + + public ArbitraryResourceNameInfo() { + } + +} diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 16a18b8b..fdfb0c6e 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -2,6 +2,7 @@ package org.qortal.repository; import org.qortal.arbitrary.misc.Service; import org.qortal.data.arbitrary.ArbitraryResourceInfo; +import org.qortal.data.arbitrary.ArbitraryResourceNameInfo; import org.qortal.data.network.ArbitraryPeerData; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData.*; @@ -23,7 +24,9 @@ public interface ArbitraryRepository { public ArbitraryTransactionData getLatestTransaction(String name, Service service, Method method, String identifier) throws DataException; - public List getArbitraryResources(Service service, String identifier, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; + public List getArbitraryResources(Service service, String identifier, String name, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; + + public List getArbitraryResourceCreatorNames(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 cdae22bc..eb3fd6a4 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -3,6 +3,7 @@ package org.qortal.repository.hsqldb; import org.qortal.arbitrary.misc.Service; import org.qortal.data.arbitrary.ArbitraryResourceInfo; import org.qortal.crypto.Crypto; +import org.qortal.data.arbitrary.ArbitraryResourceNameInfo; import org.qortal.data.network.ArbitraryPeerData; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData.*; @@ -279,9 +280,10 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } @Override - public List getArbitraryResources(Service service, String identifier, - boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException { + public List getArbitraryResources(Service service, String identifier, String name, + boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); + List bindParams = new ArrayList<>(); sql.append("SELECT name, service, identifier FROM ArbitraryTransactions WHERE 1=1"); @@ -292,14 +294,19 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { 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)"); + sql.append(" AND identifier 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))"); + bindParams.add(identifier); + bindParams.add(identifier); + } + + if (name != null) { + sql.append(" AND name = ?"); + bindParams.add(name); } sql.append(" GROUP BY name, service, identifier ORDER BY name"); @@ -312,12 +319,12 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { List arbitraryResources = new ArrayList<>(); - try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), identifier, identifier)) { + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) { if (resultSet == null) return null; do { - String name = resultSet.getString(1); + String nameResult = resultSet.getString(1); Service serviceResult = Service.valueOf(resultSet.getInt(2)); String identifierResult = resultSet.getString(3); @@ -327,7 +334,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } ArbitraryResourceInfo arbitraryResourceInfo = new ArbitraryResourceInfo(); - arbitraryResourceInfo.name = name; + arbitraryResourceInfo.name = nameResult; arbitraryResourceInfo.service = serviceResult; arbitraryResourceInfo.identifier = identifierResult; @@ -340,6 +347,64 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } } + @Override + public List getArbitraryResourceCreatorNames(Service service, String identifier, + boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException { + StringBuilder sql = new StringBuilder(512); + + sql.append("SELECT name FROM ArbitraryTransactions WHERE 1=1"); + + if (service != null) { + 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 ORDER BY name"); + + if (reverse != null && reverse) { + sql.append(" DESC"); + } + + HSQLDBRepository.limitOffsetSql(sql, limit, offset); + + List arbitraryResources = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), identifier, identifier)) { + if (resultSet == null) + return null; + + do { + String name = resultSet.getString(1); + + // We should filter out resources without names + if (name == null) { + continue; + } + + ArbitraryResourceNameInfo arbitraryResourceNameInfo = new ArbitraryResourceNameInfo(); + arbitraryResourceNameInfo.name = name; + + arbitraryResources.add(arbitraryResourceNameInfo); + } while (resultSet.next()); + + return arbitraryResources; + } catch (SQLException e) { + throw new DataException("Unable to fetch arbitrary transactions from repository", e); + } + } + // Peer file tracking