diff --git a/pom.xml b/pom.xml
index d1773583..1154fb5d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,5 +90,15 @@
swagger-jaxrs2-servlet-initializer
2.0.4
+
+ org.apache.commons
+ commons-text
+ 1.4
+
+
+ org.glassfish.jersey.media
+ jersey-media-moxy
+ 2.27
+
\ No newline at end of file
diff --git a/src/Start.java b/src/Start.java
index 9b0fce81..585f65c6 100644
--- a/src/Start.java
+++ b/src/Start.java
@@ -6,20 +6,20 @@ import repository.RepositoryFactory;
import repository.RepositoryManager;
import repository.hsqldb.HSQLDBRepositoryFactory;
-
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
- {
- RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
- RepositoryManager.setRepositoryFactory(repositoryFactory);
-
- ApiService apiService = new ApiService();
- apiService.start();
-
- ApiClient client = new ApiClient(apiService);
- String test = client.executeCommand("help ALL");
- System.out.println(test);
- }
+ 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 {
+ RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
+ RepositoryManager.setRepositoryFactory(repositoryFactory);
+
+ ApiService apiService = ApiService.getInstance();
+ apiService.start();
+
+ //// testing the API client
+ //ApiClient client = ApiClient.getInstance();
+ //String test = client.executeCommand("GET blocks/height");
+ //System.out.println(test);
+ }
}
diff --git a/src/api/ApiClient.java b/src/api/ApiClient.java
index 0d989215..49d9cd8a 100644
--- a/src/api/ApiClient.java
+++ b/src/api/ApiClient.java
@@ -1,5 +1,6 @@
package api;
+import globalization.Translator;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.annotations.Operation;
import java.lang.annotation.Annotation;
@@ -7,6 +8,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.Path;
@@ -16,133 +18,184 @@ import javax.ws.rs.PUT;
import javax.ws.rs.PATCH;
import javax.ws.rs.DELETE;
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 {
- 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 *(?.*)$", Pattern.CASE_INSENSITIVE);
- private static final List> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
- GET.class,
- POST.class,
- PUT.class,
- PATCH.class,
- DELETE.class
- );
-
- ApiService apiService;
- List helpStrings;
-
- public ApiClient(ApiService apiService)
- {
- this.apiService = apiService;
- this.helpStrings = getHelpStrings(apiService.getResources());
- }
- private List getHelpStrings(Iterable> resources)
- {
- List 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() : "";
+ private class HelpString {
- // scan for each potential http method
- for(Class extends Annotation> restMethodAnnotation : HTTP_METHOD_ANNOTATIONS)
- {
- Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation);
- if(annotation == null)
- continue;
+ public final Pattern pattern;
+ public final String fullPath;
+ public final String description;
- HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
- String httpMethodString = httpMethod.value();
+ public HelpString(Pattern pattern, String fullPath, String description) {
+ this.pattern = pattern;
+ this.fullPath = fullPath;
+ this.description = description;
+ }
+ }
- 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 ".*?"
+ private static final Pattern COMMAND_PATTERN = Pattern.compile("^ *(?GET|POST|PUT|PATCH|DELETE) *(?.*)$");
+ private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?.*)$", Pattern.CASE_INSENSITIVE);
+ private static final List> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
+ GET.class,
+ POST.class,
+ PUT.class,
+ PATCH.class,
+ DELETE.class
+ );
- // 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)
- {
- 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;
- }
+ ApiService apiService;
+ private Translator translator;
+ List helpStrings;
- private void appendHelp(StringBuilder builder, HelpString helpString) {
- builder.append(helpString.fullPath + "\n");
- builder.append(helpString.description + "\n");
- }
+ public ApiClient(ApiService apiService, Translator translator) {
+ this.apiService = apiService;
+ 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 getHelpStrings(Iterable> resources) {
+ List 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");
+ }
}
diff --git a/src/api/ApiError.java b/src/api/ApiError.java
new file mode 100644
index 00000000..471fa9f2
--- /dev/null
+++ b/src/api/ApiError.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/api/ApiErrorFactory.java b/src/api/ApiErrorFactory.java
new file mode 100644
index 00000000..ad7c7f7e
--- /dev/null
+++ b/src/api/ApiErrorFactory.java
@@ -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[] templateValues;
+
+ public ErrorMessageEntry(String templateKey, String defaultTemplate, AbstractMap.Entry... templateValues) {
+ this.templateKey = templateKey;
+ this.defaultTemplate = defaultTemplate;
+ this.templateValues = templateValues;
+ }
+ }
+
+ private Translator translator;
+ private Map errorMessages;
+
+ public ApiErrorFactory(Translator translator) {
+ this.translator = translator;
+
+ this.errorMessages = new HashMap();
+
+ //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("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("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("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... 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);
+ }
+}
diff --git a/src/api/ApiErrorMessage.java b/src/api/ApiErrorMessage.java
new file mode 100644
index 00000000..0c84fe4a
--- /dev/null
+++ b/src/api/ApiErrorMessage.java
@@ -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;
+ }
+}
diff --git a/src/api/ApiException.java b/src/api/ApiException.java
new file mode 100644
index 00000000..b8419cc7
--- /dev/null
+++ b/src/api/ApiException.java
@@ -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;
+ }
+}
diff --git a/src/api/ApiService.java b/src/api/ApiService.java
index a0e7bd26..afa236ec 100644
--- a/src/api/ApiService.java
+++ b/src/api/ApiService.java
@@ -1,6 +1,5 @@
package api;
-import io.swagger.v3.jaxrs2.integration.OpenApiServlet;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import java.util.HashSet;
import java.util.Set;
@@ -12,70 +11,72 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
-
import settings.Settings;
public class ApiService {
- private Server server;
- private Set> resources;
- public ApiService()
- {
- // resources to register
- resources = new HashSet>();
- resources.add(BlocksResource.class);
- resources.add(OpenApiResource.class); // swagger
- ResourceConfig config = new ResourceConfig(resources);
+ private final Server server;
+ private final Set> resources;
+
+ public ApiService() {
+ // resources to register
+ this.resources = new HashSet>();
+ this.resources.add(BlocksResource.class);
+ this.resources.add(OpenApiResource.class); // swagger
+ ResourceConfig config = new ResourceConfig(this.resources);
- // create RPC server
- 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> getResources()
- {
- return resources;
- }
+ // create RPC server
+ this.server = new Server(Settings.getInstance().getRpcPort());
- public void start()
- {
- try
- {
- //START RPC
- server.start();
- }
- catch (Exception e)
- {
- //FAILED TO START RPC
- }
- }
+ // whitelist
+ InetAccessHandler accessHandler = new InetAccessHandler();
+ for (String pattern : Settings.getInstance().getRpcAllowed()) {
+ accessHandler.include(pattern);
+ }
+ this.server.setHandler(accessHandler);
- public void stop()
- {
- try
- {
- //STOP RPC
- server.stop();
- }
- catch (Exception e)
- {
- //FAILED TO STOP RPC
- }
- }
+ // 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, "/*");
+ }
+
+ //XXX: replace singleton pattern by dependency injection?
+ private static ApiService instance;
+
+ public static ApiService getInstance() {
+ if (instance == null) {
+ instance = new ApiService();
+ }
+
+ return instance;
+ }
+
+ Iterable> 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
+ }
+ }
}
diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java
index 56903d23..ae79734d 100644
--- a/src/api/BlocksResource.java
+++ b/src/api/BlocksResource.java
@@ -1,5 +1,6 @@
package api;
+import globalization.Translator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
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.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@@ -18,285 +18,295 @@ import repository.Repository;
import repository.RepositoryManager;
@Path("blocks")
-@Produces(MediaType.APPLICATION_JSON)
+@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public class BlocksResource {
- @Context
- HttpServletRequest request;
- @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);
+ @Context
+ HttpServletRequest request;
- throw new UnsupportedOperationException();
- }
+ private ApiErrorFactory apiErrorFactory;
- @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);
+ public BlocksResource() {
+ this(new ApiErrorFactory(new Translator()));
+ }
- 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);
+ public BlocksResource(ApiErrorFactory apiErrorFactory) {
+ this.apiErrorFactory = apiErrorFactory;
+ }
- 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")
- @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();
+ }
- 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")
- @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();
+ }
- 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}")
- @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();
+ }
- 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")
- @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();
+ }
- 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}")
- @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();
+ }
- 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")
- @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();
+ }
- 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}")
- @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("/generatingbalance/{signature}")
+ @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("/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()) {
return repository.getBlockRepository().getBlockchainHeight();
} catch (Exception e) {
- throw new WebApplicationException(e);
+ throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
}
- }
+ }
- @GET @Path("/height/{signature}")
- @Operation(
- description = "Returns the block height of the block that matches the given signature",
- responses = {
- @ApiResponse(
- description = "The height",
- content = @Content(schema = @Schema(implementation = int.class))
- ),
- @ApiResponse(
- responseCode = "400",
- description = "Invalid signature"
- ),
- @ApiResponse(
- responseCode = "422",
- description = "Block does not exist"
- )
- }
- )
- public int getHeight(@PathParam("signature") String signature)
- {
- Security.checkApiCallAllowed("GET blocks/height", request);
+ @GET
+ @Path("/height/{signature}")
+ @Operation(
+ description = "Returns the block height of the block that matches the given signature",
+ responses = {
+ @ApiResponse(
+ description = "The height",
+ content = @Content(schema = @Schema(implementation = int.class))
+ ),
+ @ApiResponse(
+ responseCode = "400",
+ description = "Invalid signature"
+ ),
+ @ApiResponse(
+ responseCode = "422",
+ description = "Block does not exist"
+ )
+ }
+ )
+ public int getHeight(@PathParam("signature") String signature) {
+ Security.checkApiCallAllowed("GET blocks/height", request);
- throw new UnsupportedOperationException();
- }
+ throw new UnsupportedOperationException();
+ }
- @GET @Path("/byheight/{height}")
- @Operation(
- description = "Returns the block whith given height",
- responses = {
- @ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
- ),
- @ApiResponse(
- responseCode = "422",
- description = "Block does not exist"
- )
- }
- )
- public String getbyHeight(@PathParam("height") int height)
- {
- Security.checkApiCallAllowed("GET blocks/byheight", request);
+ @GET
+ @Path("/byheight/{height}")
+ @Operation(
+ description = "Returns the block whith given height",
+ responses = {
+ @ApiResponse(
+ description = "The block"
+ //content = @Content(schema = @Schema(implementation = ???))
+ ),
+ @ApiResponse(
+ responseCode = "422",
+ description = "Block does not exist"
+ )
+ }
+ )
+ public String getbyHeight(@PathParam("height") int height) {
+ Security.checkApiCallAllowed("GET blocks/byheight", request);
- throw new UnsupportedOperationException();
- }
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/api/Security.java b/src/api/Security.java
index 30a0333f..d05a41af 100644
--- a/src/api/Security.java
+++ b/src/api/Security.java
@@ -3,8 +3,8 @@ package api;
import javax.servlet.http.HttpServletRequest;
public class Security {
- public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request)
- {
- // TODO
- }
+
+ public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request) {
+ // TODO
+ }
}
diff --git a/src/globalization/Translator.java b/src/globalization/Translator.java
new file mode 100644
index 00000000..da61c595
--- /dev/null
+++ b/src/globalization/Translator.java
@@ -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 createMap(Map.Entry[] entries) {
+ HashMap map = new HashMap<>();
+ for (AbstractMap.Entry 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... templateValues) {
+ Map map = createMap(templateValues);
+ return translate(locale, templateKey, map);
+ }
+
+ public String translate(Locale locale, String templateKey, Map templateValues) {
+ return translate(locale, templateKey, null, templateValues);
+ }
+
+ public String translate(Locale locale, String templateKey, String defaultTemplate, AbstractMap.Entry... templateValues) {
+ Map map = createMap(templateValues);
+ return translate(locale, templateKey, defaultTemplate, map);
+ }
+
+ public String translate(Locale locale, String templateKey, String defaultTemplate, Map 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;
+ }
+}