mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-19 05:24:58 +00:00
ADDED: globalization.Translator - basic globalization support (implementation needed)
ADDED: api.Security (implementation needed) ADDED: api.APIErrorFactory CHANGED: added command execution to ApiClient
This commit is contained in:
parent
19a9a3a98b
commit
646462942c
10
pom.xml
10
pom.xml
@ -90,5 +90,15 @@
|
|||||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||||
<version>2.0.4</version>
|
<version>2.0.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-text</artifactId>
|
||||||
|
<version>1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jersey.media</groupId>
|
||||||
|
<artifactId>jersey-media-moxy</artifactId>
|
||||||
|
<version>2.27</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
@ -6,20 +6,20 @@ import repository.RepositoryFactory;
|
|||||||
import repository.RepositoryManager;
|
import repository.RepositoryManager;
|
||||||
import repository.hsqldb.HSQLDBRepositoryFactory;
|
import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||||
|
|
||||||
|
|
||||||
public class Start {
|
public class Start {
|
||||||
private static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
|
|
||||||
|
|
||||||
public static void main(String args[]) throws DataException
|
private static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
|
||||||
{
|
|
||||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
public static void main(String args[]) throws DataException {
|
||||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||||
|
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||||
ApiService apiService = new ApiService();
|
|
||||||
apiService.start();
|
ApiService apiService = ApiService.getInstance();
|
||||||
|
apiService.start();
|
||||||
ApiClient client = new ApiClient(apiService);
|
|
||||||
String test = client.executeCommand("help ALL");
|
//// testing the API client
|
||||||
System.out.println(test);
|
//ApiClient client = ApiClient.getInstance();
|
||||||
}
|
//String test = client.executeCommand("GET blocks/height");
|
||||||
|
//System.out.println(test);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package api;
|
package api;
|
||||||
|
|
||||||
|
import globalization.Translator;
|
||||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
@ -16,133 +18,184 @@ import javax.ws.rs.PUT;
|
|||||||
import javax.ws.rs.PATCH;
|
import javax.ws.rs.PATCH;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
import javax.ws.rs.HttpMethod;
|
import javax.ws.rs.HttpMethod;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
|
import javax.ws.rs.client.Invocation;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
|
||||||
|
import settings.Settings;
|
||||||
|
|
||||||
public class ApiClient {
|
public class ApiClient {
|
||||||
private class HelpString
|
|
||||||
{
|
|
||||||
public final Pattern pattern;
|
|
||||||
public final String fullPath;
|
|
||||||
public final String description;
|
|
||||||
|
|
||||||
public HelpString(Pattern pattern, String fullPath, String description)
|
|
||||||
{
|
|
||||||
this.pattern = pattern;
|
|
||||||
this.fullPath = fullPath;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?<command>.*)$", Pattern.CASE_INSENSITIVE);
|
|
||||||
private static final List<Class<? extends Annotation>> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
|
|
||||||
GET.class,
|
|
||||||
POST.class,
|
|
||||||
PUT.class,
|
|
||||||
PATCH.class,
|
|
||||||
DELETE.class
|
|
||||||
);
|
|
||||||
|
|
||||||
ApiService apiService;
|
|
||||||
List<HelpString> helpStrings;
|
|
||||||
|
|
||||||
public ApiClient(ApiService apiService)
|
|
||||||
{
|
|
||||||
this.apiService = apiService;
|
|
||||||
this.helpStrings = getHelpStrings(apiService.getResources());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HelpString> getHelpStrings(Iterable<Class<?>> resources)
|
private class HelpString {
|
||||||
{
|
|
||||||
List<HelpString> result = new ArrayList<>();
|
|
||||||
|
|
||||||
// scan each resource class
|
|
||||||
for (Class<?> resource : resources) {
|
|
||||||
if(OpenApiResource.class.isAssignableFrom(resource))
|
|
||||||
continue; // ignore swagger resources
|
|
||||||
|
|
||||||
Path resourcePath = resource.getDeclaredAnnotation(Path.class);
|
|
||||||
if(resourcePath == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String resourcePathString = resourcePath.value();
|
|
||||||
|
|
||||||
// scan each method
|
|
||||||
for(Method method : resource.getDeclaredMethods())
|
|
||||||
{
|
|
||||||
Operation operationAnnotation = method.getAnnotation(Operation.class);
|
|
||||||
if(operationAnnotation == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String description = operationAnnotation.description();
|
|
||||||
|
|
||||||
Path methodPath = method.getDeclaredAnnotation(Path.class);
|
|
||||||
String methodPathString = (methodPath != null) ? methodPath.value() : "";
|
|
||||||
|
|
||||||
// scan for each potential http method
|
public final Pattern pattern;
|
||||||
for(Class<? extends Annotation> restMethodAnnotation : HTTP_METHOD_ANNOTATIONS)
|
public final String fullPath;
|
||||||
{
|
public final String description;
|
||||||
Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation);
|
|
||||||
if(annotation == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
|
public HelpString(Pattern pattern, String fullPath, String description) {
|
||||||
String httpMethodString = httpMethod.value();
|
this.pattern = pattern;
|
||||||
|
this.fullPath = fullPath;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
|
private static final Pattern COMMAND_PATTERN = Pattern.compile("^ *(?<method>GET|POST|PUT|PATCH|DELETE) *(?<path>.*)$");
|
||||||
Pattern pattern = Pattern.compile("^ *(" + httpMethodString + " *)?" + getHelpPatternForPath(resourcePathString + methodPathString));
|
private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?<command>.*)$", Pattern.CASE_INSENSITIVE);
|
||||||
result.add(new HelpString(pattern, fullPath, description));
|
private static final List<Class<? extends Annotation>> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
|
||||||
}
|
GET.class,
|
||||||
}
|
POST.class,
|
||||||
}
|
PUT.class,
|
||||||
|
PATCH.class,
|
||||||
// sort by path
|
DELETE.class
|
||||||
result.sort((h1, h2)-> h1.fullPath.compareTo(h2.fullPath));
|
);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHelpPatternForPath(String path)
|
|
||||||
{
|
|
||||||
path = path
|
|
||||||
.replaceAll("\\.", "\\.") // escapes "." as "\."
|
|
||||||
.replaceAll("\\{.*?\\}", ".*?"); // replace placeholders "{...}" by the "ungreedy match anything" pattern ".*?"
|
|
||||||
|
|
||||||
// arrange the regex pattern so that it also matches partial
|
ApiService apiService;
|
||||||
StringBuilder result = new StringBuilder();
|
private Translator translator;
|
||||||
String[] parts = path.split("/");
|
List<HelpString> helpStrings;
|
||||||
for(int i = 0; i < parts.length; i++)
|
|
||||||
{
|
|
||||||
if(i!=0)
|
|
||||||
result.append("(/"); // opening bracket
|
|
||||||
result.append(parts[i]);
|
|
||||||
}
|
|
||||||
for(int i = 0; i < parts.length - 1; i++)
|
|
||||||
result.append(")?"); // closing bracket
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String executeCommand(String command)
|
|
||||||
{
|
|
||||||
final Matcher helpMatch = HELP_COMMAND_PATTERN.matcher(command);
|
|
||||||
if(helpMatch.matches())
|
|
||||||
{
|
|
||||||
command = helpMatch.group("command");
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
boolean showAll = command.trim().equalsIgnoreCase("all");
|
|
||||||
for(HelpString helpString : helpStrings)
|
|
||||||
{
|
|
||||||
if(showAll || helpString.pattern.matcher(command).matches())
|
|
||||||
appendHelp(result, helpString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendHelp(StringBuilder builder, HelpString helpString) {
|
public ApiClient(ApiService apiService, Translator translator) {
|
||||||
builder.append(helpString.fullPath + "\n");
|
this.apiService = apiService;
|
||||||
builder.append(helpString.description + "\n");
|
this.helpStrings = getHelpStrings(apiService.getResources());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX: replace singleton pattern by dependency injection?
|
||||||
|
private static ApiClient instance;
|
||||||
|
|
||||||
|
public static ApiClient getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ApiClient(ApiService.getInstance(), Translator.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HelpString> getHelpStrings(Iterable<Class<?>> resources) {
|
||||||
|
List<HelpString> result = new ArrayList<>();
|
||||||
|
|
||||||
|
// scan each resource class
|
||||||
|
for (Class<?> resource : resources) {
|
||||||
|
if (OpenApiResource.class.isAssignableFrom(resource)) {
|
||||||
|
continue; // ignore swagger resources
|
||||||
|
}
|
||||||
|
Path resourcePath = resource.getDeclaredAnnotation(Path.class);
|
||||||
|
if (resourcePath == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resourcePathString = resourcePath.value();
|
||||||
|
|
||||||
|
// scan each method
|
||||||
|
for (Method method : resource.getDeclaredMethods()) {
|
||||||
|
Operation operationAnnotation = method.getAnnotation(Operation.class);
|
||||||
|
if (operationAnnotation == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String description = operationAnnotation.description();
|
||||||
|
|
||||||
|
Path methodPath = method.getDeclaredAnnotation(Path.class);
|
||||||
|
String methodPathString = (methodPath != null) ? methodPath.value() : "";
|
||||||
|
|
||||||
|
// scan for each potential http method
|
||||||
|
for (Class<? extends Annotation> restMethodAnnotation : HTTP_METHOD_ANNOTATIONS) {
|
||||||
|
Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation);
|
||||||
|
if (annotation == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
|
||||||
|
String httpMethodString = httpMethod.value();
|
||||||
|
|
||||||
|
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
|
||||||
|
Pattern pattern = Pattern.compile("^ *(" + httpMethodString + " *)?" + getHelpPatternForPath(resourcePathString + methodPathString));
|
||||||
|
result.add(new HelpString(pattern, fullPath, description));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by path
|
||||||
|
result.sort((h1, h2) -> h1.fullPath.compareTo(h2.fullPath));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHelpPatternForPath(String path) {
|
||||||
|
path = path
|
||||||
|
.replaceAll("\\.", "\\.") // escapes "." as "\."
|
||||||
|
.replaceAll("\\{.*?\\}", ".*?"); // replace placeholders "{...}" by the "ungreedy match anything" pattern ".*?"
|
||||||
|
|
||||||
|
// arrange the regex pattern so that it also matches partial
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String[] parts = path.split("/");
|
||||||
|
for (int i = 0; i < parts.length; i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
result.append("(/"); // opening bracket
|
||||||
|
}
|
||||||
|
result.append(parts[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parts.length - 1; i++) {
|
||||||
|
result.append(")?"); // closing bracket
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String executeCommand(String command) {
|
||||||
|
// check if this is a help command
|
||||||
|
Matcher match = HELP_COMMAND_PATTERN.matcher(command);
|
||||||
|
if (match.matches()) {
|
||||||
|
command = match.group("command");
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
boolean showAll = command.trim().equalsIgnoreCase("all");
|
||||||
|
for (HelpString helpString : helpStrings) {
|
||||||
|
if (showAll || helpString.pattern.matcher(command).matches()) {
|
||||||
|
appendHelp(result, helpString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
match = COMMAND_PATTERN.matcher(command);
|
||||||
|
if(!match.matches())
|
||||||
|
return this.translator.translate(Locale.getDefault(), "ApiClient: INVALID_COMMAND", "Invalid command! \nType help to get a list of commands.");
|
||||||
|
|
||||||
|
// send the command to the API service
|
||||||
|
String method = match.group("method");
|
||||||
|
String path = match.group("path");
|
||||||
|
String url = String.format("http://127.0.0.1:%d/%s", Settings.getInstance().getRpcPort(), path);
|
||||||
|
|
||||||
|
Client client = ClientBuilder.newClient();
|
||||||
|
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // workaround for non-standard HTTP methods like PATCH
|
||||||
|
WebTarget wt = client.target(url);
|
||||||
|
Invocation.Builder builder = wt.request(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN);
|
||||||
|
Response response = builder.method(method);
|
||||||
|
|
||||||
|
// send back result
|
||||||
|
final String body = response.readEntity(String.class);
|
||||||
|
final int status = response.getStatus();
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
if(status >= 400) {
|
||||||
|
result.append("HTTP Status ");
|
||||||
|
result.append(status);
|
||||||
|
if(!StringUtils.isBlank(body)) {
|
||||||
|
result.append(": ");
|
||||||
|
result.append(body);
|
||||||
|
}
|
||||||
|
result.append("\nType help to get a list of commands.");
|
||||||
|
} else {
|
||||||
|
result.append(body);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendHelp(StringBuilder builder, HelpString helpString) {
|
||||||
|
builder.append(helpString.fullPath + "\n");
|
||||||
|
builder.append(helpString.description + "\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
120
src/api/ApiError.java
Normal file
120
src/api/ApiError.java
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
package api;
|
||||||
|
|
||||||
|
public enum ApiError {
|
||||||
|
//COMMON
|
||||||
|
UNKNOWN(0, 500),
|
||||||
|
JSON(1, 400),
|
||||||
|
NO_BALANCE(2, 422),
|
||||||
|
NOT_YET_RELEASED(3, 422),
|
||||||
|
|
||||||
|
//VALIDATION
|
||||||
|
INVALID_SIGNATURE(101, 400),
|
||||||
|
INVALID_ADDRESS(102, 400),
|
||||||
|
INVALID_SEED(103, 400),
|
||||||
|
INVALID_AMOUNT(104, 400),
|
||||||
|
INVALID_FEE(105, 400),
|
||||||
|
INVALID_SENDER(106, 400),
|
||||||
|
INVALID_RECIPIENT(107, 400),
|
||||||
|
INVALID_NAME_LENGTH(108, 400),
|
||||||
|
INVALID_VALUE_LENGTH(109, 400),
|
||||||
|
INVALID_NAME_OWNER(110, 400),
|
||||||
|
INVALID_BUYER(111, 400),
|
||||||
|
INVALID_PUBLIC_KEY(112, 400),
|
||||||
|
INVALID_OPTIONS_LENGTH(113, 400),
|
||||||
|
INVALID_OPTION_LENGTH(114, 400),
|
||||||
|
INVALID_DATA(115, 400),
|
||||||
|
INVALID_DATA_LENGTH(116, 400),
|
||||||
|
INVALID_UPDATE_VALUE(117, 400),
|
||||||
|
KEY_ALREADY_EXISTS(118, 422),
|
||||||
|
KEY_NOT_EXISTS(119, 404),
|
||||||
|
LAST_KEY_IS_DEFAULT_KEY_ERROR(120, 422),
|
||||||
|
FEE_LESS_REQUIRED(121, 422),
|
||||||
|
WALLET_NOT_IN_SYNC(122, 422),
|
||||||
|
INVALID_NETWORK_ADDRESS(123, 404),
|
||||||
|
|
||||||
|
//WALLET
|
||||||
|
WALLET_NO_EXISTS(201, 404),
|
||||||
|
WALLET_ADDRESS_NO_EXISTS(202, 404),
|
||||||
|
WALLET_LOCKED(203, 422),
|
||||||
|
WALLET_ALREADY_EXISTS(204, 422),
|
||||||
|
WALLET_API_CALL_FORBIDDEN_BY_USER(205, 403),
|
||||||
|
|
||||||
|
//BLOCKS
|
||||||
|
BLOCK_NO_EXISTS(301, 404),
|
||||||
|
|
||||||
|
//TRANSACTIONS
|
||||||
|
TRANSACTION_NO_EXISTS(311, 404),
|
||||||
|
PUBLIC_KEY_NOT_FOUND(304, 404),
|
||||||
|
|
||||||
|
//NAMING
|
||||||
|
NAME_NO_EXISTS(401, 404),
|
||||||
|
NAME_ALREADY_EXISTS(402, 422),
|
||||||
|
NAME_ALREADY_FOR_SALE(403, 422),
|
||||||
|
NAME_NOT_LOWER_CASE(404, 422),
|
||||||
|
NAME_SALE_NO_EXISTS(410, 404),
|
||||||
|
BUYER_ALREADY_OWNER(411, 422),
|
||||||
|
|
||||||
|
//POLLS
|
||||||
|
POLL_NO_EXISTS(501, 404),
|
||||||
|
POLL_ALREADY_EXISTS(502, 422),
|
||||||
|
DUPLICATE_OPTION(503, 422),
|
||||||
|
POLL_OPTION_NO_EXISTS(504, 404),
|
||||||
|
ALREADY_VOTED_FOR_THAT_OPTION(505, 422),
|
||||||
|
|
||||||
|
//ASSET
|
||||||
|
INVALID_ASSET_ID(601, 400),
|
||||||
|
|
||||||
|
//NAME PAYMENTS
|
||||||
|
NAME_NOT_REGISTERED(701, 422),
|
||||||
|
NAME_FOR_SALE(702, 422),
|
||||||
|
NAME_WITH_SPACE(703, 422),
|
||||||
|
|
||||||
|
//ATs
|
||||||
|
INVALID_DESC_LENGTH(801, 400),
|
||||||
|
EMPTY_CODE(802, 400),
|
||||||
|
DATA_SIZE(803, 400),
|
||||||
|
NULL_PAGES(804, 400),
|
||||||
|
INVALID_TYPE_LENGTH(805, 400),
|
||||||
|
INVALID_TAGS_LENGTH(806, 400),
|
||||||
|
INVALID_CREATION_BYTES(809, 400),
|
||||||
|
|
||||||
|
//BLOG/Namestorage
|
||||||
|
BODY_EMPTY(901, 400),
|
||||||
|
BLOG_DISABLED(902, 403),
|
||||||
|
NAME_NOT_OWNER(903, 422),
|
||||||
|
TX_AMOUNT(904, 400),
|
||||||
|
BLOG_ENTRY_NO_EXISTS(905, 404),
|
||||||
|
BLOG_EMPTY(906, 404),
|
||||||
|
POSTID_EMPTY(907, 400),
|
||||||
|
POST_NOT_EXISTING(908, 404),
|
||||||
|
COMMENTING_DISABLED(909, 403),
|
||||||
|
COMMENT_NOT_EXISTING(910, 404),
|
||||||
|
INVALID_COMMENT_OWNER(911, 422),
|
||||||
|
|
||||||
|
//Messages
|
||||||
|
MESSAGE_FORMAT_NOT_HEX(1001, 400),
|
||||||
|
MESSAGE_BLANK(1002, 400),
|
||||||
|
NO_PUBLIC_KEY(1003, 422),
|
||||||
|
MESSAGESIZE_EXCEEDED(1004, 400);
|
||||||
|
|
||||||
|
private final int code; // API error code
|
||||||
|
private final int status; // HTTP status code
|
||||||
|
|
||||||
|
private ApiError(int code) {
|
||||||
|
this(code, 400); // defaults to "400 - BAD REQUEST"
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiError(int code, int status) {
|
||||||
|
this.code = code;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
}
|
181
src/api/ApiErrorFactory.java
Normal file
181
src/api/ApiErrorFactory.java
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package api;
|
||||||
|
|
||||||
|
import globalization.Translator;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ApiErrorFactory {
|
||||||
|
|
||||||
|
private class ErrorMessageEntry {
|
||||||
|
|
||||||
|
String templateKey;
|
||||||
|
String defaultTemplate;
|
||||||
|
AbstractMap.Entry<String, Object>[] templateValues;
|
||||||
|
|
||||||
|
public ErrorMessageEntry(String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
|
||||||
|
this.templateKey = templateKey;
|
||||||
|
this.defaultTemplate = defaultTemplate;
|
||||||
|
this.templateValues = templateValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Translator translator;
|
||||||
|
private Map<ApiError, ErrorMessageEntry> errorMessages;
|
||||||
|
|
||||||
|
public ApiErrorFactory(Translator translator) {
|
||||||
|
this.translator = translator;
|
||||||
|
|
||||||
|
this.errorMessages = new HashMap<ApiError, ErrorMessageEntry>();
|
||||||
|
|
||||||
|
//COMMON
|
||||||
|
this.errorMessages.put(ApiError.UNKNOWN, createErrorMessageEntry(ApiError.UNKNOWN, "unknown error"));
|
||||||
|
this.errorMessages.put(ApiError.JSON, createErrorMessageEntry(ApiError.JSON, "failed to parse json message"));
|
||||||
|
this.errorMessages.put(ApiError.NO_BALANCE, createErrorMessageEntry(ApiError.NO_BALANCE, "not enough balance"));
|
||||||
|
this.errorMessages.put(ApiError.NOT_YET_RELEASED, createErrorMessageEntry(ApiError.NOT_YET_RELEASED, "that feature is not yet released"));
|
||||||
|
|
||||||
|
//VALIDATION
|
||||||
|
this.errorMessages.put(ApiError.INVALID_SIGNATURE, createErrorMessageEntry(ApiError.INVALID_SIGNATURE, "invalid signature"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_ADDRESS, createErrorMessageEntry(ApiError.INVALID_ADDRESS, "invalid address"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_SEED, createErrorMessageEntry(ApiError.INVALID_SEED, "invalid seed"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_AMOUNT, createErrorMessageEntry(ApiError.INVALID_AMOUNT, "invalid amount"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_FEE, createErrorMessageEntry(ApiError.INVALID_FEE, "invalid fee"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_SENDER, createErrorMessageEntry(ApiError.INVALID_SENDER, "invalid sender"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_RECIPIENT, createErrorMessageEntry(ApiError.INVALID_RECIPIENT, "invalid recipient"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_NAME_LENGTH, createErrorMessageEntry(ApiError.INVALID_NAME_LENGTH, "invalid name length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_VALUE_LENGTH, createErrorMessageEntry(ApiError.INVALID_VALUE_LENGTH, "invalid value length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_NAME_OWNER, createErrorMessageEntry(ApiError.INVALID_NAME_OWNER, "invalid name owner"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_BUYER, createErrorMessageEntry(ApiError.INVALID_BUYER, "invalid buyer"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_PUBLIC_KEY, createErrorMessageEntry(ApiError.INVALID_PUBLIC_KEY, "invalid public key"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_OPTIONS_LENGTH, createErrorMessageEntry(ApiError.INVALID_OPTIONS_LENGTH, "invalid options length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_OPTION_LENGTH, createErrorMessageEntry(ApiError.INVALID_OPTION_LENGTH, "invalid option length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_DATA, createErrorMessageEntry(ApiError.INVALID_DATA, "invalid data"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_DATA_LENGTH, createErrorMessageEntry(ApiError.INVALID_DATA_LENGTH, "invalid data length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_UPDATE_VALUE, createErrorMessageEntry(ApiError.INVALID_UPDATE_VALUE, "invalid update value"));
|
||||||
|
this.errorMessages.put(ApiError.KEY_ALREADY_EXISTS, createErrorMessageEntry(ApiError.KEY_ALREADY_EXISTS, "key already exists, edit is false"));
|
||||||
|
this.errorMessages.put(ApiError.KEY_NOT_EXISTS, createErrorMessageEntry(ApiError.KEY_NOT_EXISTS, "the key does not exist"));
|
||||||
|
// TODO
|
||||||
|
// this.errorMessages.put(ApiError.LAST_KEY_IS_DEFAULT_KEY_ERROR, createErrorMessageEntry(ApiError.LAST_KEY_IS_DEFAULT_KEY_ERROR,
|
||||||
|
// "you can't delete the key \"${key}\" if it is the only key",
|
||||||
|
// new AbstractMap.SimpleEntry<String.Object>("key", Qorakeys.DEFAULT.toString())));
|
||||||
|
this.errorMessages.put(ApiError.FEE_LESS_REQUIRED, createErrorMessageEntry(ApiError.FEE_LESS_REQUIRED, "fee less required"));
|
||||||
|
this.errorMessages.put(ApiError.WALLET_NOT_IN_SYNC, createErrorMessageEntry(ApiError.WALLET_NOT_IN_SYNC, "wallet needs to be synchronized"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_NETWORK_ADDRESS, createErrorMessageEntry(ApiError.INVALID_NETWORK_ADDRESS, "invalid network address"));
|
||||||
|
|
||||||
|
//WALLET
|
||||||
|
this.errorMessages.put(ApiError.WALLET_NO_EXISTS, createErrorMessageEntry(ApiError.WALLET_NO_EXISTS, "wallet does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.WALLET_ADDRESS_NO_EXISTS, createErrorMessageEntry(ApiError.WALLET_ADDRESS_NO_EXISTS, "address does not exist in wallet"));
|
||||||
|
this.errorMessages.put(ApiError.WALLET_LOCKED, createErrorMessageEntry(ApiError.WALLET_LOCKED, "wallet is locked"));
|
||||||
|
this.errorMessages.put(ApiError.WALLET_ALREADY_EXISTS, createErrorMessageEntry(ApiError.WALLET_ALREADY_EXISTS, "wallet already exists"));
|
||||||
|
this.errorMessages.put(ApiError.WALLET_API_CALL_FORBIDDEN_BY_USER, createErrorMessageEntry(ApiError.WALLET_API_CALL_FORBIDDEN_BY_USER, "user denied api call"));
|
||||||
|
|
||||||
|
//BLOCK
|
||||||
|
this.errorMessages.put(ApiError.BLOCK_NO_EXISTS, createErrorMessageEntry(ApiError.BLOCK_NO_EXISTS, "block does not exist"));
|
||||||
|
|
||||||
|
//TRANSACTIONS
|
||||||
|
this.errorMessages.put(ApiError.TRANSACTION_NO_EXISTS, createErrorMessageEntry(ApiError.TRANSACTION_NO_EXISTS, "transactions does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.PUBLIC_KEY_NOT_FOUND, createErrorMessageEntry(ApiError.PUBLIC_KEY_NOT_FOUND, "public key not found"));
|
||||||
|
|
||||||
|
//NAMING
|
||||||
|
this.errorMessages.put(ApiError.NAME_NO_EXISTS, createErrorMessageEntry(ApiError.NAME_NO_EXISTS, "name does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.NAME_ALREADY_EXISTS, createErrorMessageEntry(ApiError.NAME_ALREADY_EXISTS, "name already exists"));
|
||||||
|
this.errorMessages.put(ApiError.NAME_ALREADY_FOR_SALE, createErrorMessageEntry(ApiError.NAME_ALREADY_FOR_SALE, "name already for sale"));
|
||||||
|
this.errorMessages.put(ApiError.NAME_NOT_LOWER_CASE, createErrorMessageEntry(ApiError.NAME_NOT_LOWER_CASE, "name must be lower case"));
|
||||||
|
this.errorMessages.put(ApiError.NAME_SALE_NO_EXISTS, createErrorMessageEntry(ApiError.NAME_SALE_NO_EXISTS, "namesale does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.BUYER_ALREADY_OWNER, createErrorMessageEntry(ApiError.BUYER_ALREADY_OWNER, "buyer is already owner"));
|
||||||
|
|
||||||
|
//POLLS
|
||||||
|
this.errorMessages.put(ApiError.POLL_NO_EXISTS, createErrorMessageEntry(ApiError.POLL_NO_EXISTS, "poll does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.POLL_ALREADY_EXISTS, createErrorMessageEntry(ApiError.POLL_ALREADY_EXISTS, "poll already exists"));
|
||||||
|
this.errorMessages.put(ApiError.DUPLICATE_OPTION, createErrorMessageEntry(ApiError.DUPLICATE_OPTION, "not all options are unique"));
|
||||||
|
this.errorMessages.put(ApiError.POLL_OPTION_NO_EXISTS, createErrorMessageEntry(ApiError.POLL_OPTION_NO_EXISTS, "option does not exist"));
|
||||||
|
this.errorMessages.put(ApiError.ALREADY_VOTED_FOR_THAT_OPTION, createErrorMessageEntry(ApiError.ALREADY_VOTED_FOR_THAT_OPTION, "already voted for that option"));
|
||||||
|
|
||||||
|
//ASSETS
|
||||||
|
this.errorMessages.put(ApiError.INVALID_ASSET_ID, createErrorMessageEntry(ApiError.INVALID_ASSET_ID, "invalid asset id"));
|
||||||
|
|
||||||
|
//NAME PAYMENTS
|
||||||
|
// TODO
|
||||||
|
// this.errorMessages.put(ApiError.NAME_NOT_REGISTERED, createErrorMessageEntry(ApiError.NAME_NOT_REGISTERED, NameResult.NAME_NOT_REGISTERED.getStatusMessage()));
|
||||||
|
// this.errorMessages.put(ApiError.NAME_FOR_SALE, createErrorMessageEntry(ApiError.NAME_FOR_SALE, NameResult.NAME_FOR_SALE.getStatusMessage()));
|
||||||
|
// this.errorMessages.put(ApiError.NAME_WITH_SPACE, createErrorMessageEntry(ApiError.NAME_WITH_SPACE, NameResult.NAME_WITH_SPACE.getStatusMessage()));
|
||||||
|
//AT
|
||||||
|
this.errorMessages.put(ApiError.INVALID_CREATION_BYTES, createErrorMessageEntry(ApiError.INVALID_CREATION_BYTES, "error in creation bytes"));
|
||||||
|
// TODO
|
||||||
|
// this.errorMessages.put(ApiError.INVALID_DESC_LENGTH, createErrorMessageEntry(ApiError.INVALID_DESC_LENGTH,
|
||||||
|
// "invalid description length. max length ${MAX_LENGTH}",
|
||||||
|
// new AbstractMap.SimpleEntry<String, Object>("MAX_LENGTH", AT_Constants.DESC_MAX_LENGTH));
|
||||||
|
this.errorMessages.put(ApiError.EMPTY_CODE, createErrorMessageEntry(ApiError.EMPTY_CODE, "code is empty"));
|
||||||
|
this.errorMessages.put(ApiError.DATA_SIZE, createErrorMessageEntry(ApiError.DATA_SIZE, "invalid data length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_TYPE_LENGTH, createErrorMessageEntry(ApiError.INVALID_TYPE_LENGTH, "invalid type length"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_TAGS_LENGTH, createErrorMessageEntry(ApiError.INVALID_TAGS_LENGTH, "invalid tags length"));
|
||||||
|
this.errorMessages.put(ApiError.NULL_PAGES, createErrorMessageEntry(ApiError.NULL_PAGES, "invalid pages"));
|
||||||
|
|
||||||
|
//BLOG
|
||||||
|
this.errorMessages.put(ApiError.BODY_EMPTY, createErrorMessageEntry(ApiError.BODY_EMPTY, "invalid body it must not be empty"));
|
||||||
|
this.errorMessages.put(ApiError.BLOG_DISABLED, createErrorMessageEntry(ApiError.BLOG_DISABLED, "this blog is disabled"));
|
||||||
|
this.errorMessages.put(ApiError.NAME_NOT_OWNER, createErrorMessageEntry(ApiError.NAME_NOT_OWNER, "the creator address does not own the author name"));
|
||||||
|
// this.errorMessages.put(ApiError.TX_AMOUNT, createErrorMessageEntry(ApiError.TX_AMOUNT,
|
||||||
|
// "the data size is too large - currently only ${BATCH_TX_AMOUNT} arbitrary transactions are allowed at once!",
|
||||||
|
// new AbstractMap.SimpleEntry<String,Object>("BATCH_TX_AMOUNT", BATCH_TX_AMOUNT)));
|
||||||
|
this.errorMessages.put(ApiError.BLOG_ENTRY_NO_EXISTS, createErrorMessageEntry(ApiError.BLOG_ENTRY_NO_EXISTS, "transaction with this signature contains no entries!"));
|
||||||
|
this.errorMessages.put(ApiError.BLOG_EMPTY, createErrorMessageEntry(ApiError.BLOG_EMPTY, "this blog is empty"));
|
||||||
|
this.errorMessages.put(ApiError.POSTID_EMPTY, createErrorMessageEntry(ApiError.POSTID_EMPTY, "the attribute postid is empty! this is the signature of the post you want to comment"));
|
||||||
|
this.errorMessages.put(ApiError.POST_NOT_EXISTING, createErrorMessageEntry(ApiError.POST_NOT_EXISTING, "for the given postid no blogpost to comment was found"));
|
||||||
|
this.errorMessages.put(ApiError.COMMENTING_DISABLED, createErrorMessageEntry(ApiError.COMMENTING_DISABLED, "commenting is for this blog disabled"));
|
||||||
|
this.errorMessages.put(ApiError.COMMENT_NOT_EXISTING, createErrorMessageEntry(ApiError.COMMENT_NOT_EXISTING, "for the given signature no comment was found"));
|
||||||
|
this.errorMessages.put(ApiError.INVALID_COMMENT_OWNER, createErrorMessageEntry(ApiError.INVALID_COMMENT_OWNER, "invalid comment owner"));
|
||||||
|
|
||||||
|
//MESSAGES
|
||||||
|
this.errorMessages.put(ApiError.MESSAGE_FORMAT_NOT_HEX, createErrorMessageEntry(ApiError.MESSAGE_FORMAT_NOT_HEX, "the Message format is not hex - correct the text or use isTextMessage = true"));
|
||||||
|
this.errorMessages.put(ApiError.MESSAGE_BLANK, createErrorMessageEntry(ApiError.MESSAGE_BLANK, "The message attribute is missing or content is blank"));
|
||||||
|
this.errorMessages.put(ApiError.NO_PUBLIC_KEY, createErrorMessageEntry(ApiError.NO_PUBLIC_KEY, "The recipient has not yet performed any action in the blockchain.\nYou can't send an encrypted message to him."));
|
||||||
|
this.errorMessages.put(ApiError.MESSAGESIZE_EXCEEDED, createErrorMessageEntry(ApiError.MESSAGESIZE_EXCEEDED, "Message size exceeded!"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//XXX: replace singleton pattern by dependency injection?
|
||||||
|
private static ApiErrorFactory instance;
|
||||||
|
|
||||||
|
public static ApiErrorFactory getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ApiErrorFactory(Translator.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ErrorMessageEntry createErrorMessageEntry(ApiError errorCode, String defaultTemplate, AbstractMap.SimpleEntry<String, Object>... templateValues) {
|
||||||
|
String templateKey = String.format("%s: ApiError.%s message", ApiErrorFactory.class.getSimpleName(), errorCode.name());
|
||||||
|
return new ErrorMessageEntry(templateKey, defaultTemplate, templateValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiException createError(ApiError error) {
|
||||||
|
return createError(error, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiException createError(ApiError error, Throwable throwable) {
|
||||||
|
Locale locale = Locale.ENGLISH; // XXX: should this be in local language?
|
||||||
|
|
||||||
|
// TODO: handle AT errors
|
||||||
|
// old AT error handling
|
||||||
|
// JSONObject jsonObject = new JSONObject();
|
||||||
|
// jsonObject.put("error", error);
|
||||||
|
// if ( error > Transaction.AT_ERROR )
|
||||||
|
// {
|
||||||
|
// jsonObject.put("message", AT_Error.getATError(error - Transaction.AT_ERROR) );
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// jsonObject.put("message", this.errorMessages.get(error));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity(jsonObject.toJSONString()).build());
|
||||||
|
ErrorMessageEntry errorMessage = this.errorMessages.get(error);
|
||||||
|
String message = this.translator.translate(locale, errorMessage.templateKey, errorMessage.defaultTemplate, errorMessage.templateValues);
|
||||||
|
|
||||||
|
return new ApiException(error.getStatus(), error.getCode(), message, throwable);
|
||||||
|
}
|
||||||
|
}
|
22
src/api/ApiErrorMessage.java
Normal file
22
src/api/ApiErrorMessage.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package api;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
|
@XmlRootElement
|
||||||
|
public class ApiErrorMessage {
|
||||||
|
|
||||||
|
@XmlElement(name = "error")
|
||||||
|
public int error;
|
||||||
|
|
||||||
|
@XmlElement(name = "message")
|
||||||
|
public String message;
|
||||||
|
|
||||||
|
ApiErrorMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiErrorMessage(int errorCode, String message) {
|
||||||
|
this.error = errorCode;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
36
src/api/ApiException.java
Normal file
36
src/api/ApiException.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package api;
|
||||||
|
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
|
public class ApiException extends WebApplicationException {
|
||||||
|
// HTTP status code
|
||||||
|
|
||||||
|
int status;
|
||||||
|
|
||||||
|
// API error code
|
||||||
|
int error;
|
||||||
|
|
||||||
|
String message;
|
||||||
|
|
||||||
|
public ApiException(int status, int error, String message) {
|
||||||
|
this(status, error, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiException(int status, int error, String message, Throwable throwable) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
throwable,
|
||||||
|
Response.status(Status.fromStatusCode(status))
|
||||||
|
.entity(new ApiErrorMessage(error, message))
|
||||||
|
.type(MediaType.APPLICATION_JSON)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.status = status;
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package api;
|
package api;
|
||||||
|
|
||||||
import io.swagger.v3.jaxrs2.integration.OpenApiServlet;
|
|
||||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -12,70 +11,72 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|||||||
import org.glassfish.jersey.server.ResourceConfig;
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
|
|
||||||
|
|
||||||
import settings.Settings;
|
import settings.Settings;
|
||||||
|
|
||||||
public class ApiService {
|
public class ApiService {
|
||||||
private Server server;
|
|
||||||
private Set<Class<?>> resources;
|
|
||||||
|
|
||||||
public ApiService()
|
private final Server server;
|
||||||
{
|
private final Set<Class<?>> resources;
|
||||||
// resources to register
|
|
||||||
resources = new HashSet<Class<?>>();
|
public ApiService() {
|
||||||
resources.add(BlocksResource.class);
|
// resources to register
|
||||||
resources.add(OpenApiResource.class); // swagger
|
this.resources = new HashSet<Class<?>>();
|
||||||
ResourceConfig config = new ResourceConfig(resources);
|
this.resources.add(BlocksResource.class);
|
||||||
|
this.resources.add(OpenApiResource.class); // swagger
|
||||||
|
ResourceConfig config = new ResourceConfig(this.resources);
|
||||||
|
|
||||||
// create RPC server
|
// create RPC server
|
||||||
this.server = new Server(Settings.getInstance().getRpcPort());
|
this.server = new Server(Settings.getInstance().getRpcPort());
|
||||||
|
|
||||||
// whitelist
|
|
||||||
InetAccessHandler accessHandler = new InetAccessHandler();
|
|
||||||
for(String pattern : Settings.getInstance().getRpcAllowed())
|
|
||||||
accessHandler.include(pattern);
|
|
||||||
this.server.setHandler(accessHandler);
|
|
||||||
|
|
||||||
// context
|
|
||||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
|
||||||
context.setContextPath("/");
|
|
||||||
accessHandler.setHandler(context);
|
|
||||||
|
|
||||||
// API servlet
|
|
||||||
ServletContainer container = new ServletContainer(config);
|
|
||||||
ServletHolder apiServlet = new ServletHolder(container);
|
|
||||||
apiServlet.setInitOrder(1);
|
|
||||||
context.addServlet(apiServlet, "/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Class<?>> getResources()
|
|
||||||
{
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start()
|
// whitelist
|
||||||
{
|
InetAccessHandler accessHandler = new InetAccessHandler();
|
||||||
try
|
for (String pattern : Settings.getInstance().getRpcAllowed()) {
|
||||||
{
|
accessHandler.include(pattern);
|
||||||
//START RPC
|
}
|
||||||
server.start();
|
this.server.setHandler(accessHandler);
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
//FAILED TO START RPC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop()
|
// context
|
||||||
{
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||||
try
|
context.setContextPath("/");
|
||||||
{
|
accessHandler.setHandler(context);
|
||||||
//STOP RPC
|
|
||||||
server.stop();
|
// API servlet
|
||||||
}
|
ServletContainer container = new ServletContainer(config);
|
||||||
catch (Exception e)
|
ServletHolder apiServlet = new ServletHolder(container);
|
||||||
{
|
apiServlet.setInitOrder(1);
|
||||||
//FAILED TO STOP RPC
|
context.addServlet(apiServlet, "/*");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
//XXX: replace singleton pattern by dependency injection?
|
||||||
|
private static ApiService instance;
|
||||||
|
|
||||||
|
public static ApiService getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ApiService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Class<?>> getResources() {
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
try {
|
||||||
|
//START RPC
|
||||||
|
server.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
//FAILED TO START RPC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
//STOP RPC
|
||||||
|
server.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
//FAILED TO STOP RPC
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package api;
|
package api;
|
||||||
|
|
||||||
|
import globalization.Translator;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@ -10,7 +11,6 @@ import javax.ws.rs.GET;
|
|||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
@ -18,285 +18,295 @@ import repository.Repository;
|
|||||||
import repository.RepositoryManager;
|
import repository.RepositoryManager;
|
||||||
|
|
||||||
@Path("blocks")
|
@Path("blocks")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
public class BlocksResource {
|
public class BlocksResource {
|
||||||
@Context
|
|
||||||
HttpServletRequest request;
|
|
||||||
|
|
||||||
@GET
|
@Context
|
||||||
@Operation(
|
HttpServletRequest request;
|
||||||
description = "Returns an array of the 50 last blocks generated by your accounts",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The blocks"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Wallet does not exist"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getBlocks()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
private ApiErrorFactory apiErrorFactory;
|
||||||
}
|
|
||||||
|
|
||||||
@GET @Path("/address/{address}")
|
public BlocksResource() {
|
||||||
@Operation(
|
this(new ApiErrorFactory(new Translator()));
|
||||||
description = "Returns an array of the 50 last blocks generated by a specific address in your wallet",
|
}
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The blocks"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "400",
|
|
||||||
description = "Invalid address"
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Wallet does not exist"
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Address does not exist in wallet"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getBlocks(@PathParam("address") String address)
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/address/" + address, request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
public BlocksResource(ApiErrorFactory apiErrorFactory) {
|
||||||
}
|
this.apiErrorFactory = apiErrorFactory;
|
||||||
|
}
|
||||||
@GET @Path("/{signature}")
|
|
||||||
@Operation(
|
|
||||||
description = "Returns the block that matches the given signature",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The block"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "400",
|
|
||||||
description = "Invalid signature"
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Block does not exist"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getBlock(@PathParam("signature") String signature)
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Operation(
|
||||||
|
description = "Returns an array of the 50 last blocks generated by your accounts",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The blocks"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Wallet does not exist"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getBlocks() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks", request);
|
||||||
|
|
||||||
@GET @Path("/first")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Returns the genesis block",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The block"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getFirstBlock()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/first", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/address/{address}")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns an array of the 50 last blocks generated by a specific address in your wallet",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The blocks"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "Invalid address"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Wallet does not exist"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Address does not exist in wallet"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getBlocks(@PathParam("address") String address) {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/address/" + address, request);
|
||||||
|
|
||||||
@GET @Path("/last")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Returns the last valid block",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The block"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getLastBlock()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/last", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/{signature}")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns the block that matches the given signature",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The block"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "Invalid signature"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Block does not exist"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getBlock(@PathParam("signature") String signature) {
|
||||||
|
Security.checkApiCallAllowed("GET blocks", request);
|
||||||
|
|
||||||
@GET @Path("/child/{signature}")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Returns the child block of the block that matches the given signature",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The block"
|
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "400",
|
|
||||||
description = "Invalid signature"
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Block does not exist"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public String getChild(@PathParam("signature") String signature)
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/child", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/first")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns the genesis block",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The block"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getFirstBlock() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/first", request);
|
||||||
|
|
||||||
@GET @Path("/generatingbalance")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Calculates the generating balance of the block that will follow the last block",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The generating balance",
|
|
||||||
content = @Content(schema = @Schema(implementation = long.class))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public long getGeneratingBalance()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/last")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns the last valid block",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The block"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getLastBlock() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/last", request);
|
||||||
|
|
||||||
@GET @Path("/generatingbalance/{signature}")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Calculates the generating balance of the block that will follow the block that matches the signature",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The block",
|
|
||||||
content = @Content(schema = @Schema(implementation = long.class))
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "400",
|
|
||||||
description = "Invalid signature"
|
|
||||||
),
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "422",
|
|
||||||
description = "Block does not exist"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public long getGeneratingBalance(@PathParam("signature") String signature)
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/child/{signature}")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns the child block of the block that matches the given signature",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The block"
|
||||||
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "Invalid signature"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Block does not exist"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getChild(@PathParam("signature") String signature) {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/child", request);
|
||||||
|
|
||||||
@GET @Path("/time")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Calculates the time it should take for the network to generate the next block",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The time", // in seconds?
|
|
||||||
content = @Content(schema = @Schema(implementation = long.class))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public long getTimePerBlock()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/time", request);
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
@GET
|
||||||
}
|
@Path("/generatingbalance")
|
||||||
|
@Operation(
|
||||||
|
description = "Calculates the generating balance of the block that will follow the last block",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The generating balance",
|
||||||
|
content = @Content(schema = @Schema(implementation = long.class))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public long getGeneratingBalance() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
||||||
|
|
||||||
@GET @Path("/time/{generatingbalance}")
|
throw new UnsupportedOperationException();
|
||||||
@Operation(
|
}
|
||||||
description = "Calculates the time it should take for the network to generate blocks when the current generating balance in the network is the specified generating balance",
|
|
||||||
responses = {
|
@GET
|
||||||
@ApiResponse(
|
@Path("/generatingbalance/{signature}")
|
||||||
description = "The time", // in seconds?
|
@Operation(
|
||||||
content = @Content(schema = @Schema(implementation = long.class))
|
description = "Calculates the generating balance of the block that will follow the block that matches the signature",
|
||||||
)
|
responses = {
|
||||||
}
|
@ApiResponse(
|
||||||
)
|
description = "The block",
|
||||||
public String getTimePerBlock(@PathParam("generating") long generatingbalance)
|
content = @Content(schema = @Schema(implementation = long.class))
|
||||||
{
|
),
|
||||||
Security.checkApiCallAllowed("GET blocks/time", request);
|
@ApiResponse(
|
||||||
|
responseCode = "400",
|
||||||
|
description = "Invalid signature"
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "422",
|
||||||
|
description = "Block does not exist"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public long getGeneratingBalance(@PathParam("signature") String signature) {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/time")
|
||||||
|
@Operation(
|
||||||
|
description = "Calculates the time it should take for the network to generate the next block",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The time", // in seconds?
|
||||||
|
content = @Content(schema = @Schema(implementation = long.class))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public long getTimePerBlock() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/time", request);
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/time/{generatingbalance}")
|
||||||
|
@Operation(
|
||||||
|
description = "Calculates the time it should take for the network to generate blocks when the current generating balance in the network is the specified generating balance",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The time", // in seconds?
|
||||||
|
content = @Content(schema = @Schema(implementation = long.class))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public String getTimePerBlock(@PathParam("generating") long generatingbalance) {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/time", request);
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/height")
|
||||||
|
@Operation(
|
||||||
|
description = "Returns the block height of the last block.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "The height",
|
||||||
|
content = @Content(schema = @Schema(implementation = int.class))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public int getHeight() {
|
||||||
|
Security.checkApiCallAllowed("GET blocks/height", request);
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET @Path("/height")
|
|
||||||
@Operation(
|
|
||||||
description = "Returns the block height of the last block.",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "The height",
|
|
||||||
content = @Content(schema = @Schema(implementation = int.class))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public int getHeight()
|
|
||||||
{
|
|
||||||
Security.checkApiCallAllowed("GET blocks/height", request);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getBlockRepository().getBlockchainHeight();
|
return repository.getBlockRepository().getBlockchainHeight();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new WebApplicationException(e);
|
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET @Path("/height/{signature}")
|
@GET
|
||||||
@Operation(
|
@Path("/height/{signature}")
|
||||||
description = "Returns the block height of the block that matches the given signature",
|
@Operation(
|
||||||
responses = {
|
description = "Returns the block height of the block that matches the given signature",
|
||||||
@ApiResponse(
|
responses = {
|
||||||
description = "The height",
|
@ApiResponse(
|
||||||
content = @Content(schema = @Schema(implementation = int.class))
|
description = "The height",
|
||||||
),
|
content = @Content(schema = @Schema(implementation = int.class))
|
||||||
@ApiResponse(
|
),
|
||||||
responseCode = "400",
|
@ApiResponse(
|
||||||
description = "Invalid signature"
|
responseCode = "400",
|
||||||
),
|
description = "Invalid signature"
|
||||||
@ApiResponse(
|
),
|
||||||
responseCode = "422",
|
@ApiResponse(
|
||||||
description = "Block does not exist"
|
responseCode = "422",
|
||||||
)
|
description = "Block does not exist"
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
public int getHeight(@PathParam("signature") String signature)
|
)
|
||||||
{
|
public int getHeight(@PathParam("signature") String signature) {
|
||||||
Security.checkApiCallAllowed("GET blocks/height", request);
|
Security.checkApiCallAllowed("GET blocks/height", request);
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET @Path("/byheight/{height}")
|
@GET
|
||||||
@Operation(
|
@Path("/byheight/{height}")
|
||||||
description = "Returns the block whith given height",
|
@Operation(
|
||||||
responses = {
|
description = "Returns the block whith given height",
|
||||||
@ApiResponse(
|
responses = {
|
||||||
description = "The block"
|
@ApiResponse(
|
||||||
//content = @Content(schema = @Schema(implementation = ???))
|
description = "The block"
|
||||||
),
|
//content = @Content(schema = @Schema(implementation = ???))
|
||||||
@ApiResponse(
|
),
|
||||||
responseCode = "422",
|
@ApiResponse(
|
||||||
description = "Block does not exist"
|
responseCode = "422",
|
||||||
)
|
description = "Block does not exist"
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
public String getbyHeight(@PathParam("height") int height)
|
)
|
||||||
{
|
public String getbyHeight(@PathParam("height") int height) {
|
||||||
Security.checkApiCallAllowed("GET blocks/byheight", request);
|
Security.checkApiCallAllowed("GET blocks/byheight", request);
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package api;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
public class Security {
|
public class Security {
|
||||||
public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request)
|
|
||||||
{
|
public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
src/globalization/Translator.java
Normal file
52
src/globalization/Translator.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package globalization;
|
||||||
|
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.commons.text.StringSubstitutor;
|
||||||
|
|
||||||
|
public class Translator {
|
||||||
|
|
||||||
|
private Map<String, Object> createMap(Map.Entry<String, Object>[] entries) {
|
||||||
|
HashMap<String, Object> map = new HashMap<>();
|
||||||
|
for (AbstractMap.Entry<String, Object> entry : entries) {
|
||||||
|
map.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
//XXX: replace singleton pattern by dependency injection?
|
||||||
|
private static Translator instance;
|
||||||
|
|
||||||
|
public static Translator getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Translator();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(Locale locale, String templateKey, AbstractMap.Entry<String, Object>... templateValues) {
|
||||||
|
Map<String, Object> map = createMap(templateValues);
|
||||||
|
return translate(locale, templateKey, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(Locale locale, String templateKey, Map<String, Object> templateValues) {
|
||||||
|
return translate(locale, templateKey, null, templateValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(Locale locale, String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
|
||||||
|
Map<String, Object> map = createMap(templateValues);
|
||||||
|
return translate(locale, templateKey, defaultTemplate, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(Locale locale, String templateKey, String defaultTemplate, Map<String, Object> templateValues) {
|
||||||
|
String template = defaultTemplate; // TODO: get template for the given locale if available
|
||||||
|
|
||||||
|
StringSubstitutor sub = new StringSubstitutor(templateValues);
|
||||||
|
String result = sub.replace(template);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user