diff --git a/src/Start.java b/src/Start.java index 99632030..8ab1825d 100644 --- a/src/Start.java +++ b/src/Start.java @@ -1,4 +1,5 @@ +import api.ApiClient; import api.ApiService; import repository.DataException; import repository.RepositoryFactory; @@ -16,5 +17,8 @@ public class Start { ApiService apiService = new ApiService(); apiService.start(); + + ApiClient client = new ApiClient(apiService); + String test = client.executeCommand("help GET blocks/height"); } } diff --git a/src/api/ApiClient.java b/src/api/ApiClient.java new file mode 100644 index 00000000..174d51f2 --- /dev/null +++ b/src/api/ApiClient.java @@ -0,0 +1,123 @@ +package api; + +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; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.PATCH; +import javax.ws.rs.DELETE; +import javax.ws.rs.HttpMethod; + +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> REST_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<>(); + + for (Class resource : resources) { + Path resourcePath = resource.getDeclaredAnnotation(Path.class); + if(resourcePath == null) + continue; + + String resourcePathString = resourcePath.value(); + + for(Method method : resource.getDeclaredMethods()) + { + UsageDescription usageDescription = method.getAnnotation(UsageDescription.class); + if(usageDescription == null) + continue; + + String usageDescriptionString = usageDescription.value(); + + Path methodPath = method.getDeclaredAnnotation(Path.class); + String methodPathString = (methodPath != null) ? methodPath.value() : ""; + + for(Class restMethodAnnotation : REST_METHOD_ANNOTATIONS) + { + Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation); + if(annotation == null) + continue; + + 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)); + } + } + } + + return result; + } + + private String getRegexPatternForPath(String path) + { + return path + .replaceAll("\\.", "\\.") // escapes "." as "\." + .replaceAll("\\{.*?\\}", ".*?"); // replace placeholders "{...}" by the "ungreedy match anything" pattern ".*?" + } + + public String executeCommand(String command) + { + final Matcher helpMatch = HELP_COMMAND_PATTERN.matcher(command); + if(helpMatch.matches()) + { + command = helpMatch.group("command"); + StringBuilder help = new StringBuilder(); + + for(HelpString helpString : helpStrings) + { + if(helpString.pattern.matcher(command).matches()) + { + help.append(helpString.fullPath + "\n"); + help.append(helpString.description + "\n"); + } + } + + return help.toString(); + } + + return null; + } +} diff --git a/src/api/ApiService.java b/src/api/ApiService.java index e523a7d6..f14fd239 100644 --- a/src/api/ApiService.java +++ b/src/api/ApiService.java @@ -17,13 +17,14 @@ import settings.Settings; public class ApiService { private Server server; + private Set> resources; public ApiService() { // resources to register - Set> s = new HashSet>(); - s.add(BlocksResource.class); - ResourceConfig config = new ResourceConfig(s); + resources = new HashSet>(); + resources.add(BlocksResource.class); + ResourceConfig config = new ResourceConfig(resources); // create RPC server this.server = new Server(Settings.getInstance().getRpcPort()); @@ -44,14 +45,11 @@ public class ApiService { ServletHolder apiServlet = new ServletHolder(container); apiServlet.setInitOrder(1); context.addServlet(apiServlet, "/api/*"); - - /* - // Setup Swagger servlet - ServletHolder swaggerServlet = context.addServlet(DefaultJaxrsConfig.class, "/swagger-core"); - swaggerServlet.setInitOrder(2); - swaggerServlet.setInitParameter("api.version", "1.0.0"); - */ - + } + + Iterable> getResources() + { + return resources; } public void start() diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java index d46744a2..8b8e3850 100644 --- a/src/api/BlocksResource.java +++ b/src/api/BlocksResource.java @@ -21,6 +21,7 @@ public class BlocksResource { @GET @Path("/height") + @UsageDescription("Returns the height of the blockchain") public static String getHeight() { try (final Repository repository = RepositoryManager.getRepository()) { diff --git a/src/api/UsageDescription.java b/src/api/UsageDescription.java new file mode 100644 index 00000000..e7f100fb --- /dev/null +++ b/src/api/UsageDescription.java @@ -0,0 +1,14 @@ +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