diff --git a/pom.xml b/pom.xml
index a4c754e2..d1773583 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,5 +80,15 @@
jersey-hk2
2.27
+
+ io.swagger.core.v3
+ swagger-jaxrs2
+ 2.0.4
+
+
+ io.swagger.core.v3
+ swagger-jaxrs2-servlet-initializer
+ 2.0.4
+
\ No newline at end of file
diff --git a/src/Start.java b/src/Start.java
index 8ab1825d..9b0fce81 100644
--- a/src/Start.java
+++ b/src/Start.java
@@ -19,6 +19,7 @@ public class Start {
apiService.start();
ApiClient client = new ApiClient(apiService);
- String test = client.executeCommand("help GET blocks/height");
+ String test = client.executeCommand("help ALL");
+ System.out.println(test);
}
}
diff --git a/src/api/ApiClient.java b/src/api/ApiClient.java
index 174d51f2..0d989215 100644
--- a/src/api/ApiClient.java
+++ b/src/api/ApiClient.java
@@ -1,12 +1,12 @@
package api;
+import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
+import io.swagger.v3.oas.annotations.Operation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.Path;
@@ -33,7 +33,7 @@ public class ApiClient {
}
private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?.*)$", Pattern.CASE_INSENSITIVE);
- private static final List> REST_METHOD_ANNOTATIONS = Arrays.asList(
+ private static final List> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
GET.class,
POST.class,
PUT.class,
@@ -54,25 +54,31 @@ public class ApiClient {
{
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())
{
- UsageDescription usageDescription = method.getAnnotation(UsageDescription.class);
- if(usageDescription == null)
+ Operation operationAnnotation = method.getAnnotation(Operation.class);
+ if(operationAnnotation == null)
continue;
- String usageDescriptionString = usageDescription.value();
+ String description = operationAnnotation.description();
Path methodPath = method.getDeclaredAnnotation(Path.class);
String methodPathString = (methodPath != null) ? methodPath.value() : "";
- for(Class extends Annotation> restMethodAnnotation : REST_METHOD_ANNOTATIONS)
+ // scan for each potential http method
+ for(Class extends Annotation> restMethodAnnotation : HTTP_METHOD_ANNOTATIONS)
{
Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation);
if(annotation == null)
@@ -81,21 +87,37 @@ public class ApiClient {
HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
String httpMethodString = httpMethod.value();
- Pattern pattern = Pattern.compile("^ *" + httpMethodString + " *" + getRegexPatternForPath(resourcePathString + methodPathString));
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
- result.add(new HelpString(pattern, fullPath, usageDescriptionString));
+ 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 getRegexPatternForPath(String path)
- {
- return path
- .replaceAll("\\.", "\\.") // escapes "." as "\."
- .replaceAll("\\{.*?\\}", ".*?"); // replace placeholders "{...}" by the "ungreedy match anything" pattern ".*?"
+ 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)
@@ -104,20 +126,23 @@ public class ApiClient {
if(helpMatch.matches())
{
command = helpMatch.group("command");
- StringBuilder help = new StringBuilder();
-
+ StringBuilder result = new StringBuilder();
+
+ boolean showAll = command.trim().equalsIgnoreCase("all");
for(HelpString helpString : helpStrings)
{
- if(helpString.pattern.matcher(command).matches())
- {
- help.append(helpString.fullPath + "\n");
- help.append(helpString.description + "\n");
- }
+ if(showAll || helpString.pattern.matcher(command).matches())
+ appendHelp(result, helpString);
}
- return help.toString();
+ return result.toString();
}
return null;
}
+
+ private void appendHelp(StringBuilder builder, HelpString helpString) {
+ builder.append(helpString.fullPath + "\n");
+ builder.append(helpString.description + "\n");
+ }
}
diff --git a/src/api/ApiService.java b/src/api/ApiService.java
index f14fd239..a0e7bd26 100644
--- a/src/api/ApiService.java
+++ b/src/api/ApiService.java
@@ -1,7 +1,7 @@
package api;
-//import io.swagger.jaxrs.config.DefaultJaxrsConfig;
-
+import io.swagger.v3.jaxrs2.integration.OpenApiServlet;
+import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import java.util.HashSet;
import java.util.Set;
@@ -24,6 +24,7 @@ public class ApiService {
// resources to register
resources = new HashSet>();
resources.add(BlocksResource.class);
+ resources.add(OpenApiResource.class); // swagger
ResourceConfig config = new ResourceConfig(resources);
// create RPC server
@@ -44,7 +45,7 @@ public class ApiService {
ServletContainer container = new ServletContainer(config);
ServletHolder apiServlet = new ServletHolder(container);
apiServlet.setInitOrder(1);
- context.addServlet(apiServlet, "/api/*");
+ context.addServlet(apiServlet, "/*");
}
Iterable> getResources()
diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java
index 8b8e3850..56903d23 100644
--- a/src/api/BlocksResource.java
+++ b/src/api/BlocksResource.java
@@ -1,15 +1,19 @@
package api;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import javax.servlet.http.HttpServletRequest;
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;
-import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
@@ -20,15 +24,279 @@ public class BlocksResource {
HttpServletRequest request;
@GET
- @Path("/height")
- @UsageDescription("Returns the height of the blockchain")
- public static String getHeight()
+ @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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
try (final Repository repository = RepositoryManager.getRepository()) {
- return String.valueOf(repository.getBlockRepository().getBlockchainHeight());
+ return repository.getBlockRepository().getBlockchainHeight();
} catch (Exception e) {
- throw new WebApplicationException("What happened?");
+ throw new WebApplicationException(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);
+
+ 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);
+
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/api/Security.java b/src/api/Security.java
new file mode 100644
index 00000000..30a0333f
--- /dev/null
+++ b/src/api/Security.java
@@ -0,0 +1,10 @@
+package api;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class Security {
+ public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request)
+ {
+ // TODO
+ }
+}
diff --git a/src/api/UsageDescription.java b/src/api/UsageDescription.java
deleted file mode 100644
index e7f100fb..00000000
--- a/src/api/UsageDescription.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package api;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target(value={ElementType.TYPE,ElementType.METHOD})
-@Retention(value=RetentionPolicy.RUNTIME)
-@Documented
-public abstract @interface UsageDescription {
- public abstract String value();
-}
\ No newline at end of file