From ee3a38abb2a2965f5de9302a1d47d194d0a160dc Mon Sep 17 00:00:00 2001 From: joshua bauer Date: Thu, 29 Nov 2018 13:35:34 -0800 Subject: [PATCH] Code cleanup. Added support for injecting session attachment handler. --- pom.xml | 25 +- .../sinistral/proteus/ProteusApplication.java | 12 + .../proteus/annotations/Blocking.java | 14 +- .../sinistral/proteus/annotations/Chain.java | 14 +- .../sinistral/proteus/annotations/Debug.java | 14 +- .../proteus/modules/ApplicationModule.java | 215 +++-- .../proteus/modules/ConfigModule.java | 286 +++---- .../proteus/server/ServerRequest.java | 443 +++++----- .../server/endpoints/EndpointInfo.java | 483 ++++++----- .../server/exceptions/ServerException.java | 186 ++-- .../server/handlers/HandlerGenerator.java | 4 +- .../server/handlers/ProteusHandler.java | 801 ++++++++++-------- .../handlers/ServerDefaultHttpHandler.java | 91 +- .../ServerDefaultResponseListener.java | 215 +++-- .../handlers/ServerFallbackHandler.java | 125 +-- .../proteus/server/handlers/TypeHandler.java | 8 +- .../MaxRequestContentLengthPredicate.java | 54 +- .../server/predicates/ServerPredicates.java | 52 +- .../server/security/MapIdentityManager.java | 179 ++-- .../tools/openapi/ServerModelResolver.java | 303 ++++--- .../openapi/ServerParameterExtension.java | 689 ++++++++------- .../tools/swagger/AnnotationHelper.java | 432 +++++----- .../proteus/server/tools/swagger/Reader.java | 4 +- .../swagger/ServerParameterExtension.java | 90 +- .../proteus/services/AssetsService.java | 115 +-- .../proteus/services/BaseService.java | 83 +- .../proteus/services/OpenAPIService.java | 662 +++++++-------- .../proteus/services/SwaggerService.java | 27 +- .../proteus/utilities/SecurityOps.java | 91 +- .../proteus/utilities/TablePrinter.java | 233 ++--- src/main/resources/reference.conf | 2 - 31 files changed, 3025 insertions(+), 2927 deletions(-) diff --git a/pom.xml b/pom.xml index 92b2933..c3bbeb4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.sinistral proteus-core - 0.3.5 + 0.3.6-SNAPSHOT proteus core Proteus is an extremely light, fast, and flexible Java REST API framework built atop Undertow. http://github.com/noboomu/proteus @@ -231,7 +231,17 @@ - + + org.slf4j + slf4j-api + 1.7.25 + + + org.slf4j + slf4j-ext + 1.7.25 + + javax.ws.rs javax.ws.rs-api @@ -294,16 +304,7 @@ 4.4.10 - - org.slf4j - slf4j-api - 1.7.25 - - - org.slf4j - slf4j-ext - 1.7.25 - + org.fusesource.jansi diff --git a/src/main/java/io/sinistral/proteus/ProteusApplication.java b/src/main/java/io/sinistral/proteus/ProteusApplication.java index 74953b0..52f9bce 100644 --- a/src/main/java/io/sinistral/proteus/ProteusApplication.java +++ b/src/main/java/io/sinistral/proteus/ProteusApplication.java @@ -58,6 +58,7 @@ import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.RoutingHandler; +import io.undertow.server.session.SessionAttachmentHandler; import io.undertow.util.Headers; import io.undertow.util.Methods; @@ -86,6 +87,9 @@ public class ProteusApplication @Inject public Config config; + + @Inject(optional=true) + public SessionAttachmentHandler sessionAttachmentHandler; public List> registeredModules = new ArrayList<>(); @@ -276,6 +280,14 @@ public void buildServer() handler = rootHandler; } + if(sessionAttachmentHandler != null) + { + log.info("Using session attachment handler."); + + sessionAttachmentHandler.setNext(handler); + handler = sessionAttachmentHandler; + } + int httpPort = config.getInt("application.ports.http"); if(System.getProperty("http.port") != null) diff --git a/src/main/java/io/sinistral/proteus/annotations/Blocking.java b/src/main/java/io/sinistral/proteus/annotations/Blocking.java index c09578f..62e65d2 100644 --- a/src/main/java/io/sinistral/proteus/annotations/Blocking.java +++ b/src/main/java/io/sinistral/proteus/annotations/Blocking.java @@ -1,15 +1,16 @@ + /** - * + * */ package io.sinistral.proteus.annotations; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + 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; - /** * Indicates that this route should use a BlockingHandler */ @@ -17,7 +18,8 @@ @Target({ TYPE, METHOD }) public @interface Blocking { - boolean value() default true; + boolean value() default true; } - \ No newline at end of file + + diff --git a/src/main/java/io/sinistral/proteus/annotations/Chain.java b/src/main/java/io/sinistral/proteus/annotations/Chain.java index 1050d62..c83eb6f 100644 --- a/src/main/java/io/sinistral/proteus/annotations/Chain.java +++ b/src/main/java/io/sinistral/proteus/annotations/Chain.java @@ -1,15 +1,16 @@ + /** - * + * */ package io.sinistral.proteus.annotations; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + 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; /** @@ -19,7 +20,8 @@ @Target({ TYPE, METHOD }) public @interface Chain { - Class[] value(); + Class[] value(); } - \ No newline at end of file + + diff --git a/src/main/java/io/sinistral/proteus/annotations/Debug.java b/src/main/java/io/sinistral/proteus/annotations/Debug.java index 7e772b3..ee84c01 100644 --- a/src/main/java/io/sinistral/proteus/annotations/Debug.java +++ b/src/main/java/io/sinistral/proteus/annotations/Debug.java @@ -1,15 +1,16 @@ + /** - * + * */ package io.sinistral.proteus.annotations; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + 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; - /** * Indicates that this route should use a RequestDumpingHandler */ @@ -17,7 +18,8 @@ @Target({ TYPE, METHOD }) public @interface Debug { - boolean value() default true; + boolean value() default true; } - \ No newline at end of file + + diff --git a/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java b/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java index cb00df9..d14fb39 100644 --- a/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java +++ b/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.modules; @@ -19,17 +20,20 @@ import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; + import com.google.common.util.concurrent.Service; import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; + import com.typesafe.config.Config; import io.sinistral.proteus.server.Extractors; import io.sinistral.proteus.server.ServerResponse; import io.sinistral.proteus.server.endpoints.EndpointInfo; import io.sinistral.proteus.services.BaseService; + import io.undertow.server.DefaultResponseListener; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; @@ -41,118 +45,101 @@ @Singleton public class ApplicationModule extends AbstractModule { - private static Logger log = LoggerFactory.getLogger(ApplicationModule.class.getCanonicalName()); - - protected Set registeredEndpoints = new TreeSet<>(); - protected Set> registeredControllers = new HashSet<>(); - protected Set> registeredServices = new HashSet<>(); - protected Map registeredHandlerWrappers = new HashMap<>(); - - protected Config config; - - public ApplicationModule(Config config) - { - this.config = config; - } - - @SuppressWarnings("unchecked") - @Override - protected void configure() - { - - this.binder().requestInjection(this); - - this.bindMappers(); - - RoutingHandler router = new RoutingHandler(); - - try - { - String className = config.getString("application.defaultResponseListener"); - log.info("Installing DefaultResponseListener " + className); - Class clazz = (Class) Class.forName(className); - this.bind(DefaultResponseListener.class).to(clazz).in(Singleton.class); - } catch (Exception e) - { - this.binder().addError(e); - log.error(e.getMessage(), e); - } - - try - { - String className = config.getString("application.fallbackHandler"); - log.info("Installing FallbackListener " + className); - - Class clazz = (Class) Class.forName(className); - - HttpHandler fallbackHandler = clazz.newInstance(); - - this.binder().requestInjection(fallbackHandler); - - router.setFallbackHandler(fallbackHandler); - - } catch (Exception e) - { - this.binder().addError(e); - log.error(e.getMessage(), e); - } - - this.bind(RoutingHandler.class).toInstance(router); - - this.bind(ApplicationModule.class).toInstance(this); - - - - this.bind(new TypeLiteral>>() - { - }).annotatedWith(Names.named("registeredControllers")).toInstance(registeredControllers); - - this.bind(new TypeLiteral>() - { - }).annotatedWith(Names.named("registeredEndpoints")).toInstance(registeredEndpoints); - - this.bind(new TypeLiteral>>() - { - }).annotatedWith(Names.named("registeredServices")).toInstance(registeredServices); - - this.bind(new TypeLiteral>() - { - }).annotatedWith(Names.named("registeredHandlerWrappers")).toInstance(registeredHandlerWrappers); - - - - } - - /** - * Override for customizing XmlMapper and ObjectMapper - */ - public void bindMappers() - { - - JacksonXmlModule xmlModule = new JacksonXmlModule(); - xmlModule.setDefaultUseWrapper(false); - - XmlMapper xmlMapper = new XmlMapper(xmlModule); - xmlMapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION); - - this.bind(XmlMapper.class).toInstance(xmlMapper); - - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true); - objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); - objectMapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH,true); - objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); - objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); - - objectMapper.registerModule(new AfterburnerModule()); - objectMapper.registerModule(new Jdk8Module()); - - this.bind(ObjectMapper.class).toInstance(objectMapper); - - this.requestStaticInjection(Extractors.class); - - this.requestStaticInjection(ServerResponse.class); - } + private static Logger log = LoggerFactory.getLogger(ApplicationModule.class.getCanonicalName()); + protected Set registeredEndpoints = new TreeSet<>(); + protected Set> registeredControllers = new HashSet<>(); + protected Set> registeredServices = new HashSet<>(); + protected Map registeredHandlerWrappers = new HashMap<>(); + protected Config config; + + public ApplicationModule(Config config) + { + this.config = config; + } + + /** + * Override for customizing XmlMapper and ObjectMapper + */ + public void bindMappers() + { + JacksonXmlModule xmlModule = new JacksonXmlModule(); + + xmlModule.setDefaultUseWrapper(false); + + XmlMapper xmlMapper = new XmlMapper(xmlModule); + + xmlMapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION); + + this.bind(XmlMapper.class).toInstance(xmlMapper); + + ObjectMapper objectMapper = new ObjectMapper(); + + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true); + objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); + objectMapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH, true); + objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + objectMapper.registerModule(new AfterburnerModule()); + objectMapper.registerModule(new Jdk8Module()); + + this.bind(ObjectMapper.class).toInstance(objectMapper); + this.requestStaticInjection(Extractors.class); + this.requestStaticInjection(ServerResponse.class); + } + + @SuppressWarnings("unchecked") + @Override + protected void configure() + { + this.binder().requestInjection(this); + this.bindMappers(); + + RoutingHandler router = new RoutingHandler(); + + try { + + String className = config.getString("application.defaultResponseListener"); + log.info("Installing DefaultResponseListener " + className); + + Class clazz = (Class) Class.forName(className); + + this.bind(DefaultResponseListener.class).to(clazz).in(Singleton.class); + + } catch (Exception e) { + + this.binder().addError(e); + log.error(e.getMessage(), e); + } + + try { + + String className = config.getString("application.fallbackHandler"); + + log.info("Installing FallbackListener " + className); + + Class clazz = (Class) Class.forName(className); + HttpHandler fallbackHandler = clazz.newInstance(); + + this.binder().requestInjection(fallbackHandler); + router.setFallbackHandler(fallbackHandler); + + } catch (Exception e) { + + this.binder().addError(e); + log.error(e.getMessage(), e); + } + + this.bind(RoutingHandler.class).toInstance(router); + this.bind(ApplicationModule.class).toInstance(this); + + this.bind(new TypeLiteral>>(){}) .annotatedWith(Names.named("registeredControllers")).toInstance(registeredControllers); + this.bind(new TypeLiteral>(){}) .annotatedWith(Names.named("registeredEndpoints")).toInstance(registeredEndpoints); + this.bind(new TypeLiteral>>(){}).annotatedWith(Names.named("registeredServices")).toInstance(registeredServices); + this.bind(new TypeLiteral>(){}).annotatedWith(Names.named("registeredHandlerWrappers")).toInstance(registeredHandlerWrappers); + } } + + + diff --git a/src/main/java/io/sinistral/proteus/modules/ConfigModule.java b/src/main/java/io/sinistral/proteus/modules/ConfigModule.java index 57b7b7e..20d5b4a 100644 --- a/src/main/java/io/sinistral/proteus/modules/ConfigModule.java +++ b/src/main/java/io/sinistral/proteus/modules/ConfigModule.java @@ -1,11 +1,15 @@ + /** - * + * */ package io.sinistral.proteus.modules; import java.io.File; + import java.lang.reflect.Type; + import java.net.URL; + import java.util.List; import java.util.Map.Entry; @@ -19,6 +23,7 @@ import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.util.Types; + import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigObject; @@ -26,158 +31,141 @@ /** * Much of this is taken with reverence from Jooby - * + * * @author jbauer */ - @Singleton public class ConfigModule extends AbstractModule { - private static Logger log = LoggerFactory.getLogger(ConfigModule.class.getCanonicalName()); - - protected String configFile = null; - protected URL configURL = null; - protected Config config = null; - - public ConfigModule() - { - this.configFile = System.getProperty("config.file"); - - if(this.configFile == null) - { - this.configFile = "application.conf"; - } - } - - public ConfigModule(String configFile) - { - this.configFile = configFile; - } - - public ConfigModule(URL configURL) - { - this.configURL = configURL; - - } - - - @Override - protected void configure() - { - Config config = ConfigFactory.defaultApplication(); - - Config referenceConfig = ConfigFactory.load(ConfigFactory.defaultReference()); - - config = ConfigFactory.load(config).withFallback(referenceConfig); - - if(configURL != null) - { - config = ConfigFactory.load(ConfigFactory.parseURL(configURL)).withFallback(config); - } - else if(configFile != null) - { - config = fileConfig(configFile).withFallback(config); - } - - this.bindConfig(config); - - install(new ApplicationModule(this.config)); - } - - - @SuppressWarnings("unchecked") - private void bindConfig(final Config config) - { - - traverse(this.binder(), "", config.root()); - - for (Entry entry : config.entrySet()) - { - String name = entry.getKey(); - - Named named = Names.named(name); - - Object value = entry.getValue().unwrapped(); - - if (value instanceof List) - { - List values = (List) value; - - Type listType = values.size() == 0 ? String.class : Types.listOf(values.iterator().next().getClass()); - - Key key = (Key) Key.get(listType, Names.named(name)); - - - this.binder().bind(key).toInstance(values); - } - else - { - this.binder().bindConstant().annotatedWith(named).to(value.toString()); - } - } - - Config referenceConfig = ConfigFactory.load(ConfigFactory.defaultReference()); - - this.config = ConfigFactory.load(config).withFallback(referenceConfig); - - log.debug(this.config.toString()); - - this.binder().bind(Config.class).toInstance( config ); - - - } - - - private static void traverse(final Binder binder, final String nextPath, final ConfigObject rootConfig) - { - rootConfig.forEach((key, value) -> - { - - if (value instanceof ConfigObject) - { - try - { - - ConfigObject child = (ConfigObject) value; - - String path = nextPath + key; - - Named named = Names.named(path); - - binder.bind(Config.class).annotatedWith(named).toInstance(child.toConfig()); - - traverse(binder, path + ".", child); - - } catch (Exception e) - { - log.error("Error binding " + value,e); - } - } - }); - } - - - private static Config fileConfig(final String fileName) - { - File userDirectory = new File(System.getProperty("user.dir")); - - File fileRoot = new File(userDirectory, fileName); - - if (fileRoot.exists()) - { - return ConfigFactory.load(ConfigFactory.parseFile(fileRoot)); - } - else - { - File fileConfig = new File(new File(userDirectory, "conf"), fileName); - - if (fileConfig.exists()) - { - return ConfigFactory.load(ConfigFactory.parseFile(fileConfig)); - } - } - return ConfigFactory.empty(); - } + private static Logger log = LoggerFactory.getLogger(ConfigModule.class.getCanonicalName()); + protected String configFile = null; + protected URL configURL = null; + protected Config config = null; + + public ConfigModule() + { + this.configFile = System.getProperty("config.file"); + + if (this.configFile == null) + { + this.configFile = "application.conf"; + } + } + + public ConfigModule(String configFile) + { + this.configFile = configFile; + } + + public ConfigModule(URL configURL) + { + this.configURL = configURL; + } + + @SuppressWarnings("unchecked") + private void bindConfig(final Config config) + { + traverse(this.binder(), "", config.root()); + + for (Entry entry : config.entrySet()) + { + String name = entry.getKey(); + Named named = Names.named(name); + Object value = entry.getValue().unwrapped(); + + if (value instanceof List) + { + List values = (List) value; + Type listType = (values.size() == 0) + ? String.class + : Types.listOf(values.iterator().next().getClass()); + Key key = (Key) Key.get(listType, Names.named(name)); + + this.binder().bind(key).toInstance(values); + } + else + { + this.binder().bindConstant().annotatedWith(named).to(value.toString()); + } + } + + Config referenceConfig = ConfigFactory.load(ConfigFactory.defaultReference()); + + this.config = ConfigFactory.load(config).withFallback(referenceConfig); + + log.debug(this.config.toString()); + + this.binder().bind(Config.class).toInstance(config); + } + + @Override + protected void configure() + { + Config config = ConfigFactory.defaultApplication(); + Config referenceConfig = ConfigFactory.load(ConfigFactory.defaultReference()); + + config = ConfigFactory.load(config).withFallback(referenceConfig); + + if (configURL != null) + { + config = ConfigFactory.load(ConfigFactory.parseURL(configURL)).withFallback(config); + } + else if (configFile != null) + { + config = fileConfig(configFile).withFallback(config); + } + + this.bindConfig(config); + + install(new ApplicationModule(this.config)); + } + + private static Config fileConfig(final String fileName) + { + File userDirectory = new File(System.getProperty("user.dir")); + File fileRoot = new File(userDirectory, fileName); + + if (fileRoot.exists()) + { + return ConfigFactory.load(ConfigFactory.parseFile(fileRoot)); + } + else + { + File fileConfig = new File(new File(userDirectory, "conf"), fileName); + + if (fileConfig.exists()) + { + return ConfigFactory.load(ConfigFactory.parseFile(fileConfig)); + } + } + + return ConfigFactory.empty(); + } + + private static void traverse(final Binder binder, final String nextPath, final ConfigObject rootConfig) + { + rootConfig.forEach( + (key, value) -> { + if (value instanceof ConfigObject) + { + try { + + ConfigObject child = (ConfigObject) value; + String path = nextPath + key; + + Named named = Names.named(path); + + binder.bind(Config.class).annotatedWith(named).toInstance(child.toConfig()); + + traverse(binder, path + ".", child); + + } catch (Exception e) { + log.error("Error binding " + value, e); + } + } + } ); + } +} + -} diff --git a/src/main/java/io/sinistral/proteus/server/ServerRequest.java b/src/main/java/io/sinistral/proteus/server/ServerRequest.java index a595e2c..bad7763 100644 --- a/src/main/java/io/sinistral/proteus/server/ServerRequest.java +++ b/src/main/java/io/sinistral/proteus/server/ServerRequest.java @@ -1,17 +1,22 @@ + /** - * + * */ package io.sinistral.proteus.server; import java.io.File; import java.io.IOException; + import java.net.InetSocketAddress; + import java.nio.ByteBuffer; + import java.util.Deque; import java.util.Map; import java.util.concurrent.Executor; import io.sinistral.proteus.server.predicates.ServerPredicates; + import io.undertow.io.Receiver; import io.undertow.security.api.SecurityContext; import io.undertow.server.DefaultResponseListener; @@ -25,232 +30,222 @@ import io.undertow.util.Headers; /** - * + * * @author jbauer * */ - - public class ServerRequest -{ - protected static final Receiver.ErrorCallback ERROR_CALLBACK = new Receiver.ErrorCallback() { - @Override - public void error(HttpServerExchange exchange, IOException e) { - - exchange.putAttachment(DefaultResponseListener.EXCEPTION, e); - - exchange.endExchange(); - } - }; - +{ + protected static final Receiver.ErrorCallback ERROR_CALLBACK = new Receiver.ErrorCallback() + { + @Override + public void error(HttpServerExchange exchange, IOException e) + { + exchange.putAttachment(DefaultResponseListener.EXCEPTION, e); + exchange.endExchange(); + } + }; + public static final AttachmentKey BYTE_BUFFER_KEY = AttachmentKey.create(ByteBuffer.class); + + protected static final String CHARSET = "UTF-8"; + protected static final String TMP_DIR = System.getProperty("java.io.tmpdir"); + + public final HttpServerExchange exchange; + protected final String path; + protected FormData form; + protected final String contentType; + protected final String method; + protected final String accept; + + public ServerRequest() + { + this.method = null; + this.path = null; + this.exchange = null; + this.contentType = null; + this.accept = null; + } + + public ServerRequest(HttpServerExchange exchange) throws IOException + { + this.method = exchange.getRequestMethod().toString(); + this.path = exchange.getRequestPath(); + this.exchange = exchange; + this.contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + this.accept = exchange.getRequestHeaders().getFirst(Headers.ACCEPT); + + if (this.contentType != null) + { + if (ServerPredicates.URL_ENCODED_FORM_PREDICATE.resolve(exchange)) + { + this.parseEncodedForm(); + } + else if (ServerPredicates.MULTIPART_PREDICATE.resolve(exchange)) + { + this.parseMultipartForm(); + } + else if (exchange.getRequestContentLength() > 0) + { + this.extractBytes(); + } + } + } + + public String accept() + { + return this.accept; + } + + public String contentType() + { + return this.contentType; + } + + public HttpServerExchange exchange() + { + return exchange; + } + + private void extractBytes() throws IOException + { + this.exchange.getRequestReceiver().receiveFullBytes(new Receiver.FullBytesCallback() + { + @Override + public void handle(HttpServerExchange exchange, byte[] message) + { + ByteBuffer buffer = ByteBuffer.wrap(message); + + exchange.putAttachment(BYTE_BUFFER_KEY, buffer); + } + },ERROR_CALLBACK); + } + + private void extractFormParameters(final FormData formData) + { + if (formData != null) + { + for (String key : formData) + { + final Deque formValues = formData.get(key); + final Deque values = formValues.stream() + .filter(fv -> !fv.isFileItem()) + .map(FormData.FormValue::getValue) + .collect(java.util.stream.Collectors.toCollection(FastConcurrentDirectDeque::new)); + + exchange.getQueryParameters().put(key, values); + } + } + } + + public Deque files(final String name) + { + if (this.form != null) + { + return form.get(name); + } + + return null; + } + + public String method() + { + return this.method; + } + + private void parseEncodedForm() throws IOException + { + this.exchange.startBlocking(); + + final FormData formData = new FormEncodedDataDefinition().setDefaultEncoding(this.exchange.getRequestCharset()).create(exchange).parseBlocking(); + + this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); + + extractFormParameters(formData); + } + + private void parseMultipartForm() throws IOException + { + this.exchange.startBlocking(); + + final FormDataParser formDataParser = new MultiPartParserDefinition().setTempFileLocation(new File(TMP_DIR).toPath()).setDefaultEncoding(CHARSET).create(this.exchange); + + if (formDataParser != null) + { + final FormData formData = formDataParser.parseBlocking(); + + this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); + + extractFormParameters(formData); + } + } + + public String path() + { + return path; + } + + public String queryString() + { + return exchange.getQueryString(); + } + + public String rawPath() + { + return exchange.getRequestURI(); + } + + public void startAsync(final Executor executor, final Runnable runnable) + { + exchange.dispatch(executor, runnable); + } + + /** + * @param key + * @return the attachment + * @see io.undertow.util.AbstractAttachable#getAttachment(io.undertow.util.AttachmentKey) + */ + public T getAttachment(AttachmentKey key) + { + return exchange.getAttachment(key); + } + + /** + * @return the inetSocketAddress + * @see io.undertow.server.HttpServerExchange#getDestinationAddress() + */ + public InetSocketAddress getDestinationAddress() + { + return exchange.getDestinationAddress(); + } + + /** + * @return the path parameters + * @see io.undertow.server.HttpServerExchange#getPathParameters() + */ + public Map> getPathParameters() + { + return exchange.getPathParameters(); + } + + /** + * @return the query parameters + * @see io.undertow.server.HttpServerExchange#getQueryParameters() + */ + public Map> getQueryParameters() + { + return exchange.getQueryParameters(); + } + + /** + * @return the security context + * @see io.undertow.server.security.api#getSecurityContext() + */ + public SecurityContext getSecurityContext() + { + return exchange.getSecurityContext(); + } +} + + - protected static final String CHARSET = "UTF-8"; - protected static final String TMP_DIR = System.getProperty("java.io.tmpdir"); - - public final HttpServerExchange exchange; - - protected final String path; - protected FormData form; - protected final String contentType; - protected final String method; - protected final String accept; - - - - public ServerRequest() - { - this.method = null; - this.path = null; - this.exchange = null; - this.contentType = null; - this.accept = null; - - } - - public ServerRequest(HttpServerExchange exchange) throws IOException - { - this.method = exchange.getRequestMethod().toString(); - this.path = exchange.getRequestPath(); - this.exchange = exchange; - this.contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); - this.accept = exchange.getRequestHeaders().getFirst(Headers.ACCEPT); - - if (this.contentType != null ) - { - if ( ServerPredicates.URL_ENCODED_FORM_PREDICATE.resolve(exchange) ) - { - this.parseEncodedForm(); - } - else if ( ServerPredicates.MULTIPART_PREDICATE.resolve(exchange) ) - { - this.parseMultipartForm(); - } - else if ( exchange.getRequestContentLength() > 0 ) - { - this.extractBytes(); - } - } - } - - - public Deque files(final String name) - { - if (this.form != null) - { - return form.get(name); - } - - return null; - } - - /** - * @param key - * @return the attachment - * @see io.undertow.util.AbstractAttachable#getAttachment(io.undertow.util.AttachmentKey) - */ - public T getAttachment(AttachmentKey key) - { - return exchange.getAttachment(key); - } - - /** - * @return the inetSocketAddress - * @see io.undertow.server.HttpServerExchange#getDestinationAddress() - */ - public InetSocketAddress getDestinationAddress() - { - return exchange.getDestinationAddress(); - } - - /** - * @return the query parameters - * @see io.undertow.server.HttpServerExchange#getQueryParameters() - */ - public Map> getQueryParameters() - { - return exchange.getQueryParameters(); - } - - /** - * @return the path parameters - * @see io.undertow.server.HttpServerExchange#getPathParameters() - */ - public Map> getPathParameters() - { - return exchange.getPathParameters(); - } - - /** - * @return the security context - * @see io.undertow.server.security.api#getSecurityContext() - */ - public SecurityContext getSecurityContext() - { - return exchange.getSecurityContext(); - } - - - - private void extractBytes() throws IOException - { - - this.exchange.getRequestReceiver().receiveFullBytes(new Receiver.FullBytesCallback() { - @Override - public void handle(HttpServerExchange exchange, byte[] message) { - - ByteBuffer buffer = ByteBuffer.wrap(message); - - exchange.putAttachment(BYTE_BUFFER_KEY, buffer); - - } - }, ERROR_CALLBACK); - - } - - private void parseMultipartForm() throws IOException - { - - this.exchange.startBlocking(); - final FormDataParser formDataParser = new MultiPartParserDefinition() - .setTempFileLocation(new File(TMP_DIR).toPath()) - .setDefaultEncoding(CHARSET) - .create(this.exchange); - - - if(formDataParser != null) - { - final FormData formData = formDataParser.parseBlocking(); - this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); - extractFormParameters(formData); - } - } - - private void parseEncodedForm() throws IOException - { - this.exchange.startBlocking(); - - final FormData formData = new FormEncodedDataDefinition() - .setDefaultEncoding(this.exchange.getRequestCharset()) - .create(exchange).parseBlocking(); - - this.exchange.putAttachment(FormDataParser.FORM_DATA, formData); - - extractFormParameters(formData); - } - - private void extractFormParameters(final FormData formData) - { - if (formData != null) { - for (String key : formData) - { - final Deque formValues = formData.get(key); - final Deque values = formValues.stream() - .filter(fv -> !fv.isFile()) - .map(FormData.FormValue::getValue) - .collect(java.util.stream.Collectors.toCollection(FastConcurrentDirectDeque::new)); - exchange.getQueryParameters().put(key, values); - } - } - } - - public String queryString() - { - return exchange.getQueryString(); - } - - public String method() - { - return this.method; - } - - public String accept() - { - return this.accept; - } - - public String contentType() - { - return this.contentType; - } - - public String path() - { - return path; - } - - public String rawPath() - { - return exchange.getRequestURI(); - } - - public HttpServerExchange exchange() - { - return exchange; - } - - public void startAsync(final Executor executor, final Runnable runnable) - { - exchange.dispatch(executor, runnable); - } -} \ No newline at end of file diff --git a/src/main/java/io/sinistral/proteus/server/endpoints/EndpointInfo.java b/src/main/java/io/sinistral/proteus/server/endpoints/EndpointInfo.java index c1ac903..d0e71f9 100644 --- a/src/main/java/io/sinistral/proteus/server/endpoints/EndpointInfo.java +++ b/src/main/java/io/sinistral/proteus/server/endpoints/EndpointInfo.java @@ -1,11 +1,9 @@ + /** - * + * */ package io.sinistral.proteus.server.endpoints; -import org.apache.commons.lang3.builder.CompareToBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - import io.undertow.util.HttpString; /** @@ -14,231 +12,310 @@ */ public class EndpointInfo implements Comparable { - private HttpString method; - private String pathTemplate; - private String consumes = "*/*"; - private String produces = "*/*"; - private String controllerMethod = "*"; - private String controllerName = "_"; - - - private EndpointInfo(Builder builder) - { - this.method = builder.method; - this.pathTemplate = builder.pathTemplate; - this.consumes = builder.consumes; - this.produces = builder.produces; - this.controllerMethod = builder.controllerMethod; - this.controllerName = builder.controllerName; - } - - public EndpointInfo() - { - - } + private String consumes = "*/*"; + private String produces = "*/*"; + private String controllerMethod = "*"; + private String controllerName = "_"; + private HttpString method; + private String pathTemplate; - /** - * @return the method - */ - public HttpString getMethod() - { - return method; - } + public EndpointInfo() + { + } - /** - * @param method the method to set - */ - public void setMethod(HttpString method) - { - this.method = method; - } + private EndpointInfo(Builder builder) + { + this.method = builder.method; + this.pathTemplate = builder.pathTemplate; + this.consumes = builder.consumes; + this.produces = builder.produces; + this.controllerMethod = builder.controllerMethod; + this.controllerName = builder.controllerName; + } - /** - * @return the pathTemplate - */ - public String getPathTemplate() - { - return pathTemplate; - } - - /** - * @param pathTemplate the pathTemplate to set - */ - public void setPathTemplate(String pathTemplate) - { - this.pathTemplate = pathTemplate; - } + /** + * Creates builder to build {@link EndpointInfo}. + * @return created builder + */ + public static Builder builder() + { + return new Builder(); + } - /** - * @return the consumes - */ - public String getConsumes() - { - return consumes; - } + public int compareTo(EndpointInfo other) + { + int result = this.pathTemplate.compareTo(other.pathTemplate); + + if(result != 0) + { + return result; + } + + result = this.controllerName.compareTo(other.controllerName); + + if(result != 0) + { + return result; + } + + result = this.controllerMethod.compareTo(other.controllerMethod); - /** - * @param consumes the consumes to set - */ - public void setConsumes(String consumes) - { - this.consumes = consumes; - } - - /** - * @return the produces - */ - public String getProduces() - { - return produces; - } - - /** - * @param produces the produces to set - */ - public void setProduces(String produces) - { - this.produces = produces; - } - - /** - * @return the controllerMethod - */ - public String getControllerMethod() - { - return controllerMethod; - } - - /** - * @param controllerMethod the controllerMethod to set - */ - public void setControllerMethod(String controllerMethod) - { - this.controllerMethod = controllerMethod; - } - - /** - * @return the controllerName - */ - public String getControllerName() - { - return controllerName; - } - - /** - * @param controllerName the controllerName to set - */ - public void setControllerName(String controllerName) - { - this.controllerName = controllerName; - if(this.controllerName == null) - { - this.controllerName = ""; - } - } - - - public int hashCode() - { - return new HashCodeBuilder(17, 37). - append(controllerName). - append(controllerMethod). - append(consumes). - append(produces). - append(method). - append(pathTemplate). - toHashCode(); - } - - public int compareTo(EndpointInfo other) { - - return new CompareToBuilder() - .append(this.pathTemplate, other.pathTemplate) - .append(this.controllerName, other.controllerName) - .append(this.controllerMethod, other.controllerMethod) - .append(this.method, other.method) - - .toComparison(); - } - + if(result != 0) + { + return result; + } + + return this.method.compareTo(other.method); + } + @Override - public String toString() + public int hashCode() { - return String.format("\t%-8s %-40s %-26s %-26s %s", this.method, this.pathTemplate, "[" + this.consumes + "]", "[" + this.produces+ "]", "("+this.controllerName+"."+this.controllerMethod+ ")"); + final int prime = 31; + int result = 1; + result = prime * result + ((consumes == null) ? 0 : consumes.hashCode()); + result = prime * result + ((controllerMethod == null) ? 0 : controllerMethod.hashCode()); + result = prime * result + ((controllerName == null) ? 0 : controllerName.hashCode()); + result = prime * result + ((method == null) ? 0 : method.hashCode()); + result = prime * result + ((pathTemplate == null) ? 0 : pathTemplate.hashCode()); + result = prime * result + ((produces == null) ? 0 : produces.hashCode()); + return result; } - /** - * Creates builder to build {@link EndpointInfo}. - * @return created builder - */ - - public static Builder builder() - { - return new Builder(); - } - - /** - * Builder to build {@link EndpointInfo}. - */ - - public static final class Builder + @Override + public boolean equals(Object obj) { - private HttpString method; - private String pathTemplate; - private String consumes = "*/*"; - private String produces = "*/*"; - private String controllerMethod = "_"; - private String controllerName = "_"; - - private Builder() + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EndpointInfo other = (EndpointInfo) obj; + if (consumes == null) { + if (other.consumes != null) + return false; } - - public Builder withMethod(HttpString method) + else if (!consumes.equals(other.consumes)) + return false; + if (controllerMethod == null) { - this.method = method; - return this; + if (other.controllerMethod != null) + return false; } - - public Builder withPathTemplate(String pathTemplate) + else if (!controllerMethod.equals(other.controllerMethod)) + return false; + if (controllerName == null) { - this.pathTemplate = pathTemplate; - return this; + if (other.controllerName != null) + return false; } - - public Builder withConsumes(String consumes) + else if (!controllerName.equals(other.controllerName)) + return false; + if (method == null) { - this.consumes = consumes; - return this; + if (other.method != null) + return false; } - - public Builder withProduces(String produces) + else if (!method.equals(other.method)) + return false; + if (pathTemplate == null) { - this.produces = produces; - return this; + if (other.pathTemplate != null) + return false; } - - public Builder withControllerMethod(String controllerMethod) + else if (!pathTemplate.equals(other.pathTemplate)) + return false; + if (produces == null) { - this.controllerMethod = controllerMethod; - return this; + if (other.produces != null) + return false; } + else if (!produces.equals(other.produces)) + return false; + return true; + } - public Builder withControllerName(String controllerName) - { - this.controllerName = controllerName; - return this; - } + @Override + public String toString() + { + return String.format("\t%-8s %-40s %-26s %-26s %s", + this.method, + this.pathTemplate, + "[" + this.consumes + "]", + "[" + this.produces + "]", + "(" + this.controllerName + "." + this.controllerMethod + ")"); + } - public EndpointInfo build() - { - return new EndpointInfo(this); - } - } + /** + * @return the consumes + */ + public String getConsumes() + { + return consumes; + } + + /** + * @param consumes the consumes to set + */ + public void setConsumes(String consumes) + { + this.consumes = consumes; + } + + /** + * @return the controllerMethod + */ + public String getControllerMethod() + { + return controllerMethod; + } + + /** + * @param controllerMethod the controllerMethod to set + */ + public void setControllerMethod(String controllerMethod) + { + this.controllerMethod = controllerMethod; + } + + /** + * @return the controllerName + */ + public String getControllerName() + { + return controllerName; + } + + /** + * @param controllerName the controllerName to set + */ + public void setControllerName(String controllerName) + { + this.controllerName = controllerName; + + if (this.controllerName == null) + { + this.controllerName = ""; + } + } + + /** + * @return the method + */ + public HttpString getMethod() + { + return method; + } - - + /** + * @param method the method to set + */ + public void setMethod(HttpString method) + { + this.method = method; + } + + /** + * @return the pathTemplate + */ + public String getPathTemplate() + { + return pathTemplate; + } + + /** + * @param pathTemplate the pathTemplate to set + */ + public void setPathTemplate(String pathTemplate) + { + this.pathTemplate = pathTemplate; + } + + /** + * @return the produces + */ + public String getProduces() + { + return produces; + } + + /** + * @param produces the produces to set + */ + public void setProduces(String produces) + { + this.produces = produces; + } + + /** + * Builder to build {@link EndpointInfo}. + */ + public static final class Builder + { + private String consumes = "*/*"; + private String produces = "*/*"; + private String controllerMethod = "_"; + private String controllerName = "_"; + private HttpString method; + private String pathTemplate; + + private Builder() + { + } + + public EndpointInfo build() + { + return new EndpointInfo(this); + } + + public Builder withConsumes(String consumes) + { + this.consumes = consumes; + + return this; + } + + public Builder withControllerMethod(String controllerMethod) + { + this.controllerMethod = controllerMethod; + + return this; + } + + public Builder withControllerName(String controllerName) + { + this.controllerName = controllerName; + + return this; + } + + public Builder withMethod(HttpString method) + { + this.method = method; + + return this; + } + + public Builder withPathTemplate(String pathTemplate) + { + this.pathTemplate = pathTemplate; + + return this; + } + + public Builder withProduces(String produces) + { + this.produces = produces; + + return this; + } + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java b/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java index 52ac978..ec41118 100644 --- a/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java +++ b/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.server.exceptions; @@ -11,94 +12,97 @@ */ public class ServerException extends Exception { - /** - * - */ - private static final long serialVersionUID = 8360356916374374408L; - - private Integer status = 500; - - - - public ServerException(int status) - { - super(); - this.status = status; - } - - - public ServerException(String message, Throwable cause, int status) - { - super(message, cause); - this.status = status; - } - - /** - * @param message - */ - public ServerException(String message, int status) - { - super(message); - this.status = status; - } - - - /** - * @param cause - */ - public ServerException(Throwable cause, int status) - { - super(cause); - this.status = status; - } - - public ServerException(Status status) - { - super(); - this.status = status.getStatusCode(); - } - - - public ServerException(String message, Throwable cause, Status status) - { - super(message, cause); - this.status = status.getStatusCode(); - } - - /** - * @param message - */ - public ServerException(String message, Status status) - { - super(message); - this.status = status.getStatusCode(); - } - - - /** - * @param cause - */ - public ServerException(Throwable cause, Status status) - { - super(cause); - this.status = status.getStatusCode(); - } - - /** - * @return the status - */ - public Integer getStatus() - { - return status; - } - - /** - * @param status the status to set - */ - public void setStatus(Integer status) - { - this.status = status; - } - - + /** + * + */ + private static final long serialVersionUID = 8360356916374374408L; + + private Integer status = 500; + + public ServerException(int status) + { + super(); + + this.status = status; + } + + public ServerException(Status status) + { + super(); + + this.status = status.getStatusCode(); + } + + /** + * @param message + */ + public ServerException(String message, int status) + { + super(message); + + this.status = status; + } + + /** + * @param message + */ + public ServerException(String message, Status status) + { + super(message); + + this.status = status.getStatusCode(); + } + + /** + * @param cause + */ + public ServerException(Throwable cause, int status) + { + super(cause); + + this.status = status; + } + + /** + * @param cause + */ + public ServerException(Throwable cause, Status status) + { + super(cause); + + this.status = status.getStatusCode(); + } + + public ServerException(String message, Throwable cause, int status) + { + super(message, cause); + + this.status = status; + } + + public ServerException(String message, Throwable cause, Status status) + { + super(message, cause); + + this.status = status.getStatusCode(); + } + + /** + * @return the status + */ + public Integer getStatus() + { + return status; + } + + /** + * @param status the status to set + */ + public void setStatus(Integer status) + { + this.status = status; + } } + + + 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 41b9fc7..e555cd9 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java @@ -243,8 +243,7 @@ protected void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class cla final Map literalsNameMap = Arrays.stream(clazz.getDeclaredMethods()) .filter(m -> m.getAnnotation(ApiOperation.class) != null) - .flatMap(m -> Arrays.stream( - m.getParameters()) + .flatMap(m -> Arrays.stream(m.getParameters()) .map(Parameter::getParameterizedType)).filter(t -> { @@ -818,6 +817,7 @@ else if (producesContentType.contains(MediaType.TEXT_HTML)) io.swagger.annotations.ApiOperation apiOperationAnnotation = m.getAnnotation(io.swagger.annotations.ApiOperation.class); io.swagger.annotations.Authorization[] authorizationAnnotations = apiOperationAnnotation.authorizations(); + if (authorizationAnnotations.length > 0) { for (io.swagger.annotations.Authorization authorizationAnnotation : authorizationAnnotations) diff --git a/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java index c5639f8..81d0db4 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java @@ -1,7 +1,9 @@ + /** - * + * */ package io.sinistral.proteus.server.handlers; + import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -25,350 +27,457 @@ * @author jbauer * */ -public class ProteusHandler implements HttpHandler +public class ProteusHandler implements HttpHandler { - private final PathMatcher pathMatcher = new PathMatcher<>(); - - private final LRUCache> cache; - - private final Map> matches = new CopyOnWriteMap<>(); - // Matcher used to find if this instance contains matches for any http method for a path. - // This matcher is used to report if this instance can match a path for one of the http methods. - private final PathTemplateMatcher allMethodsMatcher = new PathTemplateMatcher<>(); - - // Handler called when no match was found and invalid method handler can't be invoked. - private volatile HttpHandler fallbackHandler = ResponseCodeHandler.HANDLE_404; - // Handler called when this instance can not match the http method but can match another http method. - // For example: For an exchange the POST method is not matched by this instance but at least one http method is - // matched for the same exchange. - // If this handler is null the fallbackHandler will be used. - private volatile HttpHandler invalidMethodHandler = ResponseCodeHandler.HANDLE_405; - - // If this is true then path matches will be added to the query parameters for easy access by later handlers. - - - public ProteusHandler(final HttpHandler defaultHandler) { - this(0); - pathMatcher.addPrefixPath("/", defaultHandler); - } - - public ProteusHandler(final HttpHandler defaultHandler, int cacheSize) { - this(cacheSize); - pathMatcher.addPrefixPath("/", defaultHandler); - } - - public ProteusHandler() { - this(0); - } - - public ProteusHandler(int cacheSize) { - if(cacheSize > 0) { - cache = new LRUCache<>(cacheSize, -1, true); - } else { - cache = null; - } - } - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception { - PathMatcher.PathMatch match = null; - boolean hit = false; - if(cache != null) { - match = cache.get(exchange.getRelativePath()); - hit = true; - } - if(match == null) { - match = pathMatcher.match(exchange.getRelativePath()); - } - if (match.getValue() == null) { - handleRouterRequest(exchange); - return; - } - if(hit) { - cache.add(exchange.getRelativePath(), match); - } - exchange.setRelativePath(match.getRemaining()); - if(exchange.getResolvedPath().isEmpty()) { - //first path handler, we can just use the matched part - exchange.setResolvedPath(match.getMatched()); - } else { - //already something in the resolved path - StringBuilder sb = new StringBuilder(exchange.getResolvedPath().length() + match.getMatched().length()); - sb.append(exchange.getResolvedPath()); - sb.append(match.getMatched()); - exchange.setResolvedPath(sb.toString()); - } - match.getValue().handleRequest(exchange); - } - - public void handleRouterRequest(HttpServerExchange exchange) throws Exception { - - PathTemplateMatcher matcher = matches.get(exchange.getRequestMethod()); - if (matcher == null) { - handleNoMatch(exchange); - return; - } - PathTemplateMatcher.PathMatchResult match = matcher.match(exchange.getRelativePath()); - if (match == null) { - handleNoMatch(exchange); - return; - } - exchange.putAttachment(PathTemplateMatch.ATTACHMENT_KEY, match); - - for (Map.Entry entry : match.getParameters().entrySet()) { - exchange.addQueryParam(entry.getKey(), entry.getValue()); - - } - for (HandlerHolder handler : match.getValue().predicatedHandlers) { - if (handler.predicate.resolve(exchange)) { - handler.handler.handleRequest(exchange); - return; - } - } - if (match.getValue().defaultHandler != null) { - match.getValue().defaultHandler.handleRequest(exchange); - } else { - fallbackHandler.handleRequest(exchange); - } - } - - - /** - * Adds a path prefix and a handler for that path. If the path does not start - * with a / then one will be prepended. - *

- * The match is done on a prefix bases, so registering /foo will also match /foo/bar. - * Though exact path matches are taken into account before prefix path matches. So - * if an exact path match exists it's handler will be triggered. - *

- * If / is specified as the path then it will replace the default handler. - * - * @param path If the request contains this prefix, run handler. - * @param handler The handler which is activated upon match. - * @return The resulting PathHandler after this path has been added to it. - */ - public synchronized ProteusHandler addPrefixPath(final String path, final HttpHandler handler) { - Handlers.handlerNotNull(handler); - pathMatcher.addPrefixPath(path, handler); - return this; - } - - /** - * If the request path is exactly equal to the given path, run the handler. - *

- * Exact paths are prioritized higher than prefix paths. - * - * @param path If the request path is exactly this, run handler. - * @param handler Handler run upon exact path match. - * @return The resulting PathHandler after this path has been added to it. - */ - public synchronized ProteusHandler addExactPath(final String path, final HttpHandler handler) { - Handlers.handlerNotNull(handler); - pathMatcher.addExactPath(path, handler); - return this; - } - - public synchronized ProteusHandler removePrefixPath(final String path) { - pathMatcher.removePrefixPath(path); - return this; - } - - public synchronized ProteusHandler removeExactPath(final String path) { - pathMatcher.removeExactPath(path); - return this; - } - - public synchronized ProteusHandler clearPaths() { - pathMatcher.clearPaths(); - return this; - } - - private void handleNoMatch(final HttpServerExchange exchange) throws Exception { - // if invalidMethodHandler is null we fail fast without matching with allMethodsMatcher - if (invalidMethodHandler != null && allMethodsMatcher.match(exchange.getRelativePath()) != null) { - invalidMethodHandler.handleRequest(exchange); - return; - } - fallbackHandler.handleRequest(exchange); - } - - public synchronized ProteusHandler add(final String method, final String template, HttpHandler handler) { - return add(new HttpString(method), template, handler); - } - - public synchronized ProteusHandler add(HttpString method, String template, HttpHandler handler) { - PathTemplateMatcher matcher = matches.get(method); - if (matcher == null) { - matches.put(method, matcher = new PathTemplateMatcher<>()); - } - RoutingMatch res = matcher.get(template); - if (res == null) { - matcher.add(template, res = new RoutingMatch()); - } - if (allMethodsMatcher.get(template) == null) { - allMethodsMatcher.add(template, res); - } - res.defaultHandler = handler; - return this; - } - - public synchronized ProteusHandler get(final String template, HttpHandler handler) { - return add(Methods.GET, template, handler); - } - - public synchronized ProteusHandler post(final String template, HttpHandler handler) { - return add(Methods.POST, template, handler); - } - - public synchronized ProteusHandler put(final String template, HttpHandler handler) { - return add(Methods.PUT, template, handler); - } - - public synchronized ProteusHandler delete(final String template, HttpHandler handler) { - return add(Methods.DELETE, template, handler); - } - - public synchronized ProteusHandler add(final String method, final String template, Predicate predicate, HttpHandler handler) { - return add(new HttpString(method), template, predicate, handler); - } - - public synchronized ProteusHandler add(HttpString method, String template, Predicate predicate, HttpHandler handler) { - PathTemplateMatcher matcher = matches.get(method); - if (matcher == null) { - matches.put(method, matcher = new PathTemplateMatcher<>()); - } - RoutingMatch res = matcher.get(template); - if (res == null) { - matcher.add(template, res = new RoutingMatch()); - } - if (allMethodsMatcher.get(template) == null) { - allMethodsMatcher.add(template, res); - } - res.predicatedHandlers.add(new HandlerHolder(predicate, handler)); - return this; - } - - public synchronized ProteusHandler get(final String template, Predicate predicate, HttpHandler handler) { - return add(Methods.GET, template, predicate, handler); - } - - public synchronized ProteusHandler post(final String template, Predicate predicate, HttpHandler handler) { - return add(Methods.POST, template, predicate, handler); - } - - public synchronized ProteusHandler put(final String template, Predicate predicate, HttpHandler handler) { - return add(Methods.PUT, template, predicate, handler); - } - - public synchronized ProteusHandler delete(final String template, Predicate predicate, HttpHandler handler) { - return add(Methods.DELETE, template, predicate, handler); - } - - public synchronized ProteusHandler addAll(ProteusHandler routingHandler) { - for (Entry> entry : routingHandler.getMatches().entrySet()) { - HttpString method = entry.getKey(); - PathTemplateMatcher matcher = matches.get(method); - if (matcher == null) { - matches.put(method, matcher = new PathTemplateMatcher<>()); - } - matcher.addAll(entry.getValue()); - // If we use allMethodsMatcher.addAll() we can have duplicate - // PathTemplates which we want to ignore here so it does not crash. - for (PathTemplate template : entry.getValue().getPathTemplates()) { - if (allMethodsMatcher.get(template.getTemplateString()) == null) { - allMethodsMatcher.add(template, new RoutingMatch()); - } - } - } - return this; - } - - /** - * - * Removes the specified route from the handler - * - * @param method The method to remove - * @param path the path tempate to remove - * @return this handler - */ - public ProteusHandler remove(HttpString method, String path) { - PathTemplateMatcher handler = matches.get(method); - if(handler != null) { - handler.remove(path); - } - return this; - } - - - /** - * - * Removes the specified route from the handler - * - * @param path the path tempate to remove - * @return this handler - */ - public ProteusHandler remove(String path) { - allMethodsMatcher.remove(path); - return this; - } - - Map> getMatches() { - return matches; - } - - /** - * @return Handler called when no match was found and invalid method handler can't be invoked. - */ - public HttpHandler getFallbackHandler() { - return fallbackHandler; - } - - /** - * @param fallbackHandler Handler that will be called when no match was found and invalid method handler can't be - * invoked. - * @return This instance. - */ - public ProteusHandler setFallbackHandler(HttpHandler fallbackHandler) { - this.fallbackHandler = fallbackHandler; - return this; - } - - /** - * @return Handler called when this instance can not match the http method but can match another http method. - */ - public HttpHandler getInvalidMethodHandler() { - return invalidMethodHandler; - } - - /** - * Sets the handler called when this instance can not match the http method but can match another http method. - * For example: For an exchange the POST method is not matched by this instance but at least one http method matched - * for the exchange. - * If this handler is null the fallbackHandler will be used. - * - * @param invalidMethodHandler Handler that will be called when this instance can not match the http method but can - * match another http method. - * @return This instance. - */ - public ProteusHandler setInvalidMethodHandler(HttpHandler invalidMethodHandler) { - this.invalidMethodHandler = invalidMethodHandler; - return this; - } - - private static class RoutingMatch { - - final List predicatedHandlers = new CopyOnWriteArrayList<>(); - volatile HttpHandler defaultHandler; - - } - - private static class HandlerHolder { - final Predicate predicate; - final HttpHandler handler; - - private HandlerHolder(Predicate predicate, HttpHandler handler) { - this.predicate = predicate; - this.handler = handler; - } - } + private final PathMatcher pathMatcher = new PathMatcher<>(); + private final Map> matches = new CopyOnWriteMap<>(); + + // Matcher used to find if this instance contains matches for any http method for a path. + // This matcher is used to report if this instance can match a path for one of the http methods. + private final PathTemplateMatcher allMethodsMatcher = new PathTemplateMatcher<>(); + + // Handler called when no match was found and invalid method handler can't be invoked. + private volatile HttpHandler fallbackHandler = ResponseCodeHandler.HANDLE_404; + + // Handler called when this instance can not match the http method but can match another http method. + // For example: For an exchange the POST method is not matched by this instance but at least one http method is + // matched for the same exchange. + // If this handler is null the fallbackHandler will be used. + private volatile HttpHandler invalidMethodHandler = ResponseCodeHandler.HANDLE_405; + private final LRUCache> cache; + + public ProteusHandler() + { + this(0); + } + + // If this is true then path matches will be added to the query parameters for easy access by later handlers. + public ProteusHandler(final HttpHandler defaultHandler) + { + this(0); + + pathMatcher.addPrefixPath("/", defaultHandler); + } + + public ProteusHandler(int cacheSize) + { + if (cacheSize > 0) + { + cache = new LRUCache<>(cacheSize, -1, true); + } + else + { + cache = null; + } + } + + public ProteusHandler(final HttpHandler defaultHandler, int cacheSize) + { + this(cacheSize); + + pathMatcher.addPrefixPath("/", defaultHandler); + } + + public synchronized ProteusHandler add(HttpString method, String template, HttpHandler handler) + { + PathTemplateMatcher matcher = matches.get(method); + + if (matcher == null) + { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + + RoutingMatch res = matcher.get(template); + + if (res == null) + { + matcher.add(template, res = new RoutingMatch()); + } + + if (allMethodsMatcher.get(template) == null) + { + allMethodsMatcher.add(template, res); + } + + res.defaultHandler = handler; + + return this; + } + + public synchronized ProteusHandler add(final String method, final String template, HttpHandler handler) + { + return add(new HttpString(method), template, handler); + } + + public synchronized ProteusHandler add(HttpString method, String template, Predicate predicate, HttpHandler handler) + { + PathTemplateMatcher matcher = matches.get(method); + + if (matcher == null) + { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + + RoutingMatch res = matcher.get(template); + + if (res == null) + { + matcher.add(template, res = new RoutingMatch()); + } + + if (allMethodsMatcher.get(template) == null) + { + allMethodsMatcher.add(template, res); + } + + res.predicatedHandlers.add(new HandlerHolder(predicate, handler)); + + return this; + } + + public synchronized ProteusHandler add(final String method, final String template, Predicate predicate, HttpHandler handler) + { + return add(new HttpString(method), template, predicate, handler); + } + + public synchronized ProteusHandler addAll(ProteusHandler routingHandler) + { + for (Entry> entry : routingHandler.getMatches().entrySet()) + { + HttpString method = entry.getKey(); + PathTemplateMatcher matcher = matches.get(method); + + if (matcher == null) + { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + + matcher.addAll(entry.getValue()); + + // If we use allMethodsMatcher.addAll() we can have duplicate + // PathTemplates which we want to ignore here so it does not crash. + for (PathTemplate template : entry.getValue().getPathTemplates()) + { + if (allMethodsMatcher.get(template.getTemplateString()) == null) + { + allMethodsMatcher.add(template, new RoutingMatch()); + } + } + } + + return this; + } + + /** + * If the request path is exactly equal to the given path, run the handler. + *

+ * Exact paths are prioritized higher than prefix paths. + * + * @param path If the request path is exactly this, run handler. + * @param handler Handler run upon exact path match. + * @return The resulting PathHandler after this path has been added to it. + */ + public synchronized ProteusHandler addExactPath(final String path, final HttpHandler handler) + { + Handlers.handlerNotNull(handler); + pathMatcher.addExactPath(path, handler); + + return this; + } + + /** + * Adds a path prefix and a handler for that path. If the path does not start + * with a / then one will be prepended. + *

+ * The match is done on a prefix bases, so registering /foo will also match /foo/bar. + * Though exact path matches are taken into account before prefix path matches. So + * if an exact path match exists it's handler will be triggered. + *

+ * If / is specified as the path then it will replace the default handler. + * + * @param path If the request contains this prefix, run handler. + * @param handler The handler which is activated upon match. + * @return The resulting PathHandler after this path has been added to it. + */ + public synchronized ProteusHandler addPrefixPath(final String path, final HttpHandler handler) + { + Handlers.handlerNotNull(handler); + pathMatcher.addPrefixPath(path, handler); + + return this; + } + + public synchronized ProteusHandler clearPaths() + { + pathMatcher.clearPaths(); + + return this; + } + + public synchronized ProteusHandler delete(final String template, HttpHandler handler) + { + return add(Methods.DELETE, template, handler); + } + + public synchronized ProteusHandler delete(final String template, Predicate predicate, HttpHandler handler) + { + return add(Methods.DELETE, template, predicate, handler); + } + + private void handleNoMatch(final HttpServerExchange exchange) throws Exception + { + // if invalidMethodHandler is null we fail fast without matching with allMethodsMatcher + if ((invalidMethodHandler != null) && (allMethodsMatcher.match(exchange.getRelativePath()) != null)) + { + invalidMethodHandler.handleRequest(exchange); + + return; + } + + fallbackHandler.handleRequest(exchange); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + PathMatcher.PathMatch match = null; + boolean hit = false; + + if (cache != null) + { + match = cache.get(exchange.getRelativePath()); + hit = true; + } + + if (match == null) + { + match = pathMatcher.match(exchange.getRelativePath()); + } + + if (match.getValue() == null) + { + handleRouterRequest(exchange); + + return; + } + + if (hit) + { + cache.add(exchange.getRelativePath(), match); + } + + exchange.setRelativePath(match.getRemaining()); + + if (exchange.getResolvedPath().isEmpty()) + { + // first path handler, we can just use the matched part + exchange.setResolvedPath(match.getMatched()); + } + else + { + // already something in the resolved path + StringBuilder sb = new StringBuilder(exchange.getResolvedPath().length() + match.getMatched().length()); + + sb.append(exchange.getResolvedPath()); + sb.append(match.getMatched()); + exchange.setResolvedPath(sb.toString()); + } + + match.getValue().handleRequest(exchange); + } + + public void handleRouterRequest(HttpServerExchange exchange) throws Exception + { + PathTemplateMatcher matcher = matches.get(exchange.getRequestMethod()); + + if (matcher == null) + { + handleNoMatch(exchange); + + return; + } + + PathTemplateMatcher.PathMatchResult match = matcher.match(exchange.getRelativePath()); + + if (match == null) + { + handleNoMatch(exchange); + + return; + } + + exchange.putAttachment(PathTemplateMatch.ATTACHMENT_KEY, match); + + for (Map.Entry entry : match.getParameters().entrySet()) + { + exchange.addQueryParam(entry.getKey(), entry.getValue()); + } + + for (HandlerHolder handler : match.getValue().predicatedHandlers) + { + if (handler.predicate.resolve(exchange)) + { + handler.handler.handleRequest(exchange); + + return; + } + } + + if (match.getValue().defaultHandler != null) + { + match.getValue().defaultHandler.handleRequest(exchange); + } + else + { + fallbackHandler.handleRequest(exchange); + } + } + + public synchronized ProteusHandler post(final String template, HttpHandler handler) + { + return add(Methods.POST, template, handler); + } + + public synchronized ProteusHandler post(final String template, Predicate predicate, HttpHandler handler) + { + return add(Methods.POST, template, predicate, handler); + } + + public synchronized ProteusHandler put(final String template, HttpHandler handler) + { + return add(Methods.PUT, template, handler); + } + + public synchronized ProteusHandler put(final String template, Predicate predicate, HttpHandler handler) + { + return add(Methods.PUT, template, predicate, handler); + } + + /** + * + * Removes the specified route from the handler + * + * @param path the path tempate to remove + * @return this handler + */ + public ProteusHandler remove(String path) + { + allMethodsMatcher.remove(path); + + return this; + } + + /** + * + * Removes the specified route from the handler + * + * @param method The method to remove + * @param path the path tempate to remove + * @return this handler + */ + public ProteusHandler remove(HttpString method, String path) + { + PathTemplateMatcher handler = matches.get(method); + + if (handler != null) + { + handler.remove(path); + } + + return this; + } + + public synchronized ProteusHandler removeExactPath(final String path) + { + pathMatcher.removeExactPath(path); + + return this; + } + + public synchronized ProteusHandler removePrefixPath(final String path) + { + pathMatcher.removePrefixPath(path); + + return this; + } + + /** + * @return Handler called when no match was found and invalid method handler can't be invoked. + */ + public HttpHandler getFallbackHandler() + { + return fallbackHandler; + } + + /** + * @param fallbackHandler Handler that will be called when no match was found and invalid method handler can't be + * invoked. + * @return This instance. + */ + public ProteusHandler setFallbackHandler(HttpHandler fallbackHandler) + { + this.fallbackHandler = fallbackHandler; + + return this; + } + + public synchronized ProteusHandler get(final String template, HttpHandler handler) + { + return add(Methods.GET, template, handler); + } + + public synchronized ProteusHandler get(final String template, Predicate predicate, HttpHandler handler) + { + return add(Methods.GET, template, predicate, handler); + } + + /** + * @return Handler called when this instance can not match the http method but can match another http method. + */ + public HttpHandler getInvalidMethodHandler() + { + return invalidMethodHandler; + } + + /** + * Sets the handler called when this instance can not match the http method but can match another http method. + * For example: For an exchange the POST method is not matched by this instance but at least one http method matched + * for the exchange. + * If this handler is null the fallbackHandler will be used. + * + * @param invalidMethodHandler Handler that will be called when this instance can not match the http method but can + * match another http method. + * @return This instance. + */ + public ProteusHandler setInvalidMethodHandler(HttpHandler invalidMethodHandler) + { + this.invalidMethodHandler = invalidMethodHandler; + + return this; + } + + Map> getMatches() + { + return matches; + } + + private static class HandlerHolder + { + final Predicate predicate; + final HttpHandler handler; + + private HandlerHolder(Predicate predicate, HttpHandler handler) + { + this.predicate = predicate; + this.handler = handler; + } + } + + + private static class RoutingMatch + { + final List predicatedHandlers = new CopyOnWriteArrayList<>(); + volatile HttpHandler defaultHandler; + } } + diff --git a/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java index 81bc3bd..ef7403b 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultHttpHandler.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.server.handlers; @@ -7,6 +8,7 @@ import java.util.stream.Collectors; import com.google.inject.Inject; + import com.typesafe.config.Config; import io.undertow.server.DefaultResponseListener; @@ -22,53 +24,52 @@ */ public class ServerDefaultHttpHandler implements HttpHandler { + protected final HeaderMap headers = new HeaderMap(); + + @Inject(optional = true) + protected DefaultResponseListener defaultResponseListener; + + @Inject + protected volatile RoutingHandler next; + + @Inject + public ServerDefaultHttpHandler(Config config) + { + Config globalHeaders = config.getConfig("globalHeaders"); + Map globalHeaderParameters = globalHeaders.entrySet().stream().collect(Collectors.toMap(e -> HttpString.tryFromString(e.getKey()), e -> e.getValue().unwrapped() + "")); + + for (Map.Entry e : globalHeaderParameters.entrySet()) + { + headers.add(e.getKey(), e.getValue()); + } + } + + /* + * (non-Javadoc) + * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) + */ + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception + { + if (this.defaultResponseListener != null) + { + exchange.addDefaultResponseListener(defaultResponseListener); + } + + long fiGlobal = this.headers.fastIterateNonEmpty(); + + while (fiGlobal != -1) + { + final HeaderValues headerValues = headers.fiCurrent(fiGlobal); - @Inject(optional=true) - protected DefaultResponseListener defaultResponseListener; - - @Inject - protected volatile RoutingHandler next; - - protected final HeaderMap headers = new HeaderMap(); - - - @Inject - public ServerDefaultHttpHandler(Config config) - { - Config globalHeaders = config.getConfig("globalHeaders"); - - Map globalHeaderParameters = globalHeaders.entrySet().stream().collect(Collectors.toMap(e -> HttpString.tryFromString(e.getKey()), e ->e.getValue().unwrapped()+"")); - - for( Map.Entry e : globalHeaderParameters.entrySet() ) - { - headers.add(e.getKey(), e.getValue()); - } - - } - - /* - * (non-Javadoc) - * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) - */ - @Override - public void handleRequest(final HttpServerExchange exchange) throws Exception - { - - if(this.defaultResponseListener != null) - { - exchange.addDefaultResponseListener(defaultResponseListener); - } - - long fiGlobal = this.headers.fastIterateNonEmpty(); - while (fiGlobal != -1) { - - final HeaderValues headerValues = headers.fiCurrent(fiGlobal); exchange.getResponseHeaders().addAll(headerValues.getHeaderName(), headerValues); + fiGlobal = headers.fiNextNonEmpty(fiGlobal); } - - next.handleRequest(exchange); - - } + next.handleRequest(exchange); + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultResponseListener.java b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultResponseListener.java index ebfde89..e5d8efd 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultResponseListener.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/ServerDefaultResponseListener.java @@ -1,10 +1,12 @@ + /** - * + * */ package io.sinistral.proteus.server.handlers; import java.io.PrintWriter; import java.io.StringWriter; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -19,10 +21,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; + import com.google.inject.Inject; import com.google.inject.Singleton; import io.sinistral.proteus.server.predicates.ServerPredicates; + import io.undertow.server.DefaultResponseListener; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; @@ -35,114 +39,105 @@ @Singleton public class ServerDefaultResponseListener implements DefaultResponseListener { - private static Logger log = LoggerFactory.getLogger(ServerDefaultResponseListener.class.getCanonicalName()); - - - @Inject - protected XmlMapper xmlMapper; - - @Inject - protected ObjectMapper objectMapper; - - @Override - public boolean handleDefaultResponse(HttpServerExchange exchange) - { - if (!exchange.isResponseChannelAvailable()) { - return false; - } - - final int statusCode = exchange.getStatusCode(); - - if (statusCode >= 400) { - - final Map errorMap = new HashMap<>(); - - final String path = exchange.getRelativePath(); - - Throwable throwable = exchange.getAttachment(DefaultResponseListener.EXCEPTION); - - if( throwable == null ) - { - final String reason = StatusCodes.getReason(statusCode); - - throwable = new Exception(reason); - } - - errorMap.put("exceptionClass", throwable.getClass().getName()); - - errorMap.put("message", throwable.getMessage()); - - errorMap.put("path", path); - - errorMap.put("code", Integer.toString(statusCode)); - - log.error("\n\tmessage: " + throwable.getMessage() + "\n\tpath: " + path,throwable); - - if( throwable.getStackTrace() != null ) - { - if( throwable.getStackTrace().length > 0 ) - { - errorMap.put("className", throwable.getStackTrace()[0].getClassName()); - } - - StringWriter sw = new StringWriter(); - throwable.printStackTrace(new PrintWriter(sw)); - String exceptionAsString = sw.toString(); - - List stringList = Arrays.stream(exceptionAsString.split("\n")).collect(Collectors.toList()); - - try - { - errorMap.put("stackTrace", objectMapper.writeValueAsString(stringList)); - } catch (JsonProcessingException e) - { - log.error(e.getMessage()); - } - - } - - - if(throwable instanceof IllegalArgumentException ) - { - exchange.setStatusCode(StatusCodes.BAD_REQUEST); - } - - if( ServerPredicates.ACCEPT_XML_EXCLUSIVE_PREDICATE.resolve(exchange) ) - { - try - { - - final String xmlBody = xmlMapper.writeValueAsString(errorMap); - exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, xmlBody.length()); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.APPLICATION_XML); - exchange.getResponseSender().send(xmlBody); - - } catch (JsonProcessingException e) - { - log.warn("Unable to create XML from error..."); - } - - } - else - { - String jsonBody; - - try - { - jsonBody = objectMapper.writeValueAsString(errorMap); - } catch (Exception e) - { - jsonBody = errorMap.toString(); - } - - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.APPLICATION_JSON); - exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, jsonBody.length()); - exchange.getResponseSender().send(jsonBody); - } - - return true; - } - return false; - } + private static Logger log = LoggerFactory.getLogger(ServerDefaultResponseListener.class.getCanonicalName()); + + @Inject + protected XmlMapper xmlMapper; + + @Inject + protected ObjectMapper objectMapper; + + @Override + public boolean handleDefaultResponse(HttpServerExchange exchange) + { + if (!exchange.isResponseChannelAvailable()) + { + return false; + } + + final int statusCode = exchange.getStatusCode(); + + if (statusCode >= 400) + { + final Map errorMap = new HashMap<>(); + final String path = exchange.getRelativePath(); + Throwable throwable = exchange.getAttachment(DefaultResponseListener.EXCEPTION); + + if (throwable == null) + { + final String reason = StatusCodes.getReason(statusCode); + + throwable = new Exception(reason); + } + + errorMap.put("exceptionClass", throwable.getClass().getName()); + errorMap.put("message", throwable.getMessage()); + errorMap.put("path", path); + errorMap.put("code", Integer.toString(statusCode)); + + log.error("\n\tmessage: " + throwable.getMessage() + "\n\tpath: " + path, throwable); + + if (throwable.getStackTrace() != null) + { + if (throwable.getStackTrace().length > 0) + { + errorMap.put("className", throwable.getStackTrace()[0].getClassName()); + } + + StringWriter sw = new StringWriter(); + + throwable.printStackTrace(new PrintWriter(sw)); + String exceptionAsString = sw.toString(); + List stringList = Arrays.stream(exceptionAsString.split("\n")).collect(Collectors.toList()); + + try { + errorMap.put("stackTrace", objectMapper.writeValueAsString(stringList)); + } catch (JsonProcessingException e) { + log.error(e.getMessage()); + } + } + + if (throwable instanceof IllegalArgumentException) + { + exchange.setStatusCode(StatusCodes.BAD_REQUEST); + } + + if (ServerPredicates.ACCEPT_XML_EXCLUSIVE_PREDICATE.resolve(exchange)) + { + try { + + final String xmlBody = xmlMapper.writeValueAsString(errorMap); + + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, xmlBody.length()); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.APPLICATION_XML); + exchange.getResponseSender().send(xmlBody); + + } catch (JsonProcessingException e) { + log.warn("Unable to create XML from error..."); + } + } + else + { + String jsonBody; + + try { + jsonBody = objectMapper.writeValueAsString(errorMap); + } catch (Exception e) { + jsonBody = errorMap.toString(); + } + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.APPLICATION_JSON); + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, jsonBody.length()); + exchange.getResponseSender().send(jsonBody); + } + + return true; + } + + return false; + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/handlers/ServerFallbackHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/ServerFallbackHandler.java index 1b317ad..3f02e49 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/ServerFallbackHandler.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/ServerFallbackHandler.java @@ -1,14 +1,17 @@ + /** - * + * */ package io.sinistral.proteus.server.handlers; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; + import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import io.sinistral.proteus.server.predicates.ServerPredicates; + import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; @@ -19,66 +22,68 @@ */ public class ServerFallbackHandler implements HttpHandler { - private class Message { - - @SuppressWarnings("unused") - public final Integer statusCode; - @SuppressWarnings("unused") - public final String reason; - - /** - * @param statusCode - * @param reason - */ - public Message(Integer statusCode, String reason) - { - this.statusCode = statusCode; - this.reason = reason; - } - } - - @Inject - protected XmlMapper xmlMapper; - - @Inject - protected ObjectMapper objectMapper; - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception - { - final int statusCode = 404; - - exchange.setStatusCode(statusCode); - - final String responseBody; - - final String reason = StatusCodes.getReason(statusCode); - - if (ServerPredicates.ACCEPT_JSON_PREDICATE.resolve(exchange)) - { - responseBody = objectMapper.writeValueAsString(new Message(statusCode,reason)); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.APPLICATION_JSON); - } - else if (ServerPredicates.ACCEPT_XML_PREDICATE.resolve(exchange)) - { - responseBody = xmlMapper.writeValueAsString(new Message(statusCode,reason)); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.APPLICATION_XML); - } - else if (ServerPredicates.ACCEPT_HTML_PREDICATE.resolve(exchange)) - { - responseBody = "Error" + statusCode + " - " + reason + ""; - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.TEXT_HTML); - } - else - { - responseBody = statusCode + " - " + reason; - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.TEXT_PLAIN); - } - + @Inject + protected XmlMapper xmlMapper; + @Inject + protected ObjectMapper objectMapper; + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + final int statusCode = 404; + + exchange.setStatusCode(statusCode); + + final String responseBody; + final String reason = StatusCodes.getReason(statusCode); + + if (ServerPredicates.ACCEPT_JSON_PREDICATE.resolve(exchange)) + { + responseBody = objectMapper.writeValueAsString(new Message(statusCode, reason)); + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.APPLICATION_JSON); + } + else if (ServerPredicates.ACCEPT_XML_PREDICATE.resolve(exchange)) + { + responseBody = xmlMapper.writeValueAsString(new Message(statusCode, reason)); + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.APPLICATION_XML); + } + else if (ServerPredicates.ACCEPT_HTML_PREDICATE.resolve(exchange)) + { + responseBody = "Error" + statusCode + " - " + reason + ""; + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.TEXT_HTML); + } + else + { + responseBody = statusCode + " - " + reason; + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, javax.ws.rs.core.MediaType.TEXT_PLAIN); + } + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + responseBody.length()); - exchange.getResponseSender().send(responseBody); + exchange.getResponseSender().send(responseBody); + } - + private class Message + { + @SuppressWarnings("unused") + public final Integer statusCode; + @SuppressWarnings("unused") + public final String reason; - } + /** + * @param statusCode + * @param reason + */ + public Message(Integer statusCode, String reason) + { + this.statusCode = statusCode; + this.reason = reason; + } + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/handlers/TypeHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/TypeHandler.java index 1b31a54..2b6019a 100644 --- a/src/main/java/io/sinistral/proteus/server/handlers/TypeHandler.java +++ b/src/main/java/io/sinistral/proteus/server/handlers/TypeHandler.java @@ -20,10 +20,10 @@ import io.sinistral.proteus.server.handlers.HandlerGenerator.StatementParameterType; -/** - * Enum class that assists in code generation for different method parameter - * types + /** + * Enum that assists in code generation for different method parameter types */ + public enum TypeHandler { @@ -159,7 +159,7 @@ public static void addStatement(MethodSpec.Builder builder, Parameter parameter, { Object[] args = new Object[handler.parameterTypes.length]; -/// typeReferenceNameForParameterizedType + for (int i = 0; i < handler.parameterTypes.length; i++) { if (handler.parameterTypes[i] instanceof StatementParameterType) diff --git a/src/main/java/io/sinistral/proteus/server/predicates/MaxRequestContentLengthPredicate.java b/src/main/java/io/sinistral/proteus/server/predicates/MaxRequestContentLengthPredicate.java index 4d478cf..1c28d23 100644 --- a/src/main/java/io/sinistral/proteus/server/predicates/MaxRequestContentLengthPredicate.java +++ b/src/main/java/io/sinistral/proteus/server/predicates/MaxRequestContentLengthPredicate.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.server.predicates; @@ -20,46 +21,59 @@ public class MaxRequestContentLengthPredicate implements Predicate { private final long maxSize; - MaxRequestContentLengthPredicate(final long maxSize) { + MaxRequestContentLengthPredicate(final long maxSize) + { this.maxSize = maxSize; } @Override - public boolean resolve(final HttpServerExchange value) { + public boolean resolve(final HttpServerExchange value) + { final String length = value.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); - if (length == null) { + + if (length == null) + { return false; } + return Long.parseLong(length) > maxSize; } - public static class Builder implements PredicateBuilder { - + public static class Builder implements PredicateBuilder + { @Override - public String name() { - return "max-content-size"; + public Predicate build(final Map config) + { + Long max = (Long) config.get("value"); + + return new MaxRequestContentLengthPredicate(max); } @Override - public Map> parameters() { - return Collections.>singletonMap("value", Long.class); + public String defaultParameter() + { + return "value"; } - - + @Override - public Set requiredParameters() { - return Collections.singleton("value"); + public String name() + { + return "max-content-size"; } @Override - public String defaultParameter() { - return "value"; + public Map> parameters() + { + return Collections.>singletonMap("value", Long.class); } @Override - public Predicate build(final Map config) { - Long max = (Long) config.get("value"); - return new MaxRequestContentLengthPredicate(max); + public Set requiredParameters() + { + return Collections.singleton("value"); } } -} \ No newline at end of file +} + + + diff --git a/src/main/java/io/sinistral/proteus/server/predicates/ServerPredicates.java b/src/main/java/io/sinistral/proteus/server/predicates/ServerPredicates.java index ff08211..787966c 100644 --- a/src/main/java/io/sinistral/proteus/server/predicates/ServerPredicates.java +++ b/src/main/java/io/sinistral/proteus/server/predicates/ServerPredicates.java @@ -1,11 +1,13 @@ + /** - * + * */ package io.sinistral.proteus.server.predicates; import java.util.Collections; import io.sinistral.proteus.server.MediaType; + import io.undertow.attribute.ExchangeAttributes; import io.undertow.predicate.Predicate; import io.undertow.predicate.Predicates; @@ -19,29 +21,27 @@ */ public class ServerPredicates { - - public static final String JSON_REGEX = "^(application\\/(json|x-javascript)|text\\/(json|x-javascript|x-json))(;.*)?$"; - - public static final String XML_REGEX = "^(application\\/(xml|xhtml\\+xml)|text\\/xml)(;.*)?$"; - - public static final String HTML_REGEX = "^(text\\/html)(;.*)?$"; - - public static final String TEXT_REGEX = "^(text\\/plain)(;.*)?$"; - - public static final Predicate JSON_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), JSON_REGEX); - public static final Predicate XML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), XML_REGEX); - public static final Predicate HTML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), HTML_REGEX); - public static final Predicate WILDCARD_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MediaType.ANY.contentType()); - public static final Predicate NO_WILDCARD_PREDICATE = Predicates.not(Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MediaType.ANY.contentType())); - public static final Predicate ACCEPT_JSON_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), JSON_REGEX); - public static final Predicate ACCEPT_XML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), XML_REGEX); - public static final Predicate ACCEPT_HTML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), HTML_REGEX); - public static final Predicate ACCEPT_TEXT_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), TEXT_REGEX); - public static final Predicate ACCEPT_XML_EXCLUSIVE_PREDICATE = Predicates.and(ACCEPT_XML_PREDICATE, NO_WILDCARD_PREDICATE ); - public static final Predicate MAX_CONTENT_SIZE_PREDICATE = new MaxRequestContentLengthPredicate.Builder().build(Collections.singletonMap("value", 0L)); - public static final Predicate STRING_BODY_PREDICATE = Predicates.and(Predicates.or(JSON_PREDICATE,XML_PREDICATE), MAX_CONTENT_SIZE_PREDICATE ); - public static final Predicate MULTIPART_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), MediaType.APPLICATION_OCTET_STREAM.contentType(), MultiPartParserDefinition.MULTIPART_FORM_DATA ); - public static final Predicate URL_ENCODED_FORM_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), FormEncodedDataDefinition.APPLICATION_X_WWW_FORM_URLENCODED ); - - + public static final String JSON_REGEX = "^(application\\/(json|x-javascript)|text\\/(json|x-javascript|x-json))(;.*)?$"; + public static final String XML_REGEX = "^(application\\/(xml|xhtml\\+xml)|text\\/xml)(;.*)?$"; + public static final String HTML_REGEX = "^(text\\/html)(;.*)?$"; + public static final String TEXT_REGEX = "^(text\\/plain)(;.*)?$"; + public static final Predicate JSON_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), JSON_REGEX); + public static final Predicate XML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), XML_REGEX); + public static final Predicate HTML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), HTML_REGEX); + public static final Predicate WILDCARD_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MediaType.ANY.contentType()); + public static final Predicate NO_WILDCARD_PREDICATE = Predicates.not(Predicates.contains(ExchangeAttributes.requestHeader(Headers.ACCEPT), MediaType.ANY.contentType())); + public static final Predicate ACCEPT_JSON_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), JSON_REGEX); + public static final Predicate ACCEPT_XML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), XML_REGEX); + public static final Predicate ACCEPT_HTML_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), HTML_REGEX); + public static final Predicate ACCEPT_TEXT_PREDICATE = Predicates.regex(ExchangeAttributes.requestHeader(Headers.ACCEPT), TEXT_REGEX); + public static final Predicate ACCEPT_XML_EXCLUSIVE_PREDICATE = Predicates.and(ACCEPT_XML_PREDICATE, NO_WILDCARD_PREDICATE); + public static final Predicate MAX_CONTENT_SIZE_PREDICATE = new MaxRequestContentLengthPredicate.Builder().build(Collections.singletonMap("value", 0L)); + public static final Predicate STRING_BODY_PREDICATE = Predicates.and(Predicates.or(JSON_PREDICATE, XML_PREDICATE), MAX_CONTENT_SIZE_PREDICATE); + public static final Predicate MULTIPART_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), + MediaType.APPLICATION_OCTET_STREAM.contentType(), + MultiPartParserDefinition.MULTIPART_FORM_DATA); + public static final Predicate URL_ENCODED_FORM_PREDICATE = Predicates.contains(ExchangeAttributes.requestHeader(Headers.CONTENT_TYPE), FormEncodedDataDefinition.APPLICATION_X_WWW_FORM_URLENCODED); } + + + diff --git a/src/main/java/io/sinistral/proteus/server/security/MapIdentityManager.java b/src/main/java/io/sinistral/proteus/server/security/MapIdentityManager.java index 960c1ec..e786f96 100644 --- a/src/main/java/io/sinistral/proteus/server/security/MapIdentityManager.java +++ b/src/main/java/io/sinistral/proteus/server/security/MapIdentityManager.java @@ -1,9 +1,11 @@ + /** - * + * */ package io.sinistral.proteus.server.security; import java.security.Principal; + import java.util.Arrays; import java.util.Collections; import java.util.Map; @@ -19,92 +21,93 @@ */ public class MapIdentityManager implements IdentityManager { + private final Map identities; + + public MapIdentityManager(final Map identities) + { + this.identities = identities; + } + + @Override + public Account verify(Account account) + { + // An existing account so for testing assume still valid. + return account; + } + + @Override + public Account verify(Credential credential) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Account verify(String id, Credential credential) + { + Account account = getAccount(id); + + if ((account != null) && verifyCredential(account, credential)) + { + return account; + } + + return null; + } + + private boolean verifyCredential(Account account, Credential credential) + { + if (credential instanceof PasswordCredential) + { + char[] password = ((PasswordCredential) credential).getPassword(); + char[] expectedPassword = identities.get(account.getPrincipal().getName()); + + return Arrays.equals(password, expectedPassword); + } + + return false; + } + + private Account getAccount(final String id) + { + if (identities.containsKey(id)) + { + return new UserAccount(id); + } + + return null; + } + + private class UserAccount implements Account + { + private static final long serialVersionUID = -8234851531206339721L; + private final Principal principal; + + public UserAccount(String id) + { + principal = new Principal() + { + @Override + public String getName() + { + return id; + } + }; + } + + @Override + public Principal getPrincipal() + { + return principal; + } + + @Override + public Set getRoles() + { + return Collections.emptySet(); + } + } +} + - private final Map identities; - - public MapIdentityManager(final Map identities) - { - this.identities = identities; - } - - @Override - public Account verify(Account account) - { - // An existing account so for testing assume still valid. - return account; - } - - @Override - public Account verify(String id, Credential credential) - { - Account account = getAccount(id); - if (account != null && verifyCredential(account, credential)) - { - return account; - } - - return null; - } - - @Override - public Account verify(Credential credential) - { - // TODO Auto-generated method stub - return null; - } - - private boolean verifyCredential(Account account, Credential credential) - { - if (credential instanceof PasswordCredential) - { - char[] password = ((PasswordCredential) credential).getPassword(); - char[] expectedPassword = identities.get(account.getPrincipal().getName()); - - return Arrays.equals(password, expectedPassword); - } - return false; - } - - private Account getAccount(final String id) - { - if (identities.containsKey(id)) - { - return new UserAccount(id); - } - return null; - } - - private class UserAccount implements Account - { - - private static final long serialVersionUID = -8234851531206339721L; - - private final Principal principal; - - public UserAccount(String id) - { - principal = new Principal() - { - - @Override - public String getName() - { - return id; - } - }; - } - - @Override - public Principal getPrincipal() - { - return principal; - } - - @Override - public Set getRoles() - { - return Collections.emptySet(); - } - } -} diff --git a/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerModelResolver.java b/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerModelResolver.java index 3262d13..f397cb6 100644 --- a/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerModelResolver.java +++ b/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerModelResolver.java @@ -1,12 +1,16 @@ + /** - * + * */ package io.sinistral.proteus.server.tools.openapi; import java.io.File; + import java.lang.annotation.Annotation; import java.lang.reflect.Type; + import java.nio.ByteBuffer; + import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -19,6 +23,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory; import io.sinistral.proteus.server.ServerResponse; + import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; @@ -30,157 +35,149 @@ */ public class ServerModelResolver extends io.swagger.v3.core.jackson.ModelResolver { + private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName()); + + public ServerModelResolver() + { + super(io.swagger.v3.core.util.Json.mapper()); + } + + /** + * @param mapper + */ + public ServerModelResolver(ObjectMapper mapper) + { + super(mapper); + } + + /* + * (non-Javadoc) + * @see io.swagger.v3.core.jackson.ModelResolver#resolve(io.swagger.v3.core. + * converter.AnnotatedType, + * io.swagger.v3.core.converter.ModelConverterContext, java.util.Iterator) + */ + @Override + public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context, Iterator next) + { + JavaType classType = TypeFactory.defaultInstance().constructType(annotatedType.getType()); + Class rawClass = classType.getRawClass(); + JavaType resolvedType = classType; + + if ((rawClass != null) &&!resolvedType.isPrimitive()) + { + // log.debug("resolvedType in " + resolvedType); + if (rawClass.isAssignableFrom(ServerResponse.class)) + { + resolvedType = classType.containedType(0); + } + else if (rawClass.isAssignableFrom(CompletableFuture.class)) + { + Class futureCls = classType.containedType(0).getRawClass(); + + if (futureCls.isAssignableFrom(ServerResponse.class)) + { + // log.debug("class is assignable from ServerResponse"); + final JavaType futureType = TypeFactory.defaultInstance().constructType(classType.containedType(0)); + + resolvedType = futureType.containedType(0); + } + else + { + // log.debug("class is NOT assignable from ServerResponse"); + resolvedType = classType.containedType(0); + } + } + + if (resolvedType != null) + { + if (resolvedType.getTypeName().contains("java.lang.Void")) + { + resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.lang.Void.class.getName()); + } + else if (resolvedType.getTypeName().contains("Optional")) + { + if (resolvedType.getTypeName().contains("java.nio.file.Path")) + { + resolvedType = TypeFactory.defaultInstance().constructParametricType(Optional.class, File.class); + } + + if (resolvedType.getTypeName().contains("ByteBuffer")) + { + resolvedType = TypeFactory.defaultInstance().constructParametricType(Optional.class, File.class); + } + } + else + { + if (resolvedType.getTypeName().contains("java.nio.file.Path")) + { + resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName()); + } + + if (resolvedType.getTypeName().contains("ByteBuffer")) + { + resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName()); + } + } + + annotatedType.setType(resolvedType); + + // log.debug("resolvedType out " + resolvedType); + } + } + + try { + + // log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName()); + return super.resolve(annotatedType, context, next); + + } catch (Exception e) { + + log.error("Error processing " + annotatedType + " " + classType + " " + annotatedType.getName(), e); + + return null; + } + } + + /* + * (non-Javadoc) + * @see + * io.swagger.v3.core.jackson.ModelResolver#resolveRequiredProperties(com. + * fasterxml.jackson.databind.introspect.Annotated, + * java.lang.annotation.Annotation[], + * io.swagger.v3.oas.annotations.media.Schema) + */ + @Override + protected List resolveRequiredProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) + { + // TODO Auto-generated method stub + return super.resolveRequiredProperties(a, annotations, schema); + } + + /* + * (non-Javadoc) + * @see + * io.swagger.v3.core.jackson.ModelResolver#shouldIgnoreClass(java.lang. + * reflect.Type) + */ + @Override + protected boolean shouldIgnoreClass(Type type) + { + // System.out.println("should ignore " + type); + JavaType classType = TypeFactory.defaultInstance().constructType(type); + String canonicalName = classType.toCanonical(); + + if (canonicalName.startsWith("io.undertow") + || canonicalName.startsWith("org.xnio") + || canonicalName.equals("io.sinistral.proteus.server.ServerRequest") + || canonicalName.contains(java.lang.Void.class.getName())) + { + return true; + } + + // TODO Auto-generated method stub + return super.shouldIgnoreClass(type); + } +} + - private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName()); - - - public ServerModelResolver() - { - super(io.swagger.v3.core.util.Json.mapper()); - } - - /** - * @param mapper - */ - public ServerModelResolver(ObjectMapper mapper) - { - super(mapper); - - } - - /* - * (non-Javadoc) - * @see io.swagger.v3.core.jackson.ModelResolver#resolve(io.swagger.v3.core. - * converter.AnnotatedType, - * io.swagger.v3.core.converter.ModelConverterContext, java.util.Iterator) - */ - @Override - public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context, Iterator next) - { - - JavaType classType = TypeFactory.defaultInstance().constructType(annotatedType.getType()); - - Class rawClass = classType.getRawClass(); - - JavaType resolvedType = classType; - - if (rawClass != null && !resolvedType.isPrimitive()) - { - //log.debug("resolvedType in " + resolvedType); - - if (rawClass.isAssignableFrom(ServerResponse.class)) - { - resolvedType = classType.containedType(0); - } - else if (rawClass.isAssignableFrom(CompletableFuture.class)) - { - Class futureCls = classType.containedType(0).getRawClass(); - - if (futureCls.isAssignableFrom(ServerResponse.class)) - { - //log.debug("class is assignable from ServerResponse"); - final JavaType futureType = TypeFactory.defaultInstance().constructType(classType.containedType(0)); - resolvedType = futureType.containedType(0); - } - else - { - //log.debug("class is NOT assignable from ServerResponse"); - resolvedType = classType.containedType(0); - } - } - - if(resolvedType != null) - { - - - if (resolvedType.getTypeName().contains("java.lang.Void")) - { - resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.lang.Void.class.getName()); - } - else if(resolvedType.getTypeName().contains("Optional")) - { - if (resolvedType.getTypeName().contains("java.nio.file.Path")) - { - resolvedType = TypeFactory.defaultInstance().constructParametricType(Optional.class, File.class); - } - - if (resolvedType.getTypeName().contains("ByteBuffer")) - { - resolvedType = TypeFactory.defaultInstance().constructParametricType(Optional.class, File.class); - } - } - else - { - if (resolvedType.getTypeName().contains("java.nio.file.Path")) - { - resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName()); - } - - if (resolvedType.getTypeName().contains("ByteBuffer")) - { - resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName()); - } - - } - - annotatedType.setType(resolvedType); - - //log.debug("resolvedType out " + resolvedType); - } - - } - - try - { - //log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName()); - - return super.resolve(annotatedType, context, next); - - } catch (Exception e) - { - log.error("Error processing " + annotatedType + " " + classType + " " + annotatedType.getName(), e); - return null; - } - - } - - /* - * (non-Javadoc) - * @see - * io.swagger.v3.core.jackson.ModelResolver#resolveRequiredProperties(com. - * fasterxml.jackson.databind.introspect.Annotated, - * java.lang.annotation.Annotation[], - * io.swagger.v3.oas.annotations.media.Schema) - */ - @Override - protected List resolveRequiredProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) - { - // TODO Auto-generated method stub - return super.resolveRequiredProperties(a, annotations, schema); - } - - /* - * (non-Javadoc) - * @see - * io.swagger.v3.core.jackson.ModelResolver#shouldIgnoreClass(java.lang. - * reflect.Type) - */ - @Override - protected boolean shouldIgnoreClass(Type type) - { - //System.out.println("should ignore " + type); - - JavaType classType = TypeFactory.defaultInstance().constructType(type); - - String canonicalName = classType.toCanonical(); - - if (canonicalName.startsWith("io.undertow") || canonicalName.startsWith("org.xnio") || canonicalName.equals("io.sinistral.proteus.server.ServerRequest") || canonicalName.contains(java.lang.Void.class.getName())) - { - return true; - } - - // TODO Auto-generated method stub - return super.shouldIgnoreClass(type); - } -} diff --git a/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerParameterExtension.java b/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerParameterExtension.java index 3300321..c88cb96 100644 --- a/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerParameterExtension.java +++ b/src/main/java/io/sinistral/proteus/server/tools/openapi/ServerParameterExtension.java @@ -1,10 +1,12 @@ + /** - * + * */ package io.sinistral.proteus.server.tools.openapi; import java.lang.annotation.Annotation; import java.lang.reflect.Type; + import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -41,351 +43,346 @@ /** * @author jbauer */ - public class ServerParameterExtension extends AbstractOpenAPIExtension { - - private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerParameterExtension.class.getCanonicalName()); - - private static String QUERY_PARAM = "query"; - private static String HEADER_PARAM = "header"; - private static String COOKIE_PARAM = "cookie"; - private static String PATH_PARAM = "path"; - private static String FORM_PARAM = "form"; - - final ObjectMapper mapper = Json.mapper(); - - @Override - public ResolvedParameter extractParameters( List annotations, - Type type, - Set typesToSkip, - Components components, - javax.ws.rs.Consumes classConsumes, - javax.ws.rs.Consumes methodConsumes, - boolean includeRequestBody, - JsonView jsonViewAnnotation, - Iterator chain) - { - - if (shouldIgnoreType(type, typesToSkip)) - { - return new ResolvedParameter(); - } - - JavaType javaType = constructType(type); - - boolean isRequired = true; - - if (isOptionalType(javaType)) - { - isRequired = false; - } - - Parameter parameter = null; - - for (Annotation annotation : annotations) - { - if (annotation instanceof QueryParam) - { - QueryParam param = (QueryParam) annotation; - Parameter qp = new Parameter(); - qp.setIn(QUERY_PARAM); - qp.setName(param.value()); - parameter = qp; - } - else if (annotation instanceof PathParam) - { - PathParam param = (PathParam) annotation; - Parameter pp = new Parameter(); - pp.setIn(PATH_PARAM); - pp.setName(param.value()); - parameter = pp; - } - else if (annotation instanceof MatrixParam) - { - MatrixParam param = (MatrixParam) annotation; - Parameter pp = new Parameter(); - pp.setIn(PATH_PARAM); - pp.setStyle(Parameter.StyleEnum.MATRIX); - pp.setName(param.value()); - parameter = pp; - } - else if (annotation instanceof HeaderParam) - { - HeaderParam param = (HeaderParam) annotation; - Parameter pp = new Parameter(); - pp.setIn(HEADER_PARAM); - pp.setName(param.value()); - parameter = pp; - } - else if (annotation instanceof CookieParam) - { - CookieParam param = (CookieParam) annotation; - Parameter pp = new Parameter(); - pp.setIn(COOKIE_PARAM); - pp.setName(param.value()); - parameter = pp; - } - else if (annotation instanceof io.swagger.v3.oas.annotations.Parameter) - { - if (((io.swagger.v3.oas.annotations.Parameter) annotation).hidden()) - { - - return new ResolvedParameter(); - } - if (parameter == null) - { - parameter = new Parameter(); - } - } - else - { - - List formParameters = new ArrayList<>(); - List parameters = new ArrayList<>(); - if (handleAdditionalAnnotation( - parameters, formParameters, annotation, type, typesToSkip, classConsumes, methodConsumes, components, includeRequestBody, - jsonViewAnnotation)) - { - ResolvedParameter extractParametersResult = new ResolvedParameter(); - extractParametersResult.parameters.addAll(parameters); - extractParametersResult.formParameters.addAll(formParameters); - } - - } - } - - List parameters = new ArrayList<>(); - ResolvedParameter extractParametersResult = new ResolvedParameter(); - - if (parameter != null && StringUtils.isNotBlank(parameter.getIn())) - { - parameter.setRequired(isRequired); - parameters.add(parameter); - } - else if (includeRequestBody) - { - Parameter unknownParameter = ParameterProcessor.applyAnnotations( - null, - type, - annotations, - components, - classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation); - if (unknownParameter != null) - { - - if (StringUtils.isNotBlank(unknownParameter.getIn()) && !"form".equals(unknownParameter.getIn())) - { - extractParametersResult.parameters.add(unknownParameter); - } - else if ("form".equals(unknownParameter.getIn())) - { - unknownParameter.setIn(null); - extractParametersResult.formParameters.add(unknownParameter); - } - else - { // return as request body - extractParametersResult.requestBody = unknownParameter; - - } - } - } - for (Parameter p : parameters) - { - - Parameter processedParameter = ParameterProcessor.applyAnnotations( - p, - type, - annotations, - components, - classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), - jsonViewAnnotation); - - if (processedParameter != null) - { - - extractParametersResult.parameters.add(processedParameter); - } - } - - return extractParametersResult; - } - - public boolean isOptionalType(JavaType propType) - { - return Arrays.asList("com.google.common.base.Optional", "java.util.Optional") - .contains(propType.getRawClass().getCanonicalName()); - } - - /** - * Adds additional annotation processing support - * @param parameters - * @param annotation - * @param type - * @param typesToSkip - */ - - private boolean handleAdditionalAnnotation( List parameters, List formParameters, Annotation annotation, - final Type type, Set typesToSkip, javax.ws.rs.Consumes classConsumes, - javax.ws.rs.Consumes methodConsumes, Components components, boolean includeRequestBody, JsonView jsonViewAnnotation) - { - boolean processed = false; - if (BeanParam.class.isAssignableFrom(annotation.getClass())) - { - - // Use Jackson's logic for processing Beans - JavaType javaType = constructType(type); - - final BeanDescription beanDesc = mapper.getSerializationConfig().introspect(javaType); - final List properties = beanDesc.findProperties(); - -// if(extracted.size() == 0) -// { -// System.out.println("Unable to find parameters..."); -// -// Parameter processedParam = ParameterProcessor.applyAnnotations( -// p, -// paramType, -// paramAnnotations, -// components, -// classConsumes == null ? new String[0] : classConsumes.value(), -// methodConsumes == null ? new String[0] : methodConsumes.value(), -// jsonViewAnnotation); -// } - - for (final BeanPropertyDefinition propDef : properties) - { - final AnnotatedField field = propDef.getField(); - final AnnotatedMethod setter = propDef.getSetter(); - final AnnotatedMethod getter = propDef.getGetter(); - final List paramAnnotations = new ArrayList(); - final Iterator extensions = OpenAPIExtensions.chain(); - Type paramType = null; - - // Gather the field's details - if (field != null) - { - paramType = field.getType(); - - AnnotationMap annotationMap = field.getAllAnnotations(); - - for (final Annotation fieldAnnotation : annotationMap.annotations()) - { - if (!paramAnnotations.contains(fieldAnnotation)) - { - paramAnnotations.add(fieldAnnotation); - } - } - } - - // Gather the setter's details but only the ones we need - if (setter != null) - { - // Do not set the param class/type from the setter if the - // values are already identified - if (paramType == null) - { - // paramType will stay null if there is no parameter - paramType = setter.getParameterType(0); - } - - AnnotationMap annotationMap = setter.getAllAnnotations(); - - for (final Annotation fieldAnnotation : annotationMap.annotations()) - { - if (!paramAnnotations.contains(fieldAnnotation)) - { - paramAnnotations.add(fieldAnnotation); - } - } - } - - // Gather the getter's details but only the ones we need - if (getter != null) - { - // Do not set the param class/type from the getter if the - // values are already identified - if (paramType == null) - { - paramType = getter.getType(); - } - - AnnotationMap annotationMap = getter.getAllAnnotations(); - - for (final Annotation fieldAnnotation : annotationMap.annotations()) - { - if (!paramAnnotations.contains(fieldAnnotation)) - { - paramAnnotations.add(fieldAnnotation); - } - } - } - - if (paramType == null) - { - continue; - } - - // Re-process all Bean fields and let the default - // swagger-jaxrs/swagger-jersey-jaxrs processors do their thing - ResolvedParameter resolvedParameter = extensions.next().extractParameters( - paramAnnotations, - paramType, - typesToSkip, - components, - classConsumes, - methodConsumes, - includeRequestBody, - jsonViewAnnotation, - extensions); - List extractedParameters = resolvedParameter.parameters; - for (Parameter p : extractedParameters) - { - Parameter processedParam = ParameterProcessor.applyAnnotations( - p, - paramType, - paramAnnotations, - components, - classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), - jsonViewAnnotation); - if (processedParam != null) - { - - log.debug("added new parameters: " + processedParam); - parameters.add(processedParam); - } - } - - List extractedFormParameters = resolvedParameter.formParameters; - for (Parameter p : extractedFormParameters) - { - Parameter processedParam = ParameterProcessor.applyAnnotations( - p, - paramType, - paramAnnotations, - components, - classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), - jsonViewAnnotation); - if (processedParam != null) - { - formParameters.add(processedParam); - } - } - - processed = true; - } - } - return processed; - } - - @Override - protected boolean shouldIgnoreClass(Class cls) - { - return cls.getName().startsWith("javax.ws.rs.") || cls.getName().startsWith("io.undertow"); - - } + private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerParameterExtension.class.getCanonicalName()); + private static String QUERY_PARAM = "query"; + private static String HEADER_PARAM = "header"; + private static String COOKIE_PARAM = "cookie"; + private static String PATH_PARAM = "path"; + private static String FORM_PARAM = "form"; + final ObjectMapper mapper = Json.mapper(); + + @Override + public ResolvedParameter extractParameters(List annotations, Type type, Set typesToSkip, Components components, javax.ws.rs.Consumes classConsumes, + javax.ws.rs.Consumes methodConsumes, boolean includeRequestBody, JsonView jsonViewAnnotation, Iterator chain) + { + if (shouldIgnoreType(type, typesToSkip)) + { + return new ResolvedParameter(); + } + + JavaType javaType = constructType(type); + boolean isRequired = true; + + if (isOptionalType(javaType)) + { + isRequired = false; + } + + Parameter parameter = null; + + for (Annotation annotation : annotations) + { + if (annotation instanceof QueryParam) + { + QueryParam param = (QueryParam) annotation; + Parameter qp = new Parameter(); + + qp.setIn(QUERY_PARAM); + qp.setName(param.value()); + + parameter = qp; + } + else if (annotation instanceof PathParam) + { + PathParam param = (PathParam) annotation; + Parameter pp = new Parameter(); + + pp.setIn(PATH_PARAM); + pp.setName(param.value()); + + parameter = pp; + } + else if (annotation instanceof MatrixParam) + { + MatrixParam param = (MatrixParam) annotation; + Parameter pp = new Parameter(); + + pp.setIn(PATH_PARAM); + pp.setStyle(Parameter.StyleEnum.MATRIX); + pp.setName(param.value()); + + parameter = pp; + } + else if (annotation instanceof HeaderParam) + { + HeaderParam param = (HeaderParam) annotation; + Parameter pp = new Parameter(); + + pp.setIn(HEADER_PARAM); + pp.setName(param.value()); + + parameter = pp; + } + else if (annotation instanceof CookieParam) + { + CookieParam param = (CookieParam) annotation; + Parameter pp = new Parameter(); + + pp.setIn(COOKIE_PARAM); + pp.setName(param.value()); + + parameter = pp; + } + else if (annotation instanceof io.swagger.v3.oas.annotations.Parameter) + { + if (((io.swagger.v3.oas.annotations.Parameter) annotation).hidden()) + { + return new ResolvedParameter(); + } + + if (parameter == null) + { + parameter = new Parameter(); + } + } + else + { + List formParameters = new ArrayList<>(); + List parameters = new ArrayList<>(); + + if (handleAdditionalAnnotation(parameters, formParameters, annotation, type, typesToSkip, classConsumes, methodConsumes, components, includeRequestBody, jsonViewAnnotation)) + { + ResolvedParameter extractParametersResult = new ResolvedParameter(); + + extractParametersResult.parameters.addAll(parameters); + extractParametersResult.formParameters.addAll(formParameters); + } + } + } + + List parameters = new ArrayList<>(); + ResolvedParameter extractParametersResult = new ResolvedParameter(); + + if ((parameter != null) && StringUtils.isNotBlank(parameter.getIn())) + { + parameter.setRequired(isRequired); + parameters.add(parameter); + } + else if (includeRequestBody) + { + Parameter unknownParameter = ParameterProcessor.applyAnnotations(null, + type, + annotations, + components, + (classConsumes == null) + ? new String[0] + : classConsumes.value(), + (methodConsumes == null) + ? new String[0] + : methodConsumes.value(), + jsonViewAnnotation); + + if (unknownParameter != null) + { + if (StringUtils.isNotBlank(unknownParameter.getIn()) &&!"form".equals(unknownParameter.getIn())) + { + extractParametersResult.parameters.add(unknownParameter); + } + else if ("form".equals(unknownParameter.getIn())) + { + unknownParameter.setIn(null); + extractParametersResult.formParameters.add(unknownParameter); + } + else + { + // return as request body + extractParametersResult.requestBody = unknownParameter; + } + } + } + + for (Parameter p : parameters) + { + Parameter processedParameter = ParameterProcessor.applyAnnotations(p, + type, + annotations, + components, + (classConsumes == null) + ? new String[0] + : classConsumes.value(), + (methodConsumes == null) + ? new String[0] + : methodConsumes.value(), + jsonViewAnnotation); + + if (processedParameter != null) + { + extractParametersResult.parameters.add(processedParameter); + } + } + + return extractParametersResult; + } + + /** + * Adds additional annotation processing support + * @param parameters + * @param annotation + * @param type + * @param typesToSkip + */ + private boolean handleAdditionalAnnotation(List parameters, List formParameters, Annotation annotation, final Type type, Set typesToSkip, + javax.ws.rs.Consumes classConsumes, javax.ws.rs.Consumes methodConsumes, Components components, boolean includeRequestBody, JsonView jsonViewAnnotation) + { + boolean processed = false; + + if (BeanParam.class.isAssignableFrom(annotation.getClass())) + { + // Use Jackson's logic for processing Beans + JavaType javaType = constructType(type); + final BeanDescription beanDesc = mapper.getSerializationConfig().introspect(javaType); + final List properties = beanDesc.findProperties(); + + for (final BeanPropertyDefinition propDef : properties) + { + final AnnotatedField field = propDef.getField(); + final AnnotatedMethod setter = propDef.getSetter(); + final AnnotatedMethod getter = propDef.getGetter(); + final List paramAnnotations = new ArrayList(); + final Iterator extensions = OpenAPIExtensions.chain(); + Type paramType = null; + + // Gather the field's details + if (field != null) + { + paramType = field.getType(); + + AnnotationMap annotationMap = field.getAllAnnotations(); + + for (final Annotation fieldAnnotation : annotationMap.annotations()) + { + if (!paramAnnotations.contains(fieldAnnotation)) + { + paramAnnotations.add(fieldAnnotation); + } + } + } + + // Gather the setter's details but only the ones we need + if (setter != null) + { + // Do not set the param class/type from the setter if the + // values are already identified + if (paramType == null) + { + // paramType will stay null if there is no parameter + paramType = setter.getParameterType(0); + } + + AnnotationMap annotationMap = setter.getAllAnnotations(); + + for (final Annotation fieldAnnotation : annotationMap.annotations()) + { + if (!paramAnnotations.contains(fieldAnnotation)) + { + paramAnnotations.add(fieldAnnotation); + } + } + } + + // Gather the getter's details but only the ones we need + if (getter != null) + { + // Do not set the param class/type from the getter if the + // values are already identified + if (paramType == null) + { + paramType = getter.getType(); + } + + AnnotationMap annotationMap = getter.getAllAnnotations(); + + for (final Annotation fieldAnnotation : annotationMap.annotations()) + { + if (!paramAnnotations.contains(fieldAnnotation)) + { + paramAnnotations.add(fieldAnnotation); + } + } + } + + if (paramType == null) + { + continue; + } + + // Re-process all Bean fields and let the default + // swagger-jaxrs/swagger-jersey-jaxrs processors do their thing + ResolvedParameter resolvedParameter = extensions.next() + .extractParameters(paramAnnotations, + paramType, + typesToSkip, + components, + classConsumes, + methodConsumes, + includeRequestBody, + jsonViewAnnotation, + extensions); + List extractedParameters = resolvedParameter.parameters; + + for (Parameter p : extractedParameters) + { + Parameter processedParam = ParameterProcessor.applyAnnotations(p, + paramType, + paramAnnotations, + components, + (classConsumes == null) + ? new String[0] + : classConsumes.value(), + (methodConsumes == null) + ? new String[0] + : methodConsumes.value(), + jsonViewAnnotation); + + if (processedParam != null) + { + log.debug("added new parameters: " + processedParam); + parameters.add(processedParam); + } + } + + List extractedFormParameters = resolvedParameter.formParameters; + + for (Parameter p : extractedFormParameters) + { + Parameter processedParam = ParameterProcessor.applyAnnotations(p, + paramType, + paramAnnotations, + components, + (classConsumes == null) + ? new String[0] + : classConsumes.value(), + (methodConsumes == null) + ? new String[0] + : methodConsumes.value(), + jsonViewAnnotation); + + if (processedParam != null) + { + formParameters.add(processedParam); + } + } + + processed = true; + } + } + + return processed; + } + + @Override + protected boolean shouldIgnoreClass(Class cls) + { + return cls.getName().startsWith("javax.ws.rs.") || cls.getName().startsWith("io.undertow"); + } + + public boolean isOptionalType(JavaType propType) + { + return Arrays.asList("com.google.common.base.Optional", "java.util.Optional").contains(propType.getRawClass().getCanonicalName()); + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java index 33997ed..e22e30d 100644 --- a/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java +++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.server.tools.swagger; @@ -19,233 +20,204 @@ */ public class AnnotationHelper { - public static FormParam createFormParam(Parameter parameter) - { - - return new FormParam() - { - - @Override - public String value() - { - FormParam annotation = parameter.getAnnotation(FormParam.class); - - if(annotation != null) - { - return annotation.value(); - } - - return parameter.getName(); - } - - @Override - public Class annotationType() - { - return FormParam.class; - } - - }; - } - - public static QueryParam createQueryParam(Parameter parameter) - { - - return new QueryParam() - { - - @Override - public String value() - { - QueryParam annotation = parameter.getAnnotation(QueryParam.class); - - if(annotation != null) - { - return annotation.value(); - } - - return parameter.getName(); - } - - @Override - public Class annotationType() - { - return QueryParam.class; - } - }; - } - - public static PathParam createPathParam(Parameter parameter) - { - - return new PathParam() - { - - @Override - public String value() - { - PathParam annotation = parameter.getAnnotation(PathParam.class); - - if(annotation != null) - { - return annotation.value(); - } - - return parameter.getName(); - } - - @Override - public Class annotationType() - { - return PathParam.class; - } - }; - } - - public static ApiParam createApiParam(Parameter parameter) - { - - return new ApiParam() - { - - @Override - public Class annotationType() - { - // TODO Auto-generated method stub - return ApiParam.class; - } - - @Override - public String name() - { - QueryParam queryParam = parameter.getAnnotation(QueryParam.class); - FormParam formParam = parameter.getAnnotation(FormParam.class); - PathParam pathParam = parameter.getAnnotation(PathParam.class); - - if(queryParam != null) - { - return queryParam.value(); - } else if( pathParam != null ) - { - return pathParam.value(); - } - else if( formParam != null ) - { - return formParam.value(); - } - else - { - return parameter.getName(); - } - } - - @Override - public String value() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public String defaultValue() - { - try - { - DefaultValue defaultValue = parameter.getAnnotation(DefaultValue.class); - - return defaultValue.value(); - - } catch (NullPointerException e) - { - - } - - // TODO Auto-generated method stub - return null; - } - - @Override - public String allowableValues() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean required() - { - return !parameter.getParameterizedType().getTypeName().contains("java.util.Optional"); - } - - @Override - public String access() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean allowMultiple() - { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean hidden() - { - // TODO Auto-generated method stub - return false; - } - - @Override - public String example() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public Example examples() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public String type() - { - // TODO Auto-generated method stub - return null; - } - - @Override - public String format() - { - - return null; - } - - @Override - public boolean allowEmptyValue() - { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean readOnly() - { - // TODO Auto-generated method stub - return false; - } - - @Override - public String collectionFormat() - { - // TODO Auto-generated method stub - return null; - } - - }; - } + public static ApiParam createApiParam(Parameter parameter) + { + return new ApiParam() + { + @Override + public Class annotationType() + { + return ApiParam.class; + } + @Override + public String name() + { + QueryParam queryParam = parameter.getAnnotation(QueryParam.class); + FormParam formParam = parameter.getAnnotation(FormParam.class); + PathParam pathParam = parameter.getAnnotation(PathParam.class); + + if (queryParam != null) + { + return queryParam.value(); + } + else if (pathParam != null) + { + return pathParam.value(); + } + else if (formParam != null) + { + return formParam.value(); + } + else + { + return parameter.getName(); + } + } + @Override + public String value() + { + // TODO Auto-generated method stub + return null; + } + @Override + public String defaultValue() + { + try { + + DefaultValue defaultValue = parameter.getAnnotation(DefaultValue.class); + + return defaultValue.value(); + + } catch (NullPointerException e) {} + + // TODO Auto-generated method stub + return null; + } + @Override + public String allowableValues() + { + // TODO Auto-generated method stub + return null; + } + @Override + public boolean required() + { + return !parameter.getParameterizedType().getTypeName().contains("java.util.Optional"); + } + @Override + public String access() + { + // TODO Auto-generated method stub + return null; + } + @Override + public boolean allowMultiple() + { + // TODO Auto-generated method stub + return false; + } + @Override + public boolean hidden() + { + // TODO Auto-generated method stub + return false; + } + @Override + public String example() + { + // TODO Auto-generated method stub + return null; + } + @Override + public Example examples() + { + // TODO Auto-generated method stub + return null; + } + @Override + public String type() + { + // TODO Auto-generated method stub + return null; + } + @Override + public String format() + { + return null; + } + @Override + public boolean allowEmptyValue() + { + // TODO Auto-generated method stub + return false; + } + @Override + public boolean readOnly() + { + // TODO Auto-generated method stub + return false; + } + @Override + public String collectionFormat() + { + // TODO Auto-generated method stub + return null; + } + }; + } + + public static FormParam createFormParam(Parameter parameter) + { + return new FormParam() + { + @Override + public String value() + { + FormParam annotation = parameter.getAnnotation(FormParam.class); + + if (annotation != null) + { + return annotation.value(); + } + + return parameter.getName(); + } + @Override + public Class annotationType() + { + return FormParam.class; + } + }; + } + + public static PathParam createPathParam(Parameter parameter) + { + return new PathParam() + { + @Override + public String value() + { + PathParam annotation = parameter.getAnnotation(PathParam.class); + + if (annotation != null) + { + return annotation.value(); + } + + return parameter.getName(); + } + @Override + public Class annotationType() + { + return PathParam.class; + } + }; + } + + public static QueryParam createQueryParam(Parameter parameter) + { + return new QueryParam() + { + @Override + public String value() + { + QueryParam annotation = parameter.getAnnotation(QueryParam.class); + + if (annotation != null) + { + return annotation.value(); + } + + return parameter.getName(); + } + @Override + public Class annotationType() + { + return QueryParam.class; + } + }; + } } + + + diff --git a/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java index 9d13929..3e1e968 100644 --- a/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java +++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java @@ -411,7 +411,9 @@ private Swagger read(Class cls, String parentPath, String parentMethod, boole // can't continue without a valid http method httpMethod = (httpMethod == null) ? parentMethod : httpMethod; - if (httpMethod != null) { + + if (httpMethod != null) + { if (apiOperation != null) { for (String tag : apiOperation.tags()) { if (!"".equals(tag)) { diff --git a/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java index 8b51250..b5fb9f6 100644 --- a/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java +++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java @@ -1,10 +1,12 @@ + /** - * + * */ package io.sinistral.proteus.server.tools.swagger; import java.lang.annotation.Annotation; import java.lang.reflect.Type; + import java.util.Iterator; import java.util.List; import java.util.Set; @@ -21,58 +23,48 @@ */ public class ServerParameterExtension extends DefaultParameterExtension { + public ServerParameterExtension() + { + super(); + } - public ServerParameterExtension() - { - super(); - - } - - @Override - public List extractParameters(List annotations, Type type, Set typesToSkip, Iterator chain) - { - - if(type.getTypeName().contains("java.nio.ByteBuffer") || type.getTypeName().contains("java.nio.file.Path")) - { - type = java.io.File.class; - - } - - return super.extractParameters(annotations, type, typesToSkip, chain); - - } + @Override + protected JavaType constructType(Type type) + { + if (type.getTypeName().contains("java.nio.ByteBuffer") || type.getTypeName().contains("java.nio.file.Path")) + { + type = java.io.File.class; + } - - @Override - protected boolean shouldIgnoreType(Type type, Set typesToSkip) - { - - if( type.getTypeName().contains("io.sinistral.proteus.server.ServerRequest") - || type.getTypeName().contains("HttpServerExchange") - || type.getTypeName().contains("HttpHandler") - || type.getTypeName().contains("io.sinistral.proteus.server.ServerResponse") - || type.getTypeName().contains("io.undertow.server.session") - ) - { - return true; - } - - return super.shouldIgnoreType(type, typesToSkip); - } + return super.constructType(type); + } - - @Override - protected JavaType constructType(Type type) - { - - if(type.getTypeName().contains("java.nio.ByteBuffer") || type.getTypeName().contains("java.nio.file.Path")) - { - type = java.io.File.class; + @Override + public List extractParameters(List annotations, Type type, Set typesToSkip, Iterator chain) + { + if (type.getTypeName().contains("java.nio.ByteBuffer") || type.getTypeName().contains("java.nio.file.Path")) + { + type = java.io.File.class; + } - } - - return super.constructType(type); + return super.extractParameters(annotations, type, typesToSkip, chain); + } - } + @Override + protected boolean shouldIgnoreType(Type type, Set typesToSkip) + { + if (type.getTypeName().contains("io.sinistral.proteus.server.ServerRequest") + || type.getTypeName().contains("HttpServerExchange") + || type.getTypeName().contains("HttpHandler") + || type.getTypeName().contains("io.sinistral.proteus.server.ServerResponse") + || type.getTypeName().contains("io.undertow.server.session")) + { + return true; + } + return super.shouldIgnoreType(type, typesToSkip); + } } + + + diff --git a/src/main/java/io/sinistral/proteus/services/AssetsService.java b/src/main/java/io/sinistral/proteus/services/AssetsService.java index 440b4f1..46b0463 100644 --- a/src/main/java/io/sinistral/proteus/services/AssetsService.java +++ b/src/main/java/io/sinistral/proteus/services/AssetsService.java @@ -1,17 +1,21 @@ + /** - * + * */ package io.sinistral.proteus.services; import java.nio.file.Paths; + import java.util.Set; import java.util.function.Supplier; import com.google.inject.Inject; import com.google.inject.name.Named; + import com.typesafe.config.Config; import io.sinistral.proteus.server.endpoints.EndpointInfo; + import io.undertow.predicate.TruePredicate; import io.undertow.server.RoutingHandler; import io.undertow.server.handlers.resource.FileResourceManager; @@ -24,60 +28,61 @@ */ public class AssetsService extends BaseService implements Supplier { + @Inject + @Named("registeredEndpoints") + protected Set registeredEndpoints; + @Inject + protected RoutingHandler router; + @Inject + @Named("assets") + protected Config serviceConfig; + + /** + * + */ + public AssetsService() + { + } - @Inject - @Named("registeredEndpoints") - protected Set registeredEndpoints; - - @Inject - protected RoutingHandler router; - - @Inject - @Named("assets") - protected Config serviceConfig; - - /** - * - */ - public AssetsService() - { - } - - public RoutingHandler get() - { - RoutingHandler router = new RoutingHandler(); - - final String assetsPath = serviceConfig.getString("path"); - final String assetsDirectoryName = serviceConfig.getString("dir") ; - final Integer assetsCacheTime = serviceConfig.getInt("cache.time"); - - final FileResourceManager fileResourceManager = new FileResourceManager(Paths.get(assetsDirectoryName).toFile()); - - router.add(Methods.GET, assetsPath + "/*", io.undertow.Handlers.rewrite("regex('" + assetsPath + "/(.*)')", "/$1", getClass().getClassLoader(), new ResourceHandler(fileResourceManager) - .setCachable(TruePredicate.instance()) - .setCacheTime(assetsCacheTime) - )); - - this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withProduces("*/*").withPathTemplate(assetsPath).withControllerName(this.getClass().getSimpleName()).withMethod(Methods.GET).build()); - - return router; - } - - - @Override - protected void startUp() throws Exception - { - super.startUp(); - - router.addAll(this.get()); - } - - - @Override - protected void shutDown() throws Exception - { - super.shutDown(); - - } + @Override + protected void shutDown() throws Exception + { + super.shutDown(); + } + @Override + protected void startUp() throws Exception + { + super.startUp(); + router.addAll(this.get()); + } + + public RoutingHandler get() + { + RoutingHandler router = new RoutingHandler(); + final String assetsPath = serviceConfig.getString("path"); + final String assetsDirectoryName = serviceConfig.getString("dir"); + final Integer assetsCacheTime = serviceConfig.getInt("cache.time"); + final FileResourceManager fileResourceManager = new FileResourceManager(Paths.get(assetsDirectoryName).toFile()); + + router.add(Methods.GET, + assetsPath + "/*", + io.undertow.Handlers.rewrite("regex('" + assetsPath + "/(.*)')", + "/$1", + getClass().getClassLoader(), + new ResourceHandler(fileResourceManager).setCachable(TruePredicate.instance()).setCacheTime(assetsCacheTime))); + + this.registeredEndpoints.add(EndpointInfo.builder() + .withConsumes("*/*") + .withProduces("*/*") + .withPathTemplate(assetsPath) + .withControllerName(this.getClass().getSimpleName()) + .withMethod(Methods.GET) + .build()); + + return router; + } } + + + diff --git a/src/main/java/io/sinistral/proteus/services/BaseService.java b/src/main/java/io/sinistral/proteus/services/BaseService.java index bacafcd..bedf807 100644 --- a/src/main/java/io/sinistral/proteus/services/BaseService.java +++ b/src/main/java/io/sinistral/proteus/services/BaseService.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.services; @@ -11,61 +12,59 @@ import com.google.inject.Inject; import com.google.inject.Module; import com.google.inject.Singleton; + import com.typesafe.config.Config; /** * An abstract base class for a Proteus service. - * + * * @author jbauer * */ @Singleton -public abstract class BaseService extends AbstractIdleService implements Module +public abstract class BaseService extends AbstractIdleService implements Module { - private static Logger log = LoggerFactory.getLogger(BaseService.class.getCanonicalName()); + private static Logger log = LoggerFactory.getLogger(BaseService.class.getCanonicalName()); - /* (non-Javadoc) - * @see com.google.inject.AbstractModule#configure() - */ - - @Inject - protected Config config; - - - public BaseService() - { - - } + /* + * (non-Javadoc) + * @see com.google.inject.AbstractModule#configure() + */ + @Inject + protected Config config; + public BaseService() + { + } - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#startUp() - */ - @Override - protected void startUp() throws Exception - { - log.info("Starting " + this.getClass().getSimpleName() ); - } + /* + * (non-Javadoc) + * @see com.google.inject.Module#configure(com.google.inject.Binder) + */ + public void configure(Binder binder) + { + } + /* + * (non-Javadoc) + * @see com.google.common.util.concurrent.AbstractIdleService#shutDown() + */ + @Override + protected void shutDown() throws Exception + { + log.info("Stopping " + this.getClass().getSimpleName()); + } - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#shutDown() - */ - @Override - protected void shutDown() throws Exception - { - log.info("Stopping " + this.getClass().getSimpleName() ); - } + /* + * (non-Javadoc) + * @see com.google.common.util.concurrent.AbstractIdleService#startUp() + */ + @Override + protected void startUp() throws Exception + { + log.info("Starting " + this.getClass().getSimpleName()); + } +} - /* (non-Javadoc) - * @see com.google.inject.Module#configure(com.google.inject.Binder) - */ - public void configure(Binder binder) - { - - - } - -} diff --git a/src/main/java/io/sinistral/proteus/services/OpenAPIService.java b/src/main/java/io/sinistral/proteus/services/OpenAPIService.java index b2e842e..badc1ac 100644 --- a/src/main/java/io/sinistral/proteus/services/OpenAPIService.java +++ b/src/main/java/io/sinistral/proteus/services/OpenAPIService.java @@ -1,15 +1,17 @@ - package io.sinistral.proteus.services; import java.io.File; import java.io.InputStream; + import java.net.URL; + import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; + import java.util.Collections; import java.util.List; import java.util.Map; @@ -23,6 +25,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,15 +33,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; + import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; + import com.typesafe.config.Config; import io.sinistral.proteus.server.endpoints.EndpointInfo; import io.sinistral.proteus.server.tools.openapi.Reader; import io.sinistral.proteus.server.tools.openapi.ServerModelResolver; import io.sinistral.proteus.server.tools.openapi.ServerParameterExtension; + import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions; @@ -51,6 +57,7 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; + import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; @@ -62,374 +69,305 @@ import io.undertow.util.Headers; import io.undertow.util.Methods; - @Singleton -public class OpenAPIService extends BaseService implements Supplier +public class OpenAPIService extends BaseService implements Supplier { - - private static Logger log = LoggerFactory.getLogger(OpenAPIService.class.getCanonicalName()); - - protected final String resourcePathPrefix = "openapi"; - - - @Inject - @Named("openapi.resourcePrefix") - protected String resourcePrefix; - - @Inject - @Named("openapi.basePath") - protected String basePath; - - @Inject - @Named("openapi.specFilename") - protected String specFilename; - - @Inject - @Named("openapi") - protected Config openAPIConfig; - - @Inject - @Named("application.name") - protected String applicationName; - - @Inject - @Named("openapi.port") - protected Integer port; - - @Inject - @Named("application.path") - protected String applicationPath; - - @Inject - protected RoutingHandler router; - - @Inject - @Named("registeredEndpoints") - protected Set registeredEndpoints; - - @Inject - @Named("registeredControllers") - protected Set> registeredControllers; - - @Inject - @Named("registeredHandlerWrappers") - protected Map registeredHandlerWrappers; - - protected ObjectMapper mapper = null; - - protected ObjectWriter writer = null; - - protected ObjectMapper yamlMapper = null; - - protected Path resourcePath = null; - - protected ClassLoader serviceClassLoader = null; - - protected OpenAPI openApi = null; - - protected String spec = null; - - protected String indexHTML = null; + private static Logger log = LoggerFactory.getLogger(OpenAPIService.class.getCanonicalName()); + protected final String resourcePathPrefix = "openapi"; + protected ObjectMapper mapper = null; + protected ObjectWriter writer = null; + protected ObjectMapper yamlMapper = null; + protected Path resourcePath = null; + protected ClassLoader serviceClassLoader = null; + protected OpenAPI openApi = null; + protected String spec = null; + protected String indexHTML = null; + @Inject + @Named("openapi.resourcePrefix") + protected String resourcePrefix; + @Inject + @Named("openapi.basePath") + protected String basePath; + @Inject + @Named("openapi.specFilename") + protected String specFilename; + @Inject + @Named("openapi") + protected Config openAPIConfig; + @Inject + @Named("application.name") + protected String applicationName; + @Inject + @Named("openapi.port") + protected Integer port; + @Inject + @Named("application.path") + protected String applicationPath; + @Inject + protected RoutingHandler router; + @Inject + @Named("registeredEndpoints") + protected Set registeredEndpoints; + @Inject + @Named("registeredControllers") + protected Set> registeredControllers; + @Inject + @Named("registeredHandlerWrappers") + protected Map registeredHandlerWrappers; + + public OpenAPIService() + { + mapper = Json.mapper(); + + mapper.registerModule(new Jdk8Module()); + + yamlMapper = Yaml.mapper(); + writer = Yaml.pretty(); + } + + public void generateHTML() + { + try { + + try (InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(resourcePrefix + "/index.html")) { + + byte[] templateBytes = IOUtils.toByteArray(templateInputStream); + String templateString = new String(templateBytes, Charset.defaultCharset()); + + templateString = templateString.replaceAll("\\{\\{ basePath \\}\\}", basePath); + templateString = templateString.replaceAll("\\{\\{ title \\}\\}", applicationName + " Swagger UI"); + this.indexHTML = templateString; + } + + URL url = this.getClass().getClassLoader().getResource(resourcePrefix); + + if (url.toExternalForm().contains("!")) + { + log.debug("Copying OpenAPI resources..."); + + String jarPathString = url.toExternalForm().substring(0, url.toExternalForm().indexOf("!")).replaceAll("file:", "").replaceAll("jar:", ""); + File srcFile = new File(jarPathString); + + try (JarFile jarFile = new JarFile(srcFile, false)) { + + String appName = config.getString("application.name").replaceAll(" ", "_"); + Path tmpDirParent = Files.createTempDirectory(appName); + Path tmpDir = tmpDirParent.resolve("openapi/"); + + if (tmpDir.toFile().exists()) + { + log.debug("Deleting existing OpenAPI directory at " + tmpDir); + + try { + FileUtils.deleteDirectory(tmpDir.toFile()); + } catch (java.lang.IllegalArgumentException e) { + + log.debug("Tmp directory is not a directory..."); + tmpDir.toFile().delete(); + } + } + + java.nio.file.Files.createDirectory(tmpDir); + + this.resourcePath = tmpDir; + + jarFile.stream().filter(ze -> ze.getName().endsWith("js") || ze.getName().endsWith("css") || ze.getName().endsWith("map") || ze.getName().endsWith("html")).forEach(ze -> { + try { + + final InputStream entryInputStream = jarFile.getInputStream(ze); + String filename = ze.getName().substring(resourcePrefix.length() + 1); + Path entryFilePath = tmpDir.resolve(filename); + + java.nio.file.Files.createDirectories(entryFilePath.getParent()); + java.nio.file.Files.copy(entryInputStream, entryFilePath, StandardCopyOption.REPLACE_EXISTING); + + } catch (Exception e) { + log.error(e.getMessage() + " for entry " + ze.getName()); + } + } ); + } + } + else + { + this.resourcePath = Paths.get(this.getClass().getClassLoader().getResource(this.resourcePrefix).toURI()); + this.serviceClassLoader = this.getClass().getClassLoader(); + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @SuppressWarnings("rawtypes") + public void generateSpec() throws Exception + { + Set> classes = this.registeredControllers; + + OpenAPIExtensions.setExtensions(Collections.singletonList(new ServerParameterExtension())); + + OpenAPI openApi = new OpenAPI(); + Info info = mapper.convertValue(openAPIConfig.getValue("info").unwrapped(), Info.class); + + openApi.setInfo(info); + + Map securitySchemes = mapper.convertValue(openAPIConfig.getValue("securitySchemes").unwrapped(), + new TypeReference>(){}); + + if (openApi.getComponents() == null) + { + openApi.setComponents(new Components()); + } + + openApi.getComponents().setSecuritySchemes(securitySchemes); + + List servers = mapper.convertValue(openAPIConfig.getValue("servers").unwrapped(),new TypeReference>(){}); + + openApi.setServers(servers); + + SwaggerConfiguration config = new SwaggerConfiguration().resourceClasses(classes.stream().map(c -> c.getName()).collect(Collectors.toSet())).openAPI(openApi); + + config.setModelConverterClassess(Collections.singleton(ServerModelResolver.class.getName())); + + OpenApiContext ctx = new GenericOpenApiContext().openApiConfiguration(config) + .openApiReader(new Reader(config)) + .openApiScanner(new JaxrsApplicationAndAnnotationScanner().openApiConfiguration(config)) + .init(); + + openApi = ctx.read(); + this.openApi = openApi; + this.spec = writer.writeValueAsString(openApi); + } - public OpenAPIService( ) - { - mapper = Json.mapper(); - mapper.registerModule(new Jdk8Module()); - - yamlMapper = Yaml.mapper(); - - - writer = Yaml.pretty(); - } - - - @SuppressWarnings("rawtypes") - public void generateSpec() throws Exception - { - - Set> classes = this.registeredControllers; - - OpenAPIExtensions.setExtensions(Collections.singletonList(new ServerParameterExtension())); - - OpenAPI openApi = new OpenAPI(); - - Info info = mapper.convertValue(openAPIConfig.getValue("info").unwrapped(), Info.class); - - openApi.setInfo(info); - - Map securitySchemes = mapper.convertValue(openAPIConfig.getValue("securitySchemes").unwrapped(), new TypeReference>(){}); - - if(openApi.getComponents() == null) - { - openApi.setComponents(new Components()); - } - - openApi.getComponents().setSecuritySchemes(securitySchemes); - - List servers = mapper.convertValue(openAPIConfig.getValue("servers").unwrapped(), new TypeReference>(){}); - - openApi.setServers(servers); - - SwaggerConfiguration config = new SwaggerConfiguration() - .resourceClasses(classes.stream().map( c -> c.getName()).collect(Collectors.toSet())) - .openAPI(openApi); - - - config.setModelConverterClassess(Collections.singleton(ServerModelResolver.class.getName())); - - OpenApiContext ctx = new GenericOpenApiContext() - .openApiConfiguration(config) - .openApiReader(new Reader(config)) - .openApiScanner(new JaxrsApplicationAndAnnotationScanner().openApiConfiguration(config)) - .init(); - - openApi = ctx.read(); - - this.openApi = openApi; - - this.spec = writer.writeValueAsString(openApi); - - } + @Override + protected void shutDown() throws Exception + { + + } - public void generateHTML() - { - try - { - - try(InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(resourcePrefix + "/index.html")) - { - byte[] templateBytes = IOUtils.toByteArray(templateInputStream); - - String templateString = new String(templateBytes,Charset.defaultCharset()); - - templateString = templateString.replaceAll("\\{\\{ basePath \\}\\}", basePath); - templateString = templateString.replaceAll("\\{\\{ title \\}\\}",applicationName + " Swagger UI"); - - this.indexHTML = templateString; - } - - - URL url = this.getClass().getClassLoader().getResource(resourcePrefix); - - if( url.toExternalForm().contains("!") ) - { - log.debug("Copying OpenAPI resources..."); - - String jarPathString = url.toExternalForm().substring(0, url.toExternalForm().indexOf("!") ).replaceAll("file:", "").replaceAll("jar:", ""); - - File srcFile = new File(jarPathString); - - try(JarFile jarFile = new JarFile(srcFile, false)) - { - String appName = config.getString("application.name").replaceAll(" ", "_"); - - Path tmpDirParent = Files.createTempDirectory(appName); - - Path tmpDir = tmpDirParent.resolve("openapi/"); - - if(tmpDir.toFile().exists()) - { - log.debug("Deleting existing OpenAPI directory at " + tmpDir); - - try - { - FileUtils.deleteDirectory(tmpDir.toFile()); - - } catch (java.lang.IllegalArgumentException e) - { - log.debug("Tmp directory is not a directory..."); - tmpDir.toFile().delete(); - } - } - - java.nio.file.Files.createDirectory( tmpDir ); - - this.resourcePath = tmpDir; - - jarFile.stream().filter( ze -> ze.getName().endsWith("js") || ze.getName().endsWith("css") || ze.getName().endsWith("map") || ze.getName().endsWith("html") ).forEach( ze -> { - - try - { - final InputStream entryInputStream = jarFile.getInputStream(ze); - - String filename = ze.getName().substring(resourcePrefix.length() + 1); - - Path entryFilePath = tmpDir.resolve(filename); - - java.nio.file.Files.createDirectories(entryFilePath.getParent()); - - java.nio.file.Files.copy(entryInputStream, entryFilePath,StandardCopyOption.REPLACE_EXISTING); - - } catch (Exception e) - { - log.error(e.getMessage() + " for entry " + ze.getName()); - } - }); - } - } - else - { - this.resourcePath = Paths.get(this.getClass().getClassLoader().getResource(this.resourcePrefix).toURI()); - this.serviceClassLoader = this.getClass().getClassLoader(); - } - - } catch (Exception e) - { - log.error(e.getMessage(),e); - } - } - - public RoutingHandler get() - { - - RoutingHandler router = new RoutingHandler(); - - /* - * YAML path - */ - - String pathTemplate = this.applicationPath + File.separator + this.specFilename ; - - FileResourceManager resourceManager = new FileResourceManager(this.resourcePath.toFile(),1024); - - router.add(HttpMethod.GET, pathTemplate, new HttpHandler(){ - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception - { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, io.sinistral.proteus.server.MediaType.TEXT_YAML.contentType()); - - - try - { -// swaggerCopy.setHost(exchange.getHostAndPort()); -// -// spec = writer.writeValueAsString(swaggerCopy); - - } catch (Exception e) - { - log.error(e.getMessage(),e); - } - - exchange.getResponseSender().send(spec); - - } - - }); - - this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withPathTemplate(pathTemplate).withControllerName(this.getClass().getSimpleName()).withMethod(Methods.GET).withProduces(io.sinistral.proteus.server.MediaType.TEXT_YAML.contentType()).build()); - - - pathTemplate = this.basePath; - - router.add(HttpMethod.GET, pathTemplate , new HttpHandler(){ + @Override + protected void startUp() throws Exception + { + this.generateSpec(); + this.generateHTML(); + log.debug("\nOpenAPI Spec:\n" + writer.writeValueAsString(this.openApi)); + router.addAll(this.get()); + } - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception - { - - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.TEXT_HTML); - exchange.getResponseSender().send(indexHTML); - - } - - }); - - this.registeredEndpoints.add(EndpointInfo.builder().withConsumes(MediaType.WILDCARD).withProduces(MediaType.TEXT_HTML).withPathTemplate(pathTemplate).withControllerName(this.getClass().getSimpleName()).withMethod(Methods.GET).build()); - - - - try - { - - pathTemplate = this.basePath + "/*"; - - router.add(HttpMethod.GET, pathTemplate, new ResourceHandler(resourceManager){ - - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception - { - - String canonicalPath = CanonicalPathUtils.canonicalize((exchange.getRelativePath())); - - canonicalPath = canonicalPath.split(basePath)[1]; - - exchange.setRelativePath(canonicalPath); - - if(serviceClassLoader == null) - { - super.handleRequest(exchange); - } - else - { - canonicalPath = resourcePrefix + canonicalPath; - - try(final InputStream resourceInputStream = serviceClassLoader.getResourceAsStream( canonicalPath)) - { - - if(resourceInputStream == null) - { - ResponseCodeHandler.HANDLE_404.handleRequest(exchange); - return; - } - - byte[] resourceBytes = IOUtils.toByteArray(resourceInputStream); - - io.sinistral.proteus.server.MediaType mediaType = io.sinistral.proteus.server.MediaType.getByFileName(canonicalPath); - - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, mediaType.toString()); - - exchange.getResponseSender().send(ByteBuffer.wrap(resourceBytes)); - } - } - - } - - }); - - this.registeredEndpoints.add(EndpointInfo.builder().withConsumes(MediaType.WILDCARD).withProduces(MediaType.WILDCARD).withPathTemplate(pathTemplate).withControllerName(this.getClass().getSimpleName()).withMethod(Methods.GET).build()); + public RoutingHandler get() + { + RoutingHandler router = new RoutingHandler(); - + /* + * YAML path + */ + String pathTemplate = this.applicationPath + File.separator + this.specFilename; + FileResourceManager resourceManager = new FileResourceManager(this.resourcePath.toFile(), 1024); - } catch (Exception e) - { - log.error(e.getMessage(),e); - } - - - - return router; - } - - - - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#startUp() - */ - @Override - protected void startUp() throws Exception - { - // TODO Auto-generated method stub - - - this.generateSpec(); - this.generateHTML(); - - log.debug("\nOpenAPI Spec:\n" + writer.writeValueAsString(this.openApi)); - - router.addAll(this.get()); - } - - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#shutDown() - */ - @Override - protected void shutDown() throws Exception - { - // TODO Auto-generated method stub - - } - + router.add(HttpMethod.GET, + pathTemplate, + new HttpHandler() + { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, io.sinistral.proteus.server.MediaType.TEXT_YAML.contentType()); + + exchange.getResponseSender().send(spec); + } + }); + + this.registeredEndpoints.add(EndpointInfo.builder() + .withConsumes("*/*") + .withPathTemplate(pathTemplate) + .withControllerName(this.getClass().getSimpleName()) + .withMethod(Methods.GET) + .withProduces(io.sinistral.proteus.server.MediaType.TEXT_YAML.contentType()) + .build()); + + pathTemplate = this.basePath; + + router.add(HttpMethod.GET, + pathTemplate, + new HttpHandler() + { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MediaType.TEXT_HTML); + exchange.getResponseSender().send(indexHTML); + } + }); + + this.registeredEndpoints.add(EndpointInfo.builder() + .withConsumes(MediaType.WILDCARD) + .withProduces(MediaType.TEXT_HTML) + .withPathTemplate(pathTemplate) + .withControllerName(this.getClass().getSimpleName()) + .withMethod(Methods.GET) + .build()); + + try { + + pathTemplate = this.basePath + "/*"; + + router.add(HttpMethod.GET, + pathTemplate, + new ResourceHandler(resourceManager) + { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception + { + String canonicalPath = CanonicalPathUtils.canonicalize((exchange.getRelativePath())); + + canonicalPath = canonicalPath.split(basePath)[1]; + + exchange.setRelativePath(canonicalPath); + + if (serviceClassLoader == null) + { + super.handleRequest(exchange); + } + else + { + canonicalPath = resourcePrefix + canonicalPath; + + try (final InputStream resourceInputStream = serviceClassLoader.getResourceAsStream(canonicalPath)) { + + if (resourceInputStream == null) + { + ResponseCodeHandler.HANDLE_404.handleRequest(exchange); + + return; + } + + byte[] resourceBytes = IOUtils.toByteArray(resourceInputStream); + + io.sinistral.proteus.server.MediaType mediaType = io.sinistral.proteus.server.MediaType.getByFileName(canonicalPath); + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, mediaType.toString()); + exchange.getResponseSender().send(ByteBuffer.wrap(resourceBytes)); + } + } + } + }); + + this.registeredEndpoints.add(EndpointInfo.builder() + .withConsumes(MediaType.WILDCARD) + .withProduces(MediaType.WILDCARD) + .withPathTemplate(pathTemplate) + .withControllerName(this.getClass().getSimpleName()) + .withMethod(Methods.GET) + .build()); + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + return router; + } } + + + diff --git a/src/main/java/io/sinistral/proteus/services/SwaggerService.java b/src/main/java/io/sinistral/proteus/services/SwaggerService.java index 2907abe..8f6d461 100644 --- a/src/main/java/io/sinistral/proteus/services/SwaggerService.java +++ b/src/main/java/io/sinistral/proteus/services/SwaggerService.java @@ -422,16 +422,7 @@ public void generateSwaggerHTML() java.nio.file.Files.createDirectories(entryFilePath.getParent()); java.nio.file.Files.copy(entryInputStream, entryFilePath,StandardCopyOption.REPLACE_EXISTING); - -// String sharedCSS = null; -// -// try(InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(resourcePrefix + File.separator + ".." + File.separator + "/swagger-ui.css")) -// { -// byte[] templateBytes = IOUtils.toByteArray(templateInputStream); -// -// sharedCSS = new String(templateBytes,Charset.defaultCharset()); -// } - + } catch (Exception e) { log.error(e.getMessage() + " for entry " + ze.getName()); @@ -632,16 +623,10 @@ public void handleRequest(HttpServerExchange exchange) throws Exception return router; } - - - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#startUp() - */ + @Override protected void startUp() throws Exception - { - // TODO Auto-generated method stub - + { this.generateSwaggerSpec(); this.generateSwaggerHTML(); @@ -651,13 +636,11 @@ protected void startUp() throws Exception router.addAll(this.get()); } - /* (non-Javadoc) - * @see com.google.common.util.concurrent.AbstractIdleService#shutDown() - */ + @Override protected void shutDown() throws Exception { - // TODO Auto-generated method stub + } diff --git a/src/main/java/io/sinistral/proteus/utilities/SecurityOps.java b/src/main/java/io/sinistral/proteus/utilities/SecurityOps.java index 3d6a2df..7c5c625 100644 --- a/src/main/java/io/sinistral/proteus/utilities/SecurityOps.java +++ b/src/main/java/io/sinistral/proteus/utilities/SecurityOps.java @@ -1,5 +1,6 @@ + /** - * + * */ package io.sinistral.proteus.utilities; @@ -7,12 +8,16 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; + import java.net.URL; + import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; + import java.security.KeyStore; + import java.util.jar.JarFile; import java.util.zip.ZipInputStream; @@ -23,6 +28,7 @@ import javax.net.ssl.TrustManagerFactory; import org.apache.commons.io.FileUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,56 +37,61 @@ */ public class SecurityOps { + public static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore, final String password) throws Exception + { + KeyManager[] keyManagers; + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - @SuppressWarnings("resource") - public static KeyStore loadKeyStore(String name, String password) throws Exception - { + keyManagerFactory.init(keyStore, password.toCharArray()); - File storeFile = new File(name); + keyManagers = keyManagerFactory.getKeyManagers(); - InputStream stream = null; + TrustManager[] trustManagers; + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - if (!storeFile.exists()) - { - stream = SecurityOps.class.getResourceAsStream("/" + name); + trustManagerFactory.init(trustStore); - } - else - { + trustManagers = trustManagerFactory.getTrustManagers(); - stream = Files.newInputStream(Paths.get(name)); - } + SSLContext sslContext; - if (stream == null) - { - throw new RuntimeException("Could not load keystore"); - } + sslContext = SSLContext.getInstance("TLS"); - try (InputStream is = stream) - { - KeyStore loadedKeystore = KeyStore.getInstance("JKS"); - loadedKeystore.load(is, password.toCharArray()); - return loadedKeystore; - } - } + sslContext.init(keyManagers, trustManagers, null); - public static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore, final String password) throws Exception - { - KeyManager[] keyManagers; - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, password.toCharArray()); - keyManagers = keyManagerFactory.getKeyManagers(); + return sslContext; + } - TrustManager[] trustManagers; - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - trustManagers = trustManagerFactory.getTrustManagers(); + @SuppressWarnings("resource") + public static KeyStore loadKeyStore(String name, String password) throws Exception + { + File storeFile = new File(name); + InputStream stream = null; - SSLContext sslContext; - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, trustManagers, null); + if (!storeFile.exists()) + { + stream = SecurityOps.class.getResourceAsStream("/" + name); + } + else + { + stream = Files.newInputStream(Paths.get(name)); + } - return sslContext; - } + if (stream == null) + { + throw new RuntimeException("Could not load keystore"); + } + try (InputStream is = stream) { + + KeyStore loadedKeystore = KeyStore.getInstance("JKS"); + + loadedKeystore.load(is, password.toCharArray()); + + return loadedKeystore; + } + } } + + + diff --git a/src/main/java/io/sinistral/proteus/utilities/TablePrinter.java b/src/main/java/io/sinistral/proteus/utilities/TablePrinter.java index 069b716..cfcfe06 100644 --- a/src/main/java/io/sinistral/proteus/utilities/TablePrinter.java +++ b/src/main/java/io/sinistral/proteus/utilities/TablePrinter.java @@ -1,127 +1,134 @@ + /** - * + * */ package io.sinistral.proteus.utilities; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; /** * @author jbauer */ public class TablePrinter { - private final int TABLEPADDING = 4; - - - private List headers; - private List> table; - private List maxLength; - - public TablePrinter(List headersIn, List> content) - { - this.headers = headersIn; - this.maxLength = new ArrayList(); - for (int i = 0; i < headers.size(); i++) - { - maxLength.add(headers.get(i).length()); - } - this.table = content; - - updateMaxLengths(); - } - - public void updateField(int row, int col, String input) - { - table.get(row).set(col, input); - updateMaxColumnLength(col); - } - - - public String toString() - { - StringBuilder sb = new StringBuilder(); - StringBuilder rowSeparatorBuilder = new StringBuilder(); - String padder = ""; - String rowSeperator = ""; - - for (int i = 0; i < 4; i++) - { - padder += " "; - } - - for (int i = 0; i < maxLength.size(); i++) - { - for (int j = 0; j < maxLength.get(i) + (TABLEPADDING * 2); j++) - { - rowSeparatorBuilder.append("-"); - } - } - - rowSeperator = rowSeparatorBuilder.toString(); - - sb.append("\n"); - - for (int i = 0; i < headers.size(); i++) - { - sb.append(padder); - sb.append(headers.get(i)); - for (int k = 0; k < (maxLength.get(i) - headers.get(i).length()); k++) - { - sb.append(" "); - } - sb.append(padder); - } - - sb.append("\n"); - sb.append(rowSeperator); - sb.append("\n"); - - for (int i = 0; i < table.size(); i++) - { - List tempRow = table.get(i); - for (int j = 0; j < tempRow.size(); j++) - { - sb.append(padder); - sb.append(tempRow.get(j)); - for (int k = 0; k < (maxLength.get(j) - tempRow.get(j).length()); k++) - { - sb.append(" "); - } - sb.append(padder); - } - sb.append("\n"); - } - - return sb.toString(); - } - - private void updateMaxLengths() - { - for (int i = 0; i < table.size(); i++) - { - List temp = table.get(i); - for (int j = 0; j < temp.size(); j++) - { - if (temp.get(j).length() > maxLength.get(j)) - { - maxLength.set(j, temp.get(j).length()); - } - } - } - } - - private void updateMaxColumnLength(int col) - { - for (int i = 0; i < table.size(); i++) - { - if (table.get(i).get(col).length() > maxLength.get(col)) - { - maxLength.set(col, table.get(i).get(col).length()); - } - } - } + private final int TABLEPADDING = 4; + private List headers; + private List> table; + private List maxLength; + + public TablePrinter(List headersIn, List> content) + { + this.headers = headersIn; + this.maxLength = new ArrayList(); + + for (int i = 0; i < headers.size(); i++) + { + maxLength.add(headers.get(i).length()); + } + + this.table = content; + + updateMaxLengths(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + StringBuilder rowSeparatorBuilder = new StringBuilder(); + String padder = ""; + String rowSeperator = ""; + + for (int i = 0; i < 4; i++) + { + padder += " "; + } + + for (int i = 0; i < maxLength.size(); i++) + { + for (int j = 0; j < maxLength.get(i) + (TABLEPADDING * 2); j++) + { + rowSeparatorBuilder.append("-"); + } + } + + rowSeperator = rowSeparatorBuilder.toString(); + + sb.append("\n"); + + for (int i = 0; i < headers.size(); i++) + { + sb.append(padder); + sb.append(headers.get(i)); + + for (int k = 0; k < (maxLength.get(i) - headers.get(i).length()); k++) + { + sb.append(" "); + } + + sb.append(padder); + } + sb.append("\n"); + sb.append(rowSeperator); + sb.append("\n"); + + for (int i = 0; i < table.size(); i++) + { + List tempRow = table.get(i); + + for (int j = 0; j < tempRow.size(); j++) + { + sb.append(padder); + sb.append(tempRow.get(j)); + + for (int k = 0; k < (maxLength.get(j) - tempRow.get(j).length()); k++) + { + sb.append(" "); + } + + sb.append(padder); + } + + sb.append("\n"); + } + + return sb.toString(); + } + + public void updateField(int row, int col, String input) + { + table.get(row).set(col, input); + updateMaxColumnLength(col); + } + + private void updateMaxColumnLength(int col) + { + for (int i = 0; i < table.size(); i++) + { + if (table.get(i).get(col).length() > maxLength.get(col)) + { + maxLength.set(col, table.get(i).get(col).length()); + } + } + } + + private void updateMaxLengths() + { + for (int i = 0; i < table.size(); i++) + { + List temp = table.get(i); + + for (int j = 0; j < temp.size(); j++) + { + if (temp.get(j).length() > maxLength.get(j)) + { + maxLength.set(j, temp.get(j).length()); + } + } + } + } } + + + diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 25872b9..8f088f1 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -187,5 +187,3 @@ undertow bufferSize = 16k directBuffers = true } - - \ No newline at end of file