mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-12 10:15:49 +00:00
Removed all code duplication for Q-Apps API endpoints.
Requests are now internally routed to the existing API handlers. This should allow new Q-Apps API endpoints to be added much more quickly, as well as removing the need to maintain their code separately from the regular API endpoints.
This commit is contained in:
parent
2c78f4b45b
commit
3c8088e463
@ -15,7 +15,21 @@ public abstract class Security {
|
||||
|
||||
public static final String API_KEY_HEADER = "X-API-KEY";
|
||||
|
||||
/**
|
||||
* Check API call is allowed, retrieving the API key from the request header or GET/POST parameters where required
|
||||
* @param request
|
||||
*/
|
||||
public static void checkApiCallAllowed(HttpServletRequest request) {
|
||||
checkApiCallAllowed(request, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check API call is allowed, retrieving the API key first from the passedApiKey parameter, with a fallback
|
||||
* to the request header or GET/POST parameters when null.
|
||||
* @param request
|
||||
* @param passedApiKey - the API key to test, or null if it should be retrieved from the request headers.
|
||||
*/
|
||||
public static void checkApiCallAllowed(HttpServletRequest request, String passedApiKey) {
|
||||
// We may want to allow automatic authentication for local requests, if enabled in settings
|
||||
boolean localAuthBypassEnabled = Settings.getInstance().isLocalAuthBypassEnabled();
|
||||
if (localAuthBypassEnabled) {
|
||||
@ -38,7 +52,10 @@ public abstract class Security {
|
||||
}
|
||||
|
||||
// We require an API key to be passed
|
||||
String passedApiKey = request.getHeader(API_KEY_HEADER);
|
||||
if (passedApiKey == null) {
|
||||
// API call not passed as a parameter, so try the header
|
||||
passedApiKey = request.getHeader(API_KEY_HEADER);
|
||||
}
|
||||
if (passedApiKey == null) {
|
||||
// Try query string - this is needed to avoid a CORS preflight. See: https://stackoverflow.com/a/43881141
|
||||
passedApiKey = request.getParameter("apiKey");
|
||||
@ -84,9 +101,9 @@ public abstract class Security {
|
||||
}
|
||||
}
|
||||
|
||||
public static void requirePriorAuthorizationOrApiKey(HttpServletRequest request, String resourceId, Service service, String identifier) {
|
||||
public static void requirePriorAuthorizationOrApiKey(HttpServletRequest request, String resourceId, Service service, String identifier, String apiKey) {
|
||||
try {
|
||||
Security.checkApiCallAllowed(request);
|
||||
Security.checkApiCallAllowed(request, apiKey);
|
||||
|
||||
} catch (ApiException e) {
|
||||
// API call wasn't allowed, but maybe it was pre-authorized
|
||||
|
@ -8,9 +8,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.arbitrary.apps.QApp;
|
||||
import org.qortal.api.*;
|
||||
import org.qortal.api.model.NameSummary;
|
||||
import org.qortal.api.resource.*;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceInfo;
|
||||
@ -19,7 +19,7 @@ import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.chat.ChatMessage;
|
||||
import org.qortal.data.group.GroupData;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -28,6 +28,8 @@ import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
@ -83,127 +85,128 @@ public class AppsResource {
|
||||
@Path("/account")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public AccountData getAccount(@QueryParam("address") String address) {
|
||||
try {
|
||||
return QApp.getAccountData(address);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
AddressesResource addressesResource = (AddressesResource) buildResource(AddressesResource.class, request, response, context);
|
||||
return addressesResource.getAccountInfo(address);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/account/names")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public List<NameData> getAccountNames(@QueryParam("address") String address) {
|
||||
try {
|
||||
return QApp.getAccountNames(address);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
public List<NameSummary> getAccountNames(@QueryParam("address") String address) {
|
||||
NamesResource namesResource = (NamesResource) buildResource(NamesResource.class, request, response, context);
|
||||
return namesResource.getNamesByAddress(address, 0, 0 ,false);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/name")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public NameData getName(@QueryParam("name") String name) {
|
||||
try {
|
||||
return QApp.getNameData(name);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
NamesResource namesResource = (NamesResource) buildResource(NamesResource.class, request, response, context);
|
||||
return namesResource.getName(name);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/chatmessages")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public List<ChatMessage> searchChatMessages(@QueryParam("before") Long before, @QueryParam("after") Long after, @QueryParam("txGroupId") Integer txGroupId, @QueryParam("involving") List<String> involvingAddresses, @QueryParam("reference") String reference, @QueryParam("chatReference") String chatReference, @QueryParam("hasChatReference") Boolean hasChatReference, @QueryParam("limit") Integer limit, @QueryParam("offset") Integer offset, @QueryParam("reverse") Boolean reverse) {
|
||||
try {
|
||||
return QApp.searchChatMessages(before, after, txGroupId, involvingAddresses, reference, chatReference, hasChatReference, limit, offset, reverse);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
ChatResource chatResource = (ChatResource) buildResource(ChatResource.class, request, response, context);
|
||||
return chatResource.searchChat(before, after, txGroupId, involvingAddresses, reference, chatReference, hasChatReference, limit, offset, reverse);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resources")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public List<ArbitraryResourceInfo> getResources(@QueryParam("service") Service service, @QueryParam("identifier") String identifier, @Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource, @Parameter(description = "Filter names by list") @QueryParam("nameListFilter") String nameListFilter, @Parameter(description = "Include status") @QueryParam("includeStatus") Boolean includeStatus, @Parameter(description = "Include metadata") @QueryParam("includeMetadata") Boolean includeMetadata, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
|
||||
try {
|
||||
return QApp.searchQdnResources(service, identifier, defaultResource, nameListFilter, includeStatus, includeMetadata, limit, offset, reverse);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
ArbitraryResource arbitraryResource = (ArbitraryResource) buildResource(ArbitraryResource.class, request, response, context);
|
||||
return arbitraryResource.getResources(service, identifier, defaultResource, limit, offset, reverse, nameListFilter, includeStatus, includeMetadata);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resourcestatus")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public ArbitraryResourceStatus getResourceStatus(@QueryParam("service") Service service, @QueryParam("name") String name, @QueryParam("identifier") String identifier) {
|
||||
return QApp.getQdnResourceStatus(service, name, identifier);
|
||||
ArbitraryResource arbitraryResource = (ArbitraryResource) buildResource(ArbitraryResource.class, request, response, context);
|
||||
ApiKey apiKey = ApiService.getInstance().getApiKey();
|
||||
return arbitraryResource.getResourceStatus(apiKey.toString(), service, name, identifier, false);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resource")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public String getResource(@QueryParam("service") Service service, @QueryParam("name") String name, @QueryParam("identifier") String identifier, @QueryParam("filepath") String filepath, @QueryParam("rebuild") boolean rebuild) {
|
||||
try {
|
||||
return QApp.fetchQdnResource64(service, name, identifier, filepath, rebuild);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
public HttpServletResponse getResource(@QueryParam("service") Service service, @QueryParam("name") String name, @QueryParam("identifier") String identifier, @QueryParam("filepath") String filepath, @QueryParam("rebuild") boolean rebuild) {
|
||||
ArbitraryResource arbitraryResource = (ArbitraryResource) buildResource(ArbitraryResource.class, request, response, context);
|
||||
ApiKey apiKey = ApiService.getInstance().getApiKey();
|
||||
return arbitraryResource.get(apiKey.toString(), service, name, identifier, filepath, rebuild, false, 5);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/groups")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public List<GroupData> listGroups(@Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
|
||||
try {
|
||||
return QApp.listGroups(limit, offset, reverse);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
GroupsResource groupsResource = (GroupsResource) buildResource(GroupsResource.class, request, response, context);
|
||||
return groupsResource.getAllGroups(limit, offset, reverse);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/balance")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public Long getBalance(@QueryParam("assetId") long assetId, @QueryParam("address") String address) {
|
||||
try {
|
||||
return QApp.getBalance(assetId, address);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
public BigDecimal getBalance(@QueryParam("assetId") long assetId, @QueryParam("address") String address) {
|
||||
AddressesResource addressesResource = (AddressesResource) buildResource(AddressesResource.class, request, response, context);
|
||||
return addressesResource.getBalance(address, assetId);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/at")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public ATData getAT(@QueryParam("atAddress") String atAddress) {
|
||||
try {
|
||||
return QApp.getAtInfo(atAddress);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
AtResource atResource = (AtResource) buildResource(AtResource.class, request, response, context);
|
||||
return atResource.getByAddress(atAddress);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/atdata")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public String getATData(@QueryParam("atAddress") String atAddress) {
|
||||
try {
|
||||
return QApp.getAtData58(atAddress);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
AtResource atResource = (AtResource) buildResource(AtResource.class, request, response, context);
|
||||
return Base58.encode(atResource.getDataByAddress(atAddress));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/ats")
|
||||
@Hidden // For internal Q-App API use only
|
||||
public List<ATData> listATs(@QueryParam("codeHash58") String codeHash58, @QueryParam("isExecutable") Boolean isExecutable, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
|
||||
AtResource atResource = (AtResource) buildResource(AtResource.class, request, response, context);
|
||||
return atResource.getByFunctionality(codeHash58, isExecutable, limit, offset, reverse);
|
||||
}
|
||||
|
||||
|
||||
public static Object buildResource(Class<?> resourceClass, HttpServletRequest request, HttpServletResponse response, ServletContext context) {
|
||||
try {
|
||||
return QApp.listATs(codeHash58, isExecutable, limit, offset, reverse);
|
||||
} catch (DataException | IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
Object resource = resourceClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
Field requestField = resourceClass.getDeclaredField("request");
|
||||
requestField.setAccessible(true);
|
||||
requestField.set(resource, request);
|
||||
|
||||
try {
|
||||
Field responseField = resourceClass.getDeclaredField("response");
|
||||
responseField.setAccessible(true);
|
||||
responseField.set(resource, response);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
Field contextField = resourceClass.getDeclaredField("context");
|
||||
contextField.setAccessible(true);
|
||||
contextField.set(resource, context);
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return resource;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to build API resource " + resourceClass.getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ public class ArbitraryResource {
|
||||
@PathParam("name") String name,
|
||||
@QueryParam("build") Boolean build) {
|
||||
|
||||
Security.requirePriorAuthorizationOrApiKey(request, name, service, null);
|
||||
Security.requirePriorAuthorizationOrApiKey(request, name, service, null, apiKey);
|
||||
return ArbitraryTransactionUtils.getStatus(service, name, null, build);
|
||||
}
|
||||
|
||||
@ -288,7 +288,7 @@ public class ArbitraryResource {
|
||||
@PathParam("identifier") String identifier,
|
||||
@QueryParam("build") Boolean build) {
|
||||
|
||||
Security.requirePriorAuthorizationOrApiKey(request, name, service, identifier);
|
||||
Security.requirePriorAuthorizationOrApiKey(request, name, service, identifier, apiKey);
|
||||
return ArbitraryTransactionUtils.getStatus(service, name, identifier, build);
|
||||
}
|
||||
|
||||
@ -682,7 +682,7 @@ public class ArbitraryResource {
|
||||
|
||||
// Authentication can be bypassed in the settings, for those running public QDN nodes
|
||||
if (!Settings.getInstance().isQDNAuthBypassEnabled()) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
Security.checkApiCallAllowed(request, apiKey);
|
||||
}
|
||||
|
||||
return this.download(service, name, identifier, filepath, rebuild, async, attempts);
|
||||
|
@ -1,276 +0,0 @@
|
||||
package org.qortal.arbitrary.apps;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.arbitrary.ArbitraryDataFile;
|
||||
import org.qortal.arbitrary.ArbitraryDataReader;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.LiteNode;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceInfo;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.chat.ChatMessage;
|
||||
import org.qortal.data.group.GroupData;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.list.ResourceListManager;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.ArbitraryTransactionUtils;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class QApp {
|
||||
|
||||
public static AccountData getAccountData(String address) throws DataException {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getAccountRepository().getAccount(address);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<NameData> getAccountNames(String address) throws DataException {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getNameRepository().getNamesByOwner(address);
|
||||
}
|
||||
}
|
||||
|
||||
public static NameData getNameData(String name) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
return LiteNode.getInstance().fetchNameData(name);
|
||||
} else {
|
||||
return repository.getNameRepository().fromName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ChatMessage> searchChatMessages(Long before, Long after, Integer txGroupId, List<String> involvingAddresses,
|
||||
String reference, String chatReference, Boolean hasChatReference,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
// Check args meet expectations
|
||||
if ((txGroupId == null && involvingAddresses.size() != 2)
|
||||
|| (txGroupId != null && !involvingAddresses.isEmpty()))
|
||||
throw new IllegalArgumentException("Invalid txGroupId or involvingAddresses");
|
||||
|
||||
// Check any provided addresses are valid
|
||||
if (involvingAddresses.stream().anyMatch(address -> !Crypto.isValidAddress(address)))
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
|
||||
if (before != null && before < 1500000000000L)
|
||||
throw new IllegalArgumentException("Invalid timestamp");
|
||||
|
||||
byte[] referenceBytes = null;
|
||||
if (reference != null)
|
||||
referenceBytes = Base58.decode(reference);
|
||||
|
||||
byte[] chatReferenceBytes = null;
|
||||
if (chatReference != null)
|
||||
chatReferenceBytes = Base58.decode(chatReference);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getChatRepository().getMessagesMatchingCriteria(
|
||||
before,
|
||||
after,
|
||||
txGroupId,
|
||||
referenceBytes,
|
||||
chatReferenceBytes,
|
||||
hasChatReference,
|
||||
involvingAddresses,
|
||||
limit, offset, reverse);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ArbitraryResourceInfo> searchQdnResources(Service service, String identifier, Boolean defaultResource,
|
||||
String nameListFilter, Boolean includeStatus, Boolean includeMetadata,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
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 new IllegalArgumentException("identifier cannot be specified when requesting a default resource");
|
||||
}
|
||||
|
||||
// Load filter from list if needed
|
||||
List<String> names = null;
|
||||
if (nameListFilter != null) {
|
||||
names = ResourceListManager.getInstance().getStringsInList(nameListFilter);
|
||||
if (names.isEmpty()) {
|
||||
// List doesn't exist or is empty - so there will be no matches
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
List<ArbitraryResourceInfo> resources = repository.getArbitraryRepository()
|
||||
.getArbitraryResources(service, identifier, names, defaultRes, limit, offset, reverse);
|
||||
|
||||
if (resources == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (includeStatus != null && includeStatus) {
|
||||
resources = ArbitraryTransactionUtils.addStatusToResources(resources);
|
||||
}
|
||||
if (includeMetadata != null && includeMetadata) {
|
||||
resources = ArbitraryTransactionUtils.addMetadataToResources(resources);
|
||||
}
|
||||
|
||||
return resources;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static ArbitraryResourceStatus getQdnResourceStatus(Service service, String name, String identifier) {
|
||||
return ArbitraryTransactionUtils.getStatus(service, name, identifier, false);
|
||||
}
|
||||
|
||||
public static String fetchQdnResource64(Service service, String name, String identifier, String filepath, boolean rebuild) throws DataException {
|
||||
ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier);
|
||||
try {
|
||||
|
||||
int attempts = 0;
|
||||
int maxAttempts = 5;
|
||||
|
||||
// Loop until we have data
|
||||
while (!Controller.isStopping()) {
|
||||
attempts++;
|
||||
if (!arbitraryDataReader.isBuilding()) {
|
||||
try {
|
||||
arbitraryDataReader.loadSynchronously(rebuild);
|
||||
break;
|
||||
} catch (MissingDataException e) {
|
||||
if (attempts > maxAttempts) {
|
||||
// Give up after 5 attempts
|
||||
throw new DataException("Data unavailable. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Thread.sleep(3000L);
|
||||
}
|
||||
|
||||
java.nio.file.Path outputPath = arbitraryDataReader.getFilePath();
|
||||
if (outputPath == null) {
|
||||
// Assume the resource doesn't exist
|
||||
throw new DataException("File not found");
|
||||
}
|
||||
|
||||
if (filepath == null || filepath.isEmpty()) {
|
||||
// No file path supplied - so check if this is a single file resource
|
||||
String[] files = ArrayUtils.removeElement(outputPath.toFile().list(), ".qortal");
|
||||
if (files.length == 1) {
|
||||
// This is a single file resource
|
||||
filepath = files[0];
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("filepath is required for resources containing more than one file");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: limit file size that can be read into memory
|
||||
java.nio.file.Path path = Paths.get(outputPath.toString(), filepath);
|
||||
if (!Files.exists(path)) {
|
||||
return null;
|
||||
}
|
||||
byte[] bytes = Files.readAllBytes(path);
|
||||
if (bytes != null) {
|
||||
return Base64.toBase64String(bytes);
|
||||
}
|
||||
throw new DataException("File contents could not be read");
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new DataException(String.format("Unable to fetch resource: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<GroupData> listGroups(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<GroupData> allGroupData = repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
allGroupData.forEach(groupData -> {
|
||||
try {
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
||||
} catch (DataException e) {
|
||||
// Exclude memberCount for this group
|
||||
}
|
||||
});
|
||||
return allGroupData;
|
||||
}
|
||||
}
|
||||
|
||||
public static Long getBalance(Long assetId, String address) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (assetId == null)
|
||||
assetId = Asset.QORT;
|
||||
|
||||
Account account = new Account(repository, address);
|
||||
return account.getConfirmedBalance(assetId);
|
||||
}
|
||||
}
|
||||
|
||||
public static ATData getAtInfo(String atAddress) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
if (atData == null) {
|
||||
throw new IllegalArgumentException("AT not found");
|
||||
}
|
||||
return atData;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAtData58(String atAddress) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
if (atStateData == null) {
|
||||
throw new IllegalArgumentException("AT not found");
|
||||
}
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
return Base58.encode(dataBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ATData> listATs(String codeHash58, Boolean isExecutable, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
// Decode codeHash
|
||||
byte[] codeHash;
|
||||
try {
|
||||
codeHash = Base58.decode(codeHash58);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
// codeHash must be present and have correct length
|
||||
if (codeHash == null || codeHash.length != 32)
|
||||
throw new IllegalArgumentException("Invalid code hash");
|
||||
|
||||
// Impose a limit on 'limit'
|
||||
if (limit != null && limit > 100)
|
||||
throw new IllegalArgumentException("Limit is too high");
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getATRepository().getATsByFunctionality(codeHash, isExecutable, limit, offset, reverse);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user