mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-03 16:27:50 +00:00
Added FATJAR packaging support to pom.xml Added some "summary" fields to API calls but more need doing. Corrected path clash from having unnecessary @OpenAPIDefinition annotations. Added API "tags" to group similar calls (address-based, block-related, etc.) Fixed addresses/lastreference/{address} Implemented addresses/lastreference/{address}/unconfirmed Implemented addresses/assets/{address} Added /admin/stop and /admin/uptime API calls. Moved general API info into new src/api/ApiDefinition.java Added CORS support to ApiService Added /transactions/address/{address} and /transactions/block/{signature} Replaced references to test.Common.* to do with repository factory. This fixes issues with building FATJAR due to references to test classes that are omitted from FATJAR. Changes to AccountBalanceData, BlockData and TransactionData to support JAX-RS rendering to JSON. Added getUnconfirmedLastReference() to Account. Added getAllBalances(address) to account repository - returns all asset balances for that address. Added getAllSignaturesInvolvingAddress(address) to account repository but currently only uses TransactionRecipients HSQLDB table. (And even that wasn't automatically populated). Included: very basic block explorer to be opened in browser as a file: block-explorer.html
221 lines
8.0 KiB
Java
221 lines
8.0 KiB
Java
package api;
|
|
|
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
import globalization.ContextPaths;
|
|
import globalization.Translator;
|
|
import io.swagger.v3.core.converter.ModelConverters;
|
|
import io.swagger.v3.jaxrs2.Reader;
|
|
import io.swagger.v3.jaxrs2.ReaderListener;
|
|
import io.swagger.v3.oas.models.media.Content;
|
|
import io.swagger.v3.oas.models.OpenAPI;
|
|
import io.swagger.v3.oas.models.Operation;
|
|
import io.swagger.v3.oas.models.PathItem;
|
|
import io.swagger.v3.oas.models.examples.Example;
|
|
import io.swagger.v3.oas.models.info.Info;
|
|
import io.swagger.v3.oas.models.media.MediaType;
|
|
import io.swagger.v3.oas.models.media.Schema;
|
|
import io.swagger.v3.oas.models.responses.ApiResponse;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
|
|
public class AnnotationPostProcessor implements ReaderListener {
|
|
|
|
private class ContextInformation {
|
|
public String path;
|
|
public Map<String, String> keys;
|
|
}
|
|
|
|
private final Translator translator;
|
|
private final ApiErrorFactory apiErrorFactory;
|
|
|
|
public AnnotationPostProcessor() {
|
|
this(Translator.getInstance(), ApiErrorFactory.getInstance());
|
|
}
|
|
|
|
public AnnotationPostProcessor(Translator translator, ApiErrorFactory apiErrorFactory) {
|
|
this.translator = translator;
|
|
this.apiErrorFactory = apiErrorFactory;
|
|
}
|
|
|
|
@Override
|
|
public void beforeScan(Reader reader, OpenAPI openAPI) {}
|
|
|
|
@Override
|
|
public void afterScan(Reader reader, OpenAPI openAPI) {
|
|
// use context path and keys from "x-translation" extension annotations
|
|
// to translate supported annotations and finally remove "x-translation" extensions
|
|
Info resourceInfo = openAPI.getInfo();
|
|
ContextInformation resourceContext = getContextInformation(openAPI.getExtensions());
|
|
removeTranslationAnnotations(openAPI.getExtensions());
|
|
TranslateProperties(Constants.TRANSLATABLE_INFO_PROPERTIES, resourceContext, resourceInfo);
|
|
|
|
for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet())
|
|
{
|
|
PathItem pathItem = pathEntry.getValue();
|
|
ContextInformation pathContext = getContextInformation(pathItem.getExtensions(), resourceContext);
|
|
removeTranslationAnnotations(pathItem.getExtensions());
|
|
TranslateProperties(Constants.TRANSLATABLE_PATH_ITEM_PROPERTIES, pathContext, pathItem);
|
|
|
|
for (Operation operation : pathItem.readOperations()) {
|
|
ContextInformation operationContext = getContextInformation(operation.getExtensions(), pathContext);
|
|
removeTranslationAnnotations(operation.getExtensions());
|
|
TranslateProperties(Constants.TRANSLATABLE_OPERATION_PROPERTIES, operationContext, operation);
|
|
|
|
addApiErrorResponses(operation);
|
|
removeApiErrorsAnnotations(operation.getExtensions());
|
|
|
|
for (Map.Entry<String, ApiResponse> responseEntry : operation.getResponses().entrySet()) {
|
|
ApiResponse response = responseEntry.getValue();
|
|
ContextInformation responseContext = getContextInformation(response.getExtensions(), operationContext);
|
|
removeTranslationAnnotations(response.getExtensions());
|
|
TranslateProperties(Constants.TRANSLATABLE_API_RESPONSE_PROPERTIES, responseContext, response);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addApiErrorResponses(Operation operation) {
|
|
List<ApiError> apiErrors = getApiErrors(operation.getExtensions());
|
|
if(apiErrors != null) {
|
|
for(ApiError apiError : apiErrors) {
|
|
String statusCode = Integer.toString(apiError.getStatus());
|
|
ApiResponse apiResponse = operation.getResponses().get(statusCode);
|
|
if(apiResponse == null) {
|
|
Schema errorMessageSchema = ModelConverters.getInstance().readAllAsResolvedSchema(ApiErrorMessage.class).schema;
|
|
MediaType mediaType = new MediaType().schema(errorMessageSchema);
|
|
Content content = new Content().addMediaType(javax.ws.rs.core.MediaType.APPLICATION_JSON, mediaType);
|
|
apiResponse = new ApiResponse().content(content);
|
|
operation.getResponses().addApiResponse(statusCode, apiResponse);
|
|
}
|
|
|
|
int apiErrorCode = apiError.getCode();
|
|
ApiErrorMessage apiErrorMessage = new ApiErrorMessage(apiErrorCode, this.apiErrorFactory.getErrorMessage(apiError));
|
|
Example example = new Example().value(apiErrorMessage);
|
|
|
|
// XXX: addExamples(..) is not working in Swagger 2.0.4. This bug is referenced in https://github.com/swagger-api/swagger-ui/issues/2651
|
|
// Replace the call to .setExample(..) by .addExamples(..) when the bug is fixed.
|
|
apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).setExample(example);
|
|
//apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).addExamples(Integer.toString(apiErrorCode), example);
|
|
}
|
|
}
|
|
}
|
|
|
|
private <T> void TranslateProperties(List<TranslatableProperty<T>> translatableProperties, ContextInformation context, T item) {
|
|
if(context.keys != null) {
|
|
Map<String, String> keys = context.keys;
|
|
for(TranslatableProperty<T> prop : translatableProperties) {
|
|
String key = keys.get(prop.keyName());
|
|
if(key != null) {
|
|
String originalValue = prop.getValue(item);
|
|
// XXX: use browser locale instead default?
|
|
String translation = translator.translate(context.path, key, originalValue);
|
|
prop.setValue(item, translation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<ApiError> getApiErrors(Map<String, Object> extensions) {
|
|
if(extensions == null)
|
|
return null;
|
|
|
|
List<String> apiErrorStrings = new ArrayList();
|
|
try {
|
|
ArrayNode apiErrorsNode = (ArrayNode)extensions.get("x-" + Constants.API_ERRORS_EXTENSION_NAME);
|
|
if(apiErrorsNode == null)
|
|
return null;
|
|
|
|
for(int i = 0; i < apiErrorsNode.size(); i++) {
|
|
String errorString = apiErrorsNode.get(i).asText();
|
|
apiErrorStrings.add(errorString);
|
|
}
|
|
} catch(Exception e) {
|
|
// TODO: error logging
|
|
return null;
|
|
}
|
|
|
|
List<ApiError> result = new ArrayList<>();
|
|
for(String apiErrorString : apiErrorStrings) {
|
|
ApiError apiError = null;
|
|
try {
|
|
apiError = ApiError.valueOf(apiErrorString);
|
|
} catch(IllegalArgumentException e) {
|
|
try {
|
|
int errorCodeInt = Integer.parseInt(apiErrorString);
|
|
apiError = ApiError.fromCode(errorCodeInt);
|
|
} catch (NumberFormatException ex) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if(apiError == null)
|
|
return null;
|
|
|
|
result.add(apiError);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private ContextInformation getContextInformation(Map<String, Object> extensions) {
|
|
return getContextInformation(extensions, null);
|
|
}
|
|
|
|
private ContextInformation getContextInformation(Map<String, Object> extensions, ContextInformation base) {
|
|
if(extensions != null) {
|
|
Map<String, Object> translationDefinitions = (Map<String, Object>)extensions.get("x-" + Constants.TRANSLATION_EXTENSION_NAME);
|
|
if(translationDefinitions != null) {
|
|
ContextInformation result = new ContextInformation();
|
|
result.path = combinePaths(base, (String)translationDefinitions.get(Constants.TRANSLATION_PATH_EXTENSION_NAME));
|
|
result.keys = getTranslationKeys(translationDefinitions);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if(base != null) {
|
|
ContextInformation result = new ContextInformation();
|
|
result.path = base.path;
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void removeApiErrorsAnnotations(Map<String, Object> extensions) {
|
|
String extensionName = Constants.API_ERRORS_EXTENSION_NAME;
|
|
removeExtension(extensions, extensionName);
|
|
}
|
|
|
|
private void removeTranslationAnnotations(Map<String, Object> extensions) {
|
|
String extensionName = Constants.TRANSLATION_EXTENSION_NAME;
|
|
removeExtension(extensions, extensionName);
|
|
}
|
|
|
|
private void removeExtension(Map<String, Object> extensions, String extensionName) {
|
|
if(extensions == null)
|
|
return;
|
|
|
|
extensions.remove("x-" + extensionName);
|
|
}
|
|
|
|
private Map<String, String> getTranslationKeys(Map<String, Object> translationDefinitions) {
|
|
Map<String, String> result = new HashMap<>();
|
|
|
|
for(TranslatableProperty prop : Constants.TRANSLATABLE_INFO_PROPERTIES) {
|
|
String key = (String)translationDefinitions.get(prop.keyName());
|
|
if(key != null)
|
|
result.put(prop.keyName(), key);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private String combinePaths(ContextInformation base, String path) {
|
|
String basePath = (base != null) ? base.path : null;
|
|
return ContextPaths.combinePaths(basePath, path);
|
|
}
|
|
}
|