diff --git a/README.md b/README.md new file mode 100644 index 0000000..426ff74 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# PROTEUS + +An extremely lightweight, flexible and fast [Swagger](http://swagger.io/) first REST API framework atop [Undertow](http://undertow.io). +A great deal of inspiration came from working with the following excellent projects: [Play](http://playframework.com), [Jooby](http://jooby.org), and [light-4j](https://github.com/networknt/light-4j). + +### Motivation + - Several years of working with the [Play](http://playframework.com) framework convinced us there had to be a better way. + - We faced a Goldilocks Crisis with the existing alternatives: [Jooby](http://jooby.org) did too much, [light-4j](https://github.com/networknt/light-4j) didn't do quite enough. + - We needed a framework that enabled us to write clean MVC REST controllers that created Swagger docs we could plug directly into the existing [codegen](https://github.com/swagger-api/swagger-codegen) solutions. + - We needed a framework with minimal overhead and performance at or near that of raw [Undertow](http://undertow.io). + +### Built With + - [Undertow](http://undertow.io) (server) + - [Guice](https://github.com/google/guice) (di) + - [Java Runtime Compiler](https://github.com/OpenHFT/Java-Runtime-Compiler) (runtime generated class compilation) + - [javapoet](https://github.com/square/javapoet) (runtime class generation) + - [Jackson](https://github.com/FasterXML/jackson-dataformat-xml) (xml) + - [jsoniter](http://jsoniter.com/) (json) + - [Logback](https://logback.qos.ch/) (logging) + - [Typesafe Config](https://github.com/typesafehub/config) (config) + - [Swagger](http://swagger.io/) (annotations and swagger spec) + - [jax-rs](http://docs.oracle.com/javaee/6/api/javax/ws/rs/package-summary.html) (annotations only) + +### Dependencies +* [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +* [Maven 3](http://maven.apache.org/) + +### Setup + - We are very impressed by what Jooby has done with server configuration + - Parameters are all configured in the ```conf/application.conf``` file + - Proteus applications generally have a main method that creates an instance of ```io.sinistral.proteus.Application``` + - The user adds ```Service``` and ```Module``` classes to the application instance via ```addService``` and ```addModule``` methods prior to calling ```start``` + +### Getting Started + - COMING SOON + +### Running + + mvn exec:exec + + diff --git a/pom.xml b/pom.xml index 63e77f9..8f71b83 100644 --- a/pom.xml +++ b/pom.xml @@ -282,11 +282,6 @@ - - org.javassist - javassist - 3.22.0-CR1 - io.swagger @@ -323,11 +318,6 @@ org.fusesource.jansi jansi 1.15 - - - com.graphql-java - graphql-java - 2.3.0 io.sinistral diff --git a/src/main/java/io/sinistral/proteus/ProteusApplication.java b/src/main/java/io/sinistral/proteus/ProteusApplication.java index 011cc05..1830c11 100644 --- a/src/main/java/io/sinistral/proteus/ProteusApplication.java +++ b/src/main/java/io/sinistral/proteus/ProteusApplication.java @@ -36,7 +36,7 @@ import io.sinistral.proteus.modules.ConfigModule; import io.sinistral.proteus.server.endpoints.EndpointInfo; -import io.sinistral.proteus.server.handlers.DefaultHttpHandler; +import io.sinistral.proteus.server.handlers.ServerDefaultHttpHandler; import io.sinistral.proteus.server.handlers.HandlerGenerator; import io.sinistral.proteus.services.AssetsService; import io.sinistral.proteus.services.SwaggerService; @@ -116,8 +116,8 @@ public void start() if( rootHandlerClass == null && rootHandler == null ) { - log.warn("No root handler class or root HttpHandler was specified, using default DefaultHttpHandler."); - rootHandlerClass = DefaultHttpHandler.class; + log.warn("No root handler class or root HttpHandler was specified, using default ServerDefaultHttpHandler."); + rootHandlerClass = ServerDefaultHttpHandler.class; } log.info("Starting services..."); @@ -350,7 +350,7 @@ public static void main(String[] args) app.addService(AssetsService.class); - app.setRootHandlerClass(DefaultHttpHandler.class); + app.setRootHandlerClass(ServerDefaultHttpHandler.class); app.start(); diff --git a/src/main/java/io/sinistral/proteus/annotations/Chain.java b/src/main/java/io/sinistral/proteus/annotations/Chain.java new file mode 100644 index 0000000..40b2f67 --- /dev/null +++ b/src/main/java/io/sinistral/proteus/annotations/Chain.java @@ -0,0 +1,26 @@ +/** + * + */ +package io.sinistral.proteus.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import io.undertow.server.HandlerWrapper; + +@Retention(RUNTIME) +@Target({ TYPE, METHOD }) +/** + * @author jbauer + * + */ +public @interface Chain +{ + Class[] value(); +} + + \ No newline at end of file diff --git a/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java b/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java index 3246ce4..1761851 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java @@ -54,6 +54,7 @@ import io.sinistral.proteus.server.ServerResponse; import io.sinistral.proteus.server.endpoints.EndpointInfo; import io.swagger.annotations.Api; +import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; @@ -750,7 +751,7 @@ else if(t.equals(HttpServerExchange.class) || t.equals(ServerRequest.class)) endpointInfo.setControllerMethod( m.getName()); - String methodName = String.format("%c%s%sHandler", Character.toLowerCase(clazz.getSimpleName().charAt(0)), clazz.getSimpleName().substring(1, clazz.getSimpleName().length()), StringUtils.capitalize(m.getName())); + String handlerName = String.format("%c%s%sHandler", Character.toLowerCase(clazz.getSimpleName().charAt(0)), clazz.getSimpleName().substring(1, clazz.getSimpleName().length()), StringUtils.capitalize(m.getName())); TypeSpec.Builder handlerClassBuilder = TypeSpec.anonymousClassBuilder("").addSuperinterface(httpHandlerClass); @@ -1025,12 +1026,43 @@ else if(producesContentType.contains(MediaType.TEXT_HTML)) - FieldSpec handlerField = FieldSpec.builder(httpHandlerClass, methodName, Modifier.FINAL).initializer("$L", handlerClassBuilder.build()).build(); + FieldSpec handlerField = FieldSpec.builder(httpHandlerClass, handlerName, Modifier.FINAL).initializer("$L", handlerClassBuilder.build()).build(); initBuilder.addCode("$L\n", handlerField.toString()); + + Optional wrapAnnotation = Optional.ofNullable(m.getAnnotation(io.sinistral.proteus.annotations.Chain.class)); + + if( wrapAnnotation.isPresent() ) + { + io.sinistral.proteus.annotations.Chain w = wrapAnnotation.get(); + + Class wrapperClasses[] = w.value(); + + initBuilder.addStatement("$T currentHandler = $L", HttpHandler.class, handlerName); + + + for( int i = 0; i < wrapperClasses.length; i++ ) + { + Class wrapperClass = wrapperClasses[i]; + + String wrapperName = generateFieldName(wrapperClass.getCanonicalName()); + + initBuilder.addStatement("$T $L = new $T()", wrapperClass,wrapperName, wrapperClass); - initBuilder.addStatement("$L.add(io.undertow.util.Methods.$L,$S,$L)", "router", httpMethod, methodPath, methodName); + initBuilder.addStatement("currentHandler = $L.wrap($L)", wrapperName, "currentHandler"); + + } + + initBuilder.addStatement("$L.add(io.undertow.util.Methods.$L,$S,$L)", "router", httpMethod, methodPath, "currentHandler"); + + } + else + { + initBuilder.addStatement("$L.add(io.undertow.util.Methods.$L,$S,$L)", "router", httpMethod, methodPath, handlerName); + } + + initBuilder.addCode("$L", "\n"); registeredEndpoints.add(endpointInfo); @@ -1315,11 +1347,34 @@ public static String typeLiteralNameForType(Type type) erasedTypeName = erasedParts[0]; } - typeName = String.format("%s%s", Character.toLowerCase(erasedTypeName.charAt(0)), erasedTypeName.substring(1, erasedTypeName.length())); + typeName = generateFieldName(erasedTypeName); return typeName; } + + public static String generateFieldName(String name) + { + String[] parts = name.split("\\."); + + StringBuilder sb = new StringBuilder(); + + for( int i = 0; i < parts.length; i++ ) + { + String part = parts[i]; + + if(i == 0) + { + sb.append(String.format("%s%s", Character.toLowerCase(part.charAt(0)), part.substring(1, part.length()))); + } + else + { + sb.append(String.format("%s%s", Character.toUpperCase(part.charAt(0)), part.substring(1, part.length()))); + } + } + + return sb.toString(); + } public static void generateTypeLiteral(MethodSpec.Builder builder, Type type, String name) { diff --git a/src/main/java/io/sinistral/proteus/server/handlers/DefaultHttpHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java similarity index 94% rename from src/main/java/io/sinistral/proteus/server/handlers/DefaultHttpHandler.java rename to src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java index 90541d9..81bc3bd 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/DefaultHttpHandler.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java @@ -20,7 +20,7 @@ /** * @author jbauer */ -public class DefaultHttpHandler implements HttpHandler +public class ServerDefaultHttpHandler implements HttpHandler { @Inject(optional=true) @@ -33,7 +33,7 @@ public class DefaultHttpHandler implements HttpHandler @Inject - public DefaultHttpHandler(Config config) + public ServerDefaultHttpHandler(Config config) { Config globalHeaders = config.getConfig("globalHeaders");