mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-16 20:22:32 +00:00
CHANGED: integrated Swagger/OpenApi
CHANGED: added method stubs and describing annotations to BlocksResource
This commit is contained in:
parent
4f279fc616
commit
19a9a3a98b
10
pom.xml
10
pom.xml
@ -80,5 +80,15 @@
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 *(?<command>.*)$", Pattern.CASE_INSENSITIVE);
|
||||
private static final List<Class<? extends Annotation>> REST_METHOD_ANNOTATIONS = Arrays.asList(
|
||||
private static final List<Class<? extends Annotation>> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
|
||||
GET.class,
|
||||
POST.class,
|
||||
PUT.class,
|
||||
@ -54,25 +54,31 @@ public class ApiClient {
|
||||
{
|
||||
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())
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -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<Class<?>>();
|
||||
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<Class<?>> getResources()
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
10
src/api/Security.java
Normal file
10
src/api/Security.java
Normal file
@ -0,0 +1,10 @@
|
||||
package api;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class Security {
|
||||
public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user