From d921cffdaa34f5d1dbc06e1f93e539101713741f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 17 Dec 2021 12:50:00 +0000 Subject: [PATCH] Refactored ArbitraryResource to group by data type, to improve readability and code organization. --- .../api/resource/ArbitraryResource.java | 391 +++++++++--------- 1 file changed, 203 insertions(+), 188 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index a2131d71..568bca93 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -331,70 +331,90 @@ public class ArbitraryResource { } @GET - @Path("/{service}/{name}") + @Path("/hosted/transactions") @Operation( - summary = "Fetch raw data from file with supplied service, name, and relative path", - description = "An optional rebuild boolean can be supplied. If true, any existing cached data will be invalidated.", + summary = "List arbitrary transactions hosted by this node", responses = { @ApiResponse( - description = "Path to file structure containing requested data", - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string" - ) - ) + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryTransactionData.class)) ) } ) - @SecurityRequirement(name = "apiKey") - public HttpServletResponse get(@PathParam("service") Service service, - @PathParam("name") String name, - @QueryParam("filepath") String filepath, - @QueryParam("rebuild") boolean rebuild) { + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + public List getHostedTransactions() { Security.checkApiCallAllowed(request); - return this.download(service, name, null, filepath, rebuild); + try (final Repository repository = RepositoryManager.getRepository()) { + + List hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository); + + return hostedTransactions; + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } } @GET - @Path("/{service}/{name}/{identifier}") + @Path("/hosted/resources") @Operation( - summary = "Fetch raw data from file with supplied service, name, identifier, and relative path", - description = "An optional rebuild boolean can be supplied. If true, any existing cached data will be invalidated.", + summary = "List arbitrary resources hosted by this node", responses = { @ApiResponse( - description = "Path to file structure containing requested data", - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string" - ) - ) + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceInfo.class)) ) } ) - @SecurityRequirement(name = "apiKey") - public HttpServletResponse get(@PathParam("service") Service service, - @PathParam("name") String name, - @PathParam("identifier") String identifier, - @QueryParam("filepath") String filepath, - @QueryParam("rebuild") boolean rebuild) { + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + public List getHostedResources( + @Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus) { Security.checkApiCallAllowed(request); - return this.download(service, name, identifier, filepath, rebuild); + List resources = new ArrayList<>(); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository); + for (ArbitraryTransactionData transactionData : transactionDataList) { + ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData); + if (transaction.isDataLocal()) { + String name = transactionData.getName(); + Service service = transactionData.getService(); + String identifier = transactionData.getIdentifier(); + + if (transactionData.getName() != null) { + List transactionResources = repository.getArbitraryRepository() + .getArbitraryResources(service, identifier, name, (identifier == null), null, null, false); + if (transactionResources != null) { + resources.addAll(transactionResources); + } + } + } + } + + if (includeStatus != null && includeStatus == true) { + resources = this.addStatusToResources(resources); + } + + return resources; + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } } @POST - @Path("/{service}/{name}") + @Path("/compute") @Operation( - summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path", + summary = "Compute nonce for raw, unsigned ARBITRARY transaction", requestBody = @RequestBody( required = true, content = @Content( mediaType = MediaType.TEXT_PLAIN, schema = @Schema( - type = "string", example = "/Users/user/Documents/MyDirectoryOrFile" + type = "string", + description = "raw, unsigned ARBITRARY transaction in base58 encoding", + example = "raw transaction base58" ) ) ), @@ -410,33 +430,59 @@ public class ArbitraryResource { ) } ) + @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) @SecurityRequirement(name = "apiKey") - public String post(@PathParam("service") String serviceString, - @PathParam("name") String name, - String path) { + public String computeNonce(String rawBytes58) { Security.checkApiCallAllowed(request); - if (path == null || path.isEmpty()) { - throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Path not supplied"); - } + try (final Repository repository = RepositoryManager.getRepository()) { + byte[] rawBytes = Base58.decode(rawBytes58); + // We're expecting unsigned transaction, so append empty signature prior to decoding + rawBytes = Bytes.concat(rawBytes, new byte[TransactionTransformer.SIGNATURE_LENGTH]); - return this.upload(Service.valueOf(serviceString), name, null, path, null, null); + TransactionData transactionData = TransactionTransformer.fromBytes(rawBytes); + if (transactionData == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (transactionData.getType() != TransactionType.ARBITRARY) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + ArbitraryTransaction arbitraryTransaction = (ArbitraryTransaction) Transaction.fromData(repository, transactionData); + + // Quicker validity check first before we compute nonce + ValidationResult result = arbitraryTransaction.isValid(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + LOGGER.info("Computing nonce..."); + arbitraryTransaction.computeNonce(); + + // Re-check, but ignores signature + result = arbitraryTransaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + // Strip zeroed signature + transactionData.setSignature(null); + + byte[] bytes = ArbitraryTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } } - @POST - @Path("/{service}/{name}/base64") + + @GET + @Path("/{service}/{name}") @Operation( - summary = "Build raw, unsigned, ARBITRARY transaction, based on user-supplied base64 encoded data", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.APPLICATION_OCTET_STREAM, - schema = @Schema(type = "string", format = "byte") - ) - ), + summary = "Fetch raw data from file with supplied service, name, and relative path", + description = "An optional rebuild boolean can be supplied. If true, any existing cached data will be invalidated.", responses = { @ApiResponse( - description = "raw, unsigned, ARBITRARY transaction encoded in Base58", + description = "Path to file structure containing requested data", content = @Content( mediaType = MediaType.TEXT_PLAIN, schema = @Schema( @@ -447,28 +493,57 @@ public class ArbitraryResource { } ) @SecurityRequirement(name = "apiKey") - public String postBase64EncodedData(@PathParam("service") String serviceString, - @PathParam("name") String name, - String base64) { + public HttpServletResponse get(@PathParam("service") Service service, + @PathParam("name") String name, + @QueryParam("filepath") String filepath, + @QueryParam("rebuild") boolean rebuild) { Security.checkApiCallAllowed(request); - if (base64 == null) { - throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data not supplied"); - } + return this.download(service, name, null, filepath, rebuild); + } - return this.upload(Service.valueOf(serviceString), name, null, null, null, base64); + @GET + @Path("/{service}/{name}/{identifier}") + @Operation( + summary = "Fetch raw data from file with supplied service, name, identifier, and relative path", + description = "An optional rebuild boolean can be supplied. If true, any existing cached data will be invalidated.", + responses = { + @ApiResponse( + description = "Path to file structure containing requested data", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @SecurityRequirement(name = "apiKey") + public HttpServletResponse get(@PathParam("service") Service service, + @PathParam("name") String name, + @PathParam("identifier") String identifier, + @QueryParam("filepath") String filepath, + @QueryParam("rebuild") boolean rebuild) { + Security.checkApiCallAllowed(request); + + return this.download(service, name, identifier, filepath, rebuild); } + + + // Upload data at supplied path + @POST - @Path("/{service}/{name}/string") + @Path("/{service}/{name}") @Operation( - summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied string", + summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path", requestBody = @RequestBody( required = true, content = @Content( mediaType = MediaType.TEXT_PLAIN, schema = @Schema( - type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}" + type = "string", example = "/Users/user/Documents/MyDirectoryOrFile" ) ) ), @@ -485,19 +560,18 @@ public class ArbitraryResource { } ) @SecurityRequirement(name = "apiKey") - public String postString(@PathParam("service") String serviceString, - @PathParam("name") String name, - String string) { + public String post(@PathParam("service") String serviceString, + @PathParam("name") String name, + String path) { Security.checkApiCallAllowed(request); - if (string == null || string.isEmpty()) { - throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data string not supplied"); + if (path == null || path.isEmpty()) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Path not supplied"); } - return this.upload(Service.valueOf(serviceString), name, null, null, string, null); + return this.upload(Service.valueOf(serviceString), name, null, path, null, null); } - @POST @Path("/{service}/{name}/{identifier}") @Operation( @@ -537,17 +611,19 @@ public class ArbitraryResource { return this.upload(Service.valueOf(serviceString), name, identifier, path, null, null); } + + + // Upload base64-encoded data + @POST - @Path("/{service}/{name}/{identifier}/string") + @Path("/{service}/{name}/base64") @Operation( - summary = "Build raw, unsigned, ARBITRARY transaction, based on user supplied string", + summary = "Build raw, unsigned, ARBITRARY transaction, based on user-supplied base64 encoded data", requestBody = @RequestBody( required = true, content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}" - ) + mediaType = MediaType.APPLICATION_OCTET_STREAM, + schema = @Schema(type = "string", format = "byte") ) ), responses = { @@ -563,17 +639,16 @@ public class ArbitraryResource { } ) @SecurityRequirement(name = "apiKey") - public String postString(@PathParam("service") String serviceString, - @PathParam("name") String name, - @PathParam("identifier") String identifier, - String string) { + public String postBase64EncodedData(@PathParam("service") String serviceString, + @PathParam("name") String name, + String base64) { Security.checkApiCallAllowed(request); - if (string == null || string.isEmpty()) { - throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data string not supplied"); + if (base64 == null) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data not supplied"); } - return this.upload(Service.valueOf(serviceString), name, identifier, null, string, null); + return this.upload(Service.valueOf(serviceString), name, null, null, null, base64); } @POST @@ -601,9 +676,9 @@ public class ArbitraryResource { ) @SecurityRequirement(name = "apiKey") public String postBase64EncodedData(@PathParam("service") String serviceString, - @PathParam("name") String name, - @PathParam("identifier") String identifier, - String base64) { + @PathParam("name") String name, + @PathParam("identifier") String identifier, + String base64) { Security.checkApiCallAllowed(request); if (base64 == null) { @@ -613,91 +688,58 @@ public class ArbitraryResource { return this.upload(Service.valueOf(serviceString), name, identifier, null, null, base64); } - @GET - @Path("/hosted/transactions") - @Operation( - summary = "List arbitrary transactions hosted by this node", - responses = { - @ApiResponse( - content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryTransactionData.class)) - ) - } - ) - @ApiErrors({ApiError.REPOSITORY_ISSUE}) - public List getHostedTransactions() { - Security.checkApiCallAllowed(request); - - try (final Repository repository = RepositoryManager.getRepository()) { - - List hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository); - return hostedTransactions; - } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); - } - } + // Upload plain-text data in string form - @GET - @Path("/hosted/resources") + @POST + @Path("/{service}/{name}/string") @Operation( - summary = "List arbitrary resources hosted by this node", + summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied string", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}" + ) + ) + ), responses = { @ApiResponse( - content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceInfo.class)) + description = "raw, unsigned, ARBITRARY transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) ) } ) - @ApiErrors({ApiError.REPOSITORY_ISSUE}) - public List getHostedResources( - @Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus) { + @SecurityRequirement(name = "apiKey") + public String postString(@PathParam("service") String serviceString, + @PathParam("name") String name, + String string) { Security.checkApiCallAllowed(request); - List resources = new ArrayList<>(); - - try (final Repository repository = RepositoryManager.getRepository()) { - - List transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository); - for (ArbitraryTransactionData transactionData : transactionDataList) { - ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData); - if (transaction.isDataLocal()) { - String name = transactionData.getName(); - Service service = transactionData.getService(); - String identifier = transactionData.getIdentifier(); - - if (transactionData.getName() != null) { - List transactionResources = repository.getArbitraryRepository() - .getArbitraryResources(service, identifier, name, (identifier == null), null, null, false); - if (transactionResources != null) { - resources.addAll(transactionResources); - } - } - } - } - - if (includeStatus != null && includeStatus == true) { - resources = this.addStatusToResources(resources); - } - - return resources; - - } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + if (string == null || string.isEmpty()) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data string not supplied"); } + + return this.upload(Service.valueOf(serviceString), name, null, null, string, null); } @POST - @Path("/compute") + @Path("/{service}/{name}/{identifier}/string") @Operation( - summary = "Compute nonce for raw, unsigned ARBITRARY transaction", + summary = "Build raw, unsigned, ARBITRARY transaction, based on user supplied string", requestBody = @RequestBody( required = true, content = @Content( mediaType = MediaType.TEXT_PLAIN, schema = @Schema( - type = "string", - description = "raw, unsigned ARBITRARY transaction in base58 encoding", - example = "raw transaction base58" + type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}" ) ) ), @@ -713,49 +755,22 @@ public class ArbitraryResource { ) } ) - @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) @SecurityRequirement(name = "apiKey") - public String computeNonce(String rawBytes58) { + public String postString(@PathParam("service") String serviceString, + @PathParam("name") String name, + @PathParam("identifier") String identifier, + String string) { Security.checkApiCallAllowed(request); - try (final Repository repository = RepositoryManager.getRepository()) { - byte[] rawBytes = Base58.decode(rawBytes58); - // We're expecting unsigned transaction, so append empty signature prior to decoding - rawBytes = Bytes.concat(rawBytes, new byte[TransactionTransformer.SIGNATURE_LENGTH]); - - TransactionData transactionData = TransactionTransformer.fromBytes(rawBytes); - if (transactionData == null) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); - - if (transactionData.getType() != TransactionType.ARBITRARY) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); - - ArbitraryTransaction arbitraryTransaction = (ArbitraryTransaction) Transaction.fromData(repository, transactionData); - - // Quicker validity check first before we compute nonce - ValidationResult result = arbitraryTransaction.isValid(); - if (result != ValidationResult.OK) - throw TransactionsResource.createTransactionInvalidException(request, result); - - LOGGER.info("Computing nonce..."); - arbitraryTransaction.computeNonce(); + if (string == null || string.isEmpty()) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Data string not supplied"); + } - // Re-check, but ignores signature - result = arbitraryTransaction.isValidUnconfirmed(); - if (result != ValidationResult.OK) - throw TransactionsResource.createTransactionInvalidException(request, result); + return this.upload(Service.valueOf(serviceString), name, identifier, null, string, null); + } - // Strip zeroed signature - transactionData.setSignature(null); - byte[] bytes = ArbitraryTransactionTransformer.toBytes(transactionData); - return Base58.encode(bytes); - } catch (TransformationException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); - } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); - } - } + // Shared methods private String upload(Service service, String name, String identifier, String path, String string, String base64) { // Fetch public key from registered name