From 3c818b72fd54caa9bd4e5db64bfbb44cfda19966 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Mon, 22 Apr 2019 11:52:36 -0700 Subject: [PATCH 1/7] Externalize object mapper. --- .../sinistral/proteus/ProteusApplication.java | 5 +++ .../proteus/modules/ApplicationModule.java | 38 +++++++++++-------- .../proteus/modules/ConfigModule.java | 12 ++++++ .../proteus/modules/JacksonModule.java | 32 ++++++++++++++++ core/src/main/resources/reference.conf | 2 + core/src/test/resources/application.conf | 2 + 6 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java diff --git a/core/src/main/java/io/sinistral/proteus/ProteusApplication.java b/core/src/main/java/io/sinistral/proteus/ProteusApplication.java index d50afbe..ed1595a 100644 --- a/core/src/main/java/io/sinistral/proteus/ProteusApplication.java +++ b/core/src/main/java/io/sinistral/proteus/ProteusApplication.java @@ -14,6 +14,9 @@ import com.google.inject.name.Named; import com.typesafe.config.Config; import io.sinistral.proteus.modules.ConfigModule; +import io.sinistral.proteus.modules.JacksonModule; +import io.sinistral.proteus.server.Extractors; +import io.sinistral.proteus.server.ServerResponse; import io.sinistral.proteus.server.endpoints.EndpointInfo; import io.sinistral.proteus.server.handlers.HandlerGenerator; import io.sinistral.proteus.server.handlers.ServerDefaultHttpHandler; @@ -226,6 +229,8 @@ public boolean isRunning() public void buildServer() { + + for (Class controllerClass : registeredControllers) { HandlerGenerator generator = new HandlerGenerator("io.sinistral.proteus.controllers.handlers", controllerClass); diff --git a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java index 16b57ec..a3ece92 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java @@ -59,21 +59,29 @@ public void bindMappers() 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); - } + try { + + String className = config.getString("application.jacksonModule"); + + log.info("Installing JacksonModule " + className); + + Class clazz = (Class) Class.forName(className); + + JacksonModule module = clazz.newInstance(); + + install(module); + + } catch (Exception e) { + + this.binder().addError(e); + + log.error(e.getMessage(), e); + + install(new JacksonModule()); + + } + + } @SuppressWarnings("unchecked") @Override diff --git a/core/src/main/java/io/sinistral/proteus/modules/ConfigModule.java b/core/src/main/java/io/sinistral/proteus/modules/ConfigModule.java index 09a5a26..0d1fd73 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/ConfigModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/ConfigModule.java @@ -46,6 +46,8 @@ public ConfigModule() } } + + public ConfigModule(String configFile) { this.configFile = configFile; @@ -56,6 +58,16 @@ public ConfigModule(URL configURL) this.configURL = configURL; } + public Config getConfig() + { + return config; + } + + public void setConfig(Config config) + { + this.config = config; + } + @SuppressWarnings("unchecked") private void bindConfig(final Config config) { diff --git a/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java new file mode 100644 index 0000000..fa5a7f0 --- /dev/null +++ b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java @@ -0,0 +1,32 @@ +package io.sinistral.proteus.modules; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; +import com.google.inject.AbstractModule; +import io.sinistral.proteus.server.Extractors; +import io.sinistral.proteus.server.ServerResponse; + +public class JacksonModule extends AbstractModule +{ + @Override + protected void configure() + { + 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); + } +} diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 3afcb62..796bf00 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -22,6 +22,8 @@ application { defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" + jacksonModule = "io.sinistral.proteus.modules.JacksonModule" + tmpdir = ${java.io.tmpdir}/${application.name} # path to default favicon file diff --git a/core/src/test/resources/application.conf b/core/src/test/resources/application.conf index 9c55400..ddd47d4 100644 --- a/core/src/test/resources/application.conf +++ b/core/src/test/resources/application.conf @@ -22,6 +22,8 @@ application { defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" + jacksonModule = "io.sinistral.proteus.modules.JacksonModule" + tmpdir = ${java.io.tmpdir}/${application.name} # path to default favicon file From 04ce8d317c555dc82696810f652759f56ee3ecf7 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Mon, 22 Apr 2019 11:54:04 -0700 Subject: [PATCH 2/7] Make JacksonModule more flexible. --- .../java/io/sinistral/proteus/modules/ApplicationModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java index a3ece92..6889df0 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java @@ -65,9 +65,9 @@ public void bindMappers() log.info("Installing JacksonModule " + className); - Class clazz = (Class) Class.forName(className); + Class clazz = (Class) Class.forName(className); - JacksonModule module = clazz.newInstance(); + AbstractModule module = clazz.newInstance(); install(module); From 80ab753f08830b9576038e6bc0a373fbce36d485 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Mon, 22 Apr 2019 18:21:27 -0700 Subject: [PATCH 3/7] Added support for more flexible schemas. --- .../proteus/modules/ApplicationModule.java | 3 + .../proteus/modules/JacksonModule.java | 5 - .../{swagger => }/test/controllers/Tests.java | 5 +- .../{swagger => }/test/models/User.java | 2 +- .../test/server/DefaultServer.java | 4 +- .../test/server/TestControllerEndpoints.java | 6 +- openapi/pom.xml | 9 +- .../proteus/openapi/jaxrs2/Reader.java | 11 +- .../openapi/jaxrs2/ServerModelResolver.java | 41 ++- .../jaxrs2/ServerParameterExtension.java | 10 +- .../openapi/models/MoneyModelConverter.java | 157 +++++++++ .../openapi/services/OpenAPIService.java | 5 +- .../test/controllers/OpenAPITests.java | 316 ++++++++++++++++++ .../proteus/openapi/test/models/Pojo.java | 40 +++ .../test/server/OpenAPIDefaultServer.java | 129 +++++++ .../TestOpenAPIControllerEndpoints.java | 107 ++++++ .../{swagger => }/test/controllers/Tests.java | 17 +- .../test/server/DefaultServer.java | 4 +- .../test/server/TestControllerEndpoints.java | 8 +- openapi/src/test/resources/application.conf | 93 ++++++ openapi/src/test/resources/development.jks | Bin 0 -> 3916 bytes openapi/src/test/resources/development.ts | Bin 0 -> 1471 bytes openapi/src/test/resources/logback-test.xml | 8 +- .../swagger/test/controllers/Tests.java | 14 +- .../test/server/TestControllerEndpoints.java | 21 +- .../proteus/test/controllers/Tests.java | 261 +++++++++++++++ .../proteus/test/server/DefaultServer.java | 129 +++++++ .../test/server/TestControllerEndpoints.java | 88 +++++ 28 files changed, 1447 insertions(+), 46 deletions(-) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/controllers/Tests.java (98%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/models/User.java (94%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/server/DefaultServer.java (95%) rename core/src/test/java/io/sinistral/proteus/{swagger => }/test/server/TestControllerEndpoints.java (98%) create mode 100644 openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/controllers/Tests.java (95%) rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/server/DefaultServer.java (95%) rename openapi/src/test/java/io/sinistral/proteus/{swagger => }/test/server/TestControllerEndpoints.java (91%) create mode 100644 openapi/src/test/resources/application.conf create mode 100644 openapi/src/test/resources/development.jks create mode 100644 openapi/src/test/resources/development.ts create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java create mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java diff --git a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java index 6889df0..b6a13ef 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/ApplicationModule.java @@ -81,6 +81,9 @@ public void bindMappers() } + this.requestStaticInjection(Extractors.class); + this.requestStaticInjection(ServerResponse.class); + } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java index fa5a7f0..b66e00e 100644 --- a/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java +++ b/core/src/main/java/io/sinistral/proteus/modules/JacksonModule.java @@ -5,8 +5,6 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; import com.google.inject.AbstractModule; -import io.sinistral.proteus.server.Extractors; -import io.sinistral.proteus.server.ServerResponse; public class JacksonModule extends AbstractModule { @@ -25,8 +23,5 @@ protected void configure() objectMapper.registerModule(new Jdk8Module()); this.bind(ObjectMapper.class).toInstance(objectMapper); - - this.requestStaticInjection(Extractors.class); - this.requestStaticInjection(ServerResponse.class); } } diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java similarity index 98% rename from core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java rename to core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java index 3a2f3ed..aa852b6 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/core/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.controllers; +package io.sinistral.proteus.test.controllers; import static io.sinistral.proteus.server.ServerResponse.response; @@ -37,10 +37,9 @@ import com.google.inject.Singleton; import io.sinistral.proteus.annotations.Blocking; -import io.sinistral.proteus.annotations.Debug; import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; -import io.sinistral.proteus.swagger.test.models.User; +import io.sinistral.proteus.test.models.User; import io.undertow.server.HttpServerExchange; diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java b/core/src/test/java/io/sinistral/proteus/test/models/User.java similarity index 94% rename from core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java rename to core/src/test/java/io/sinistral/proteus/test/models/User.java index 32d81c2..04e405e 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/models/User.java +++ b/core/src/test/java/io/sinistral/proteus/test/models/User.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.models; +package io.sinistral.proteus.test.models; /** diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java b/core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java similarity index 95% rename from core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java rename to core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java index a65fab2..4f651d7 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java +++ b/core/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -1,11 +1,11 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import java.util.List; -import io.sinistral.proteus.swagger.test.controllers.Tests; +import io.sinistral.proteus.test.controllers.Tests; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; diff --git a/core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java similarity index 98% rename from core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java rename to core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java index ae48ec1..64aaf53 100644 --- a/core/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/core/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.containsString; @@ -36,8 +36,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.http.ContentType; -import io.sinistral.proteus.swagger.test.models.User; -import io.sinistral.proteus.swagger.test.models.User.UserType; +import io.sinistral.proteus.test.models.User; +import io.sinistral.proteus.test.models.User.UserType; /* * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; diff --git a/openapi/pom.xml b/openapi/pom.xml index 7badaa1..1758cea 100644 --- a/openapi/pom.xml +++ b/openapi/pom.xml @@ -29,7 +29,7 @@ src/test/resources - src/test/java + src/test/java/sinistral/proteus/openapi **/*.java @@ -109,6 +109,13 @@ swagger-integration ${openapi.version} + + + org.zalando + jackson-datatype-money + 1.1.1 + + ${project.groupId} proteus-core diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java index 69522a4..b3e3d7d 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.type.TypeFactory; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; import io.swagger.v3.core.converter.AnnotatedType; @@ -1373,11 +1374,19 @@ else if (classType.getRawClass().isAssignableFrom(CompletableFuture.class)) } } - ResolvedSchema resolvedSchema = ModelConverters.getInstance() + ModelConverters converters = ModelConverters.getInstance(); + + converters.addConverter(new ServerModelResolver()); + converters.addConverter(new MoneyModelConverter()); + + + ResolvedSchema resolvedSchema = converters .resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation)); if (resolvedSchema.schema != null) { + System.out.println("resolvedSchema: " + resolvedSchema.schema); + Schema returnTypeSchema = resolvedSchema.schema; Content content = new Content(); MediaType mediaType = new MediaType().schema(returnTypeSchema); diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java index 0944968..0d364f5 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java @@ -8,12 +8,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.type.TypeFactory; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; 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; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.media.Schema; +import org.javamoney.moneta.Money; +import org.zalando.jackson.datatype.money.MoneyModule; import java.io.File; import java.lang.annotation.Annotation; @@ -30,9 +33,19 @@ public class ServerModelResolver extends io.swagger.v3.core.jackson.ModelResolve { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName()); + static final ObjectMapper mapper = Json.mapper(); + + static + { + mapper.registerModule(new MoneyModule()); + } + public ServerModelResolver() { - super(Json.mapper()); + super(mapper); + + + System.out.println("not from super"); } /** @@ -40,7 +53,8 @@ public ServerModelResolver() */ public ServerModelResolver(ObjectMapper mapper) { - super(mapper); + super(ServerModelResolver.mapper); + } /* @@ -83,6 +97,11 @@ else if (rawClass.isAssignableFrom(CompletableFuture.class)) if (resolvedType != null) { +// if (resolvedType.getTypeName().contains("org.javamoney.moneta.Money")) +// { +// resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Money.class.getName()); +// } + if (resolvedType.getTypeName().contains("java.lang.Void")) { resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Void.class.getName()); @@ -120,8 +139,24 @@ else if (resolvedType.getTypeName().contains("Optional")) try { + // log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName()); - return super.resolve(annotatedType, context, next); + + if(annotatedType.getType().getTypeName().contains("org.javamoney.moneta.Money")) + { + MoneyModelConverter.MoneySchema schema = new MoneyModelConverter.MoneySchema(); + + context.defineModel("money",schema); + + return schema; + } + else { + + Schema schema = super.resolve(annotatedType, context, next); + + return schema; + } + } catch (Exception e) { diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java index 5d6c85b..56e9efb 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java @@ -4,6 +4,8 @@ */ package io.sinistral.proteus.openapi.jaxrs2; +import org.zalando.jackson.datatype.money.MoneyModule; + import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; @@ -38,7 +40,13 @@ public class ServerParameterExtension extends AbstractOpenAPIExtension private static String COOKIE_PARAM = "cookie"; private static String PATH_PARAM = "path"; private static String FORM_PARAM = "form"; - final ObjectMapper mapper = Json.mapper(); + + final static ObjectMapper mapper = Json.mapper(); + + static + { + mapper.registerModule(new MoneyModule()); + } @Override public ResolvedParameter extractParameters(List annotations, Type type, Set typesToSkip, Components components, javax.ws.rs.Consumes classConsumes, diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java b/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java new file mode 100644 index 0000000..f37539e --- /dev/null +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java @@ -0,0 +1,157 @@ +package io.sinistral.proteus.openapi.models; + + +import com.fasterxml.jackson.databind.JavaType; +import com.google.common.collect.ImmutableMap; +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverter; +import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import org.javamoney.moneta.Money; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class MoneyModelConverter implements ModelConverter +{ + + @Override + public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { + + JavaType _type = Json.mapper().constructType(type.getType()); + + if(_type != null && (_type.getRawClass().equals(Money.class))) + { + return new MoneySchema(); + } + + if (type.getType() instanceof Class) { + + System.out.println("MoneyModelConverter resolving class: " + ((Class) type.getType()).getName()); + + Class cls = (Class) type.getType(); + + if(cls.isAssignableFrom(Money.class)) + { + return new MoneySchema(); + } + } + else if(type.getType().getTypeName().equals("[simple type, class org.javamoney.moneta.Money]")) + { + System.out.println("is org.javamoney.moneta.Money"); + return new MoneySchema(); + } + else if (type.isSchemaProperty()) { + _type = Json.mapper().constructType(type.getType()); + + if (_type != null) { + Class cls = _type.getRawClass(); + if (Money.class.isAssignableFrom(cls)) { + return new MoneySchema(); + } + } + } + + if (chain.hasNext()) { + System.out.println("skipped resolving " + _type); + + return chain.next().resolve(type, context, chain); + } + + return null; + + } + + + public static class MoneySchema extends Schema + { + private String _type = "object"; + + private String _$ref = null;// "#/components/schemas/Money"; + + private String _description = "A monetary amount"; + + private Map _properties = ImmutableMap.of("number",new NumberSchema(),"currency",new StringSchema()); + + private List _required = Arrays.asList("number","currency"); + + public MoneySchema() + { + super(); + super.setName("Money"); + super.setType("object"); + super.set$ref(_$ref); + super.description(_description); + super.properties(_properties); + } + + @Override + protected Money cast(Object value) + { + if (value != null) { + try { + if (value instanceof Money) { + return (Money) value; + } + } catch (Exception e) { + } + } + return null; + } + + @Override + public boolean equals(java.lang.Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MoneySchema MoneySchema = (MoneySchema) o; + return Objects.equals(this._type, MoneySchema._type) && + Objects.equals(this._properties, MoneySchema._properties) && + super.equals(o); + } + + @Override + public int hashCode() + { + return Objects.hash(_type, _properties, super.hashCode()); + } + +// @Override +// public String toString() +// { +// StringBuilder sb = new StringBuilder(); +// sb.append("class MoneySchema {\n"); +// sb.append(" ").append(toIndentedString(super.toString())).append("\n"); +// sb.append(" title: ").append(toIndentedString(getTitle())).append("\n"); +// sb.append(" type: ").append(toIndentedString(_type)).append("\n"); +// sb.append(toIndentedString(_properties.toString())).append("\n"); +// sb.append(" }").append("\n"); +// +// sb.append("}"); +// return sb.toString(); +// } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) + { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + } +} diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java index 09a2ca0..0d5aeeb 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java @@ -12,7 +12,9 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +29,7 @@ import io.sinistral.proteus.openapi.jaxrs2.Reader; import io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver; import io.sinistral.proteus.openapi.jaxrs2.ServerParameterExtension; +import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.services.DefaultService; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -265,7 +268,7 @@ protected void generateSpec() throws Exception SwaggerConfiguration config = new SwaggerConfiguration().resourceClasses(classes.stream().map(Class::getName).collect(Collectors.toSet())).openAPI(openApi); - config.setModelConverterClassess(Collections.singleton(ServerModelResolver.class.getName())); + config.setModelConverterClassess(new HashSet<>(Arrays.asList(ServerModelResolver.class.getName()))); OpenApiContext ctx = new GenericOpenApiContext().openApiConfiguration(config) .openApiReader(new Reader(config)) diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java new file mode 100644 index 0000000..9ddefb8 --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java @@ -0,0 +1,316 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.sinistral.proteus.annotations.Blocking; +import io.sinistral.proteus.openapi.test.models.Pojo; +import io.sinistral.proteus.server.ServerRequest; +import io.sinistral.proteus.server.ServerResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import io.undertow.server.HttpServerExchange; +import org.javamoney.moneta.Money; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static io.sinistral.proteus.server.ServerResponse.response; + +/** + * @author jbauer + * + */ + +@Tags({@Tag(name = "tests")}) +@Path("/tests") +@Produces((MediaType.APPLICATION_JSON)) +@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) +@Singleton +public class OpenAPITests +{ + private static final ByteBuffer buffer; + static { + String message = "Hello, World!"; + byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + buffer = ByteBuffer.allocateDirect(messageBytes.length); + buffer.put(messageBytes); + buffer.flip(); + } + + @Inject + protected ObjectMapper objectMapper; + + + @GET + @Path("exchange/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @Operation(description = "Plaintext endpoint" ) + public void exchangePlaintext(HttpServerExchange exchange) + { + response("Hello, World!").textPlain().send(exchange); + + } + + + @GET + @Path("response/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @Operation(description = "Plaintext endpoint" ) + public ServerResponse responsePlaintext(ServerRequest request) + { + return response("Hello, World!").textPlain(); + + } + + @GET + @Path("response/future/map") + @Operation(description = "Future map endpoint" ) + public CompletableFuture>> responseFutureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return CompletableFuture.completedFuture(response( map ).applicationJson()); + } + + @GET + @Path("response/map") + @Operation(description = "Map endpoint" ) + public ServerResponse> futureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return response( map ).applicationJson(); + } + + @POST + @Path("response/file/path") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(description = "Upload file path endpoint" ) + public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception + { + return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); + } + + @POST + @Path("response/file/path/optional") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(description = "Upload optional file path endpoint" ) + public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception + { + if(file.isPresent()) + { + return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); + } + else + { + return response().noContent(); + } + } + + @GET + @Path("generic/set") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Generic set endpoint" ) + public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @GET + @Path("types/money") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getMoney(ServerRequest request ) throws Exception + { + return response( Money.of(100.0,"USD") ).applicationJson(); + } + + @GET + @Path("types/pojo2") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getPojo2(ServerRequest request ) throws Exception + { + return response( new Pojo(100L,"USD") ).applicationJson(); + } + + @GET + @Path("types/pojo") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getPojo(ServerRequest request ) throws Exception + { + return response( new Pojo(100L,"USD") ).applicationJson(); + } + + @POST + @Path("generic/set/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + @Operation(description = "Generic bean set endpoint" ) + public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/list/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + + @Operation(description = "Generic bean list endpoint" ) + public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + @GET + @Path("optional/set") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Generic optional set endpoint" ) + public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception + { + return response( ids.get() ).applicationJson(); + } + + + @POST + @Path("response/parse/ids") + @Blocking + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Operation(description = "Convert ids") + public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception + { + + return response( ids ).applicationJson(); + + + } + + @GET + @Path("response/parse/timestamp") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @Operation(description = "Convert timestamp") + public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception + { + return response().body(timestamp.toString()).textPlain(); + } + + @GET + @Path("response/parse/instant") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @Operation(description = "Convert instant") + public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception + { + + return response().body(instant.toString()).textPlain(); + + + } + + @POST + @Path("response/bytebuffer") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @Operation(description = "Upload file path endpoint") + public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception + { + + return response(file).applicationOctetStream(); + + + } + + @POST + @Path("response/file") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @Operation(description = "Upload file path endpoint") + public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception + { + + ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); + + + return response(response).applicationOctetStream(); + + + } + + @GET + @Path("response/debug") + @Operation(description = "Debug endpoint") + public ServerResponse> debugEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + @GET + @Path("response/debug/blocking") + @Blocking + @Operation(description="Debug blocking endpoint") + public ServerResponse> debugBlockingEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + + @GET + @SecurityRequirement(name = "testRequirement") + @Path("secure/resource") + @Operation(description="Secure resource") + @Produces(MediaType.APPLICATION_JSON) + public ServerResponse> responseSecureContext() + { + Map responseMap = new HashMap<>(); + responseMap.put("secure",true); + + return response(responseMap); + } +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java new file mode 100644 index 0000000..5d9e8af --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/models/Pojo.java @@ -0,0 +1,40 @@ +package io.sinistral.proteus.openapi.test.models; + +public class Pojo +{ + + public Long id; + + public String name; + + + public Pojo(Long id, String name) + { + this.id = id; + this.name = name; + } + + public Pojo() + { + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java new file mode 100644 index 0000000..d22cac3 --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java @@ -0,0 +1,129 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.server; + +import io.restassured.RestAssured; +import io.sinistral.proteus.ProteusApplication; +import io.sinistral.proteus.openapi.services.OpenAPIService; +import io.sinistral.proteus.openapi.test.controllers.OpenAPITests; +import io.sinistral.proteus.services.AssetsService; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @author jbauer + */ +public class OpenAPIDefaultServer extends BlockJUnit4ClassRunner +{ + private static Logger log = LoggerFactory.getLogger(OpenAPIDefaultServer.class.getCanonicalName()); + + private static boolean first = true; + + /** + * @param clazz + * @throws InitializationError + */ + public OpenAPIDefaultServer(Class clazz) throws InitializationError + { + super(clazz); + } + + @Override + public void run(final RunNotifier notifier) + { + notifier.addListener(new RunListener() + { + @Override + public void testStarted(Description description) throws Exception + { + + super.testStarted(description); + } + + @Override + public void testFinished(Description description) throws Exception + { + + super.testFinished(description); + } + }); + + runInternal(notifier); + + super.run(notifier); + } + + private static void runInternal(final RunNotifier notifier) + { + + if (first) + { + + first = false; + + final ProteusApplication app = new ProteusApplication(); + + app.addService(OpenAPIService.class); + app.addService(AssetsService.class); + + app.addController(OpenAPITests.class); + + app.start(); + + int port = 0; + + try + { + Thread.sleep(5000); + + System.out.println(app.getPorts()); + + List ports = app.getPorts(); + + port = ports.get(0); + + } catch (Exception e) + { + e.printStackTrace(); + } + + + + RestAssured.baseURI = String.format("http://localhost:%d/v1",port); + + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + + while (!app.isRunning()) + { + try + { + Thread.sleep(100L); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + notifier.addListener(new RunListener() + { + @Override + public void testRunFinished(final Result result) throws Exception + { + app.shutdown(); + }; + }); + } + + } + +} diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java new file mode 100644 index 0000000..7f003ce --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java @@ -0,0 +1,107 @@ +/** + * + */ +package io.sinistral.proteus.openapi.test.server; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.stream.LongStream; + +import static io.restassured.RestAssured.when; + +/* + * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; + */ + +/** + * @author jbauer + */ +@RunWith(OpenAPIDefaultServer.class) +public class TestOpenAPIControllerEndpoints +{ + + private File file = null; + + private Set idSet = new HashSet<>(); + + @Before + public void setUp() + { + try + { + byte[] bytes = new byte[8388608]; + Random random = new Random(); + random.nextBytes(bytes); + + file = Files.createTempFile("test-asset", ".mp4").toFile(); + + LongStream.range(1L,10L).forEach( l -> { + + idSet.add(l); + }); + + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + + @Test + public void testOpenAPIDocs() + { + when().get("openapi.yaml").then().log().all().statusCode(200); + } + + public void testPojoType() + { + when().get("tests/types/pojo").then().log().all().statusCode(200); + } + + @Test public void testPojoType2() + { + when().get("tests/types/pojo2").then().log().all().statusCode(200); + } + + public void testMoneyType() + { + when().get("tests/types/money").then().log().all().statusCode(200); + } + + + @Test + public void testSecurityRequirementEndpoint() + { + when().get("tests/secure/resource").then().statusCode(200); + } + + + @After + public void tearDown() + { + try + { + if(file.exists()) + { + file.delete(); + } + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java similarity index 95% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java rename to openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java index 71bf8dd..6616627 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.controllers; +package io.sinistral.proteus.test.controllers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; @@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; import io.undertow.server.HttpServerExchange; +import org.javamoney.moneta.Money; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -127,8 +128,18 @@ public ServerResponse> genericSet( ServerRequest request, @QueryParam { return response( ids ).applicationJson(); } - - + + + @GET + @Path("types/money") + @Produces((MediaType.APPLICATION_JSON)) + @Operation(description = "Money type endpoint" ) + public ServerResponse getMoney(ServerRequest request ) throws Exception + { + return response( Money.of(100.0,"USD") ).applicationJson(); + } + + @POST @Path("generic/set/bean") @Produces((MediaType.APPLICATION_JSON)) diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java similarity index 95% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java rename to openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java index 5d1ea93..fa501cf 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/DefaultServer.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -1,13 +1,13 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import io.restassured.RestAssured; import io.sinistral.proteus.ProteusApplication; import io.sinistral.proteus.openapi.services.OpenAPIService; import io.sinistral.proteus.services.AssetsService; -import io.sinistral.proteus.swagger.test.controllers.Tests; +import io.sinistral.proteus.test.controllers.Tests; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; diff --git a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java similarity index 91% rename from openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java rename to openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java index c91cb8d..2f03a19 100644 --- a/openapi/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -1,7 +1,7 @@ /** * */ -package io.sinistral.proteus.swagger.test.server; +package io.sinistral.proteus.test.server; import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; @@ -66,6 +66,12 @@ public void testOpenAPIDocs() when().get("openapi.yaml").then().statusCode(200); } + @Test + public void testMoneyType() + { + when().get("tests/types/money").then().statusCode(200); + } + @Test public void testSecurityRequirementEndpoint() diff --git a/openapi/src/test/resources/application.conf b/openapi/src/test/resources/application.conf new file mode 100644 index 0000000..ddd47d4 --- /dev/null +++ b/openapi/src/test/resources/application.conf @@ -0,0 +1,93 @@ + +application { + + env = dev + + version = "1.0" + + name="proteus" + + path = "/v1" + + host = "localhost" + + ports { + http = 0 + # https = 8443 + } + + charset = UTF-8 + + fallbackHandler = "io.sinistral.proteus.server.handlers.ServerFallbackHandler" + + defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" + + jacksonModule = "io.sinistral.proteus.modules.JacksonModule" + + tmpdir = ${java.io.tmpdir}/${application.name} + + # path to default favicon file + favicon = "/io/sinistral/proteus/favicon.ico" + +} + +api.version="v1" + +globalHeaders +{ +# Access-Control-Allow-Origin: "*" +# Access-Control-Allow-Methods: "*" +# Access-Control-Allow-Headers: "*" + Server = ${application.name} +} + +assets { + # the base path assets will be server from + path = "/public" + # the directory to load the assets from + dir = "./assets" + cache { + # cache timeout for the assets + time = 500 + } + + +} + + + + + +undertow +{ + server { + enableHttp2 = false + alwaysSetDate = true + alwaysSetKeepAlive = false + recordRequestStartTime = false + maxEntitySize = 100M + bufferPipelinedData = false + } + + socket { + backlog = 10000 + } + + + ssl { + enabled=false + keystorePath="development.jks" + truststorePath="development.ts" + keystorePassword="password" + truststorePassword="password" + } + + enableHttp2=false + # x AvailableProcessors + ioThreadsMultiplier = 2 + workerThreadMultiplier = 8 + bufferSize = 16K + directBuffers = true +} + + \ No newline at end of file diff --git a/openapi/src/test/resources/development.jks b/openapi/src/test/resources/development.jks new file mode 100644 index 0000000000000000000000000000000000000000..96d0454c8fe518f8eaad45b4578608019d43f207 GIT binary patch literal 3916 zcmchZWl$83+Qk>xUAj}c8`%Y6Dd}8Vgrz%_Se9BsU||uE4oN|3RZSxk{}!dY2in8eALZwP4F1dP zspd8_008oM2$nn+LIb9d022X;#EI#Nh(G`!mfVj6l}ey6X`#3i)e+~HR7*0+kJ2!Y zdf4}Q*SVGx6Gr9auNz+QHnb#R#b{4fh=;@Xj};d?ki` zGKqsc|N2YjZj8F1(=@@Z=2!9&*Ttf5&O*p8Tv2V@Tp)3)BNbGiGD=IsSNLJy4}5k) z;lz-u{HovBl7dpAf@%lCalv+;_vy-RKB4ylti;>O`<%LdZ!nvw-#P_CGZ7Sj`gTx) zZ>F|@k^tNlZP6r`_P2$5Ga-H9!Qg#$AibXZ0nosKHsmnczN|o)OS<>!=vK5?|bDto&+U-fLCGCKL6O7-;F`tV$mHAvJ%Yby}VzTTAqdvgoDTEdJ@w zX$D$2TB_x|zO}{ECbq~asu_!l8{-7C~fDe1!&Ks{DsNdGg zQ@{SRN|yaQ*;QhDxW}DCj-{h}ZX7@<6>XkrV;}kCiZ$5cF$4c+Ow9VE%`L-3zYpOK z#d}3dg=~F@Ump?bYzQ}=8x{yWPHnQuEo>gjt}ASyE+?3E<|>?BqfUyRZMv#4!3AX> z0lN$6f*Z`HQRZ?sI!8F7os+h!vL8R`Gtrd&`i5GDo(~INh|*ffa`jCx2e2J{#C(4n z1TL&7IvZY28mEIyFDakM2iR)*(ER#D`DR?(JF%W>Vh;BZAExaYd8PN#Ix77GTIcDw zeEZq!O{H4ISTnEtAl`ozEVGvp!2BvFz^jUVQXn{WZAWHJU;13FH{Wa5W?*gG6{zw( zn60L_)+cie*I*EtxhzN=ivLw4*y(8I`A&7t8S`q%)*6m^D|MQd)6;zLrQ-|Z*oL{S z3rElBFKxL8@R-&KSH#RV-RsPCwxRSN_|WA0YOG^=sW5}fG7CIL_gsMct^%EDUiV$O zOcs0;aREWUE3FPU2)IjoBvM+{`3FN>URJknBr93Q!5idYB6f!MoypOr~ z-AvtlWXbPsFBQ%Slhv1){vp|y4X)8>DtP#n+k^prGtl(@nmN7l3YQ=i_^CDY?yAVCHBF8QCOmr48>wDw-u>zgn6IBfqXI_4N zQqutDu>-AnVah_&Z}(c7Kd6def{$Jw3(rPqo9|Go)0}{r~Zrd0aq!)WQo2|>{G8F@$?M#M6l{c_YJp)4U_LMIJpdi?|p#QZkGkjyuA9YOmE zN^M67tyu3I#j(mo<8o_LE0sfoqsW1`sr6i=SC7bu$KsM-@KwBrZoH8%_#MlHD0r=^ zFLB<$oWPS?zSjEa6>`nnEmg^)=Vuk1MvH9f$wA^hz%Z?ay3_k{a>)g-v1HCo>nl9A zitf}sZ5-q#3gb4DUI_a}uDU!|w-cx1J`J?Y}Ueib{TCb2L0ayR1Tz8ik8~2Kx7edyhvJQjk1UJg2`TU9^%ej4ns+W~dk@U+3XAC} z=W?LEj|IWWt;5e`MT*rdiu&X@4b{Z59w$C8O1a9(3C<9)3SFoQkP=0j#;k8{_D_@A z7>GVxe==+JCm<>4YZP0el*Px#YQ57gRUEJ4yugg0BGpe+(@J%;%*~z(PYI-AKn=hh zKI+88uFWPHl3Vc6UQr*+=a%lz6&g~}(D3TKyT_TJdYOJkK^7I}h?=Th&So;3h51O_ zWwv=&4@mzZz2}yPStZqi#^^v+Va?6wX{=qF*Y+kEGg37^HVtiqUgQQhw13EUX+YSxm1c zE{8?5KI#>e;0Rop*oNZy%wb>Am?f|4Deg(>lM1vAE-my9q4uA6(S%5t&EF-1x1n z=V1BAmYLCh`pjXpue7xLGaiMCJl;A`K1Q0Ofj5S16+2hyl#SC}{J`ieSRm4OK<`t) z!WP%5a0CA-$%!pRd;MAE9kQ$)5>}2-M7m{xY5%M%eP-!&0RX5snG*OmZgGvNh>7gg zO4l~U*2m24g|8(|w{zj~0749D%&w`>N*kRaKqkRkzDm<6PYZ7}WP``wCoT%bg#Z(0 zjWauF8E%p3=V(gPcG75z2UCeG)GOK2S2K|WzDxwoAUuB(^43C~^?h_XsVcJgr zUT7fen{p6E008s=f(3;_up~}pB*a9-#6Z9M!BP-vFu8bwe!_2RA|gr<0OAX!`@7mm zEP-G~QcI){(mxdW|Ag!xg!CVT;vaei0{{Mc*-@!_PC8h;Hu*4L^SYjeTTz{qP&KC6R*XrTiYNMtj4Y4+_z)#LP zfuLs1c{C4^<*KZKYROc_vxEFWe6Y~y#I|Yh-TC>mC%IB1DOS2d zv0yHE%^Y)|cQSdc9EGlthtjk(c-b_Bvbgc7kGaPJE-A@ajFS699IDOxLBL2cvK=K z(U8uf$I#Um3u(~+D*@CUT=t<_(eoApd7@_WHjM5>^(M(hem zEJ1`N|4^zCs*)6m_usE`0HpZdn>cV4^qOx=&y!SU7HV&Qr?;F2K=0T}(&7r<@Q1|o z8>v{{SKLPA&sd1By`$WfyVMXD2Cbep3-&y{okJLmt|q_8uwS*|Q# z`6;P)=0L&ygxCa)x?~rZVPX;@01=K4!UN&@+Yl}qAPbNQFbfi^bSb!kSG~XKv=ZcA zC}_R)-y!q<4GG1;r@Zysrzt!ns7$6WrE-R+d7-Oxa6@M>= ze~Hn!h~0u%>}r*UMXD|?d??~aM0B-J(g-ucs_EGd`(Yj9`N~{%dCs} z9kmjUL%c{qxx=X>-VZ?yx~Xn|zY)vkh|-8s$kJz$9K?Phgp^{U-9mF`mURnH1@}gN z3WaD2g25Yt7eCj3tJF?5?V6})8l6i#13r3y=pK19K#sA)KKT_mnSZ|Zxo)*)8zWG6o~%pf8b$bx2KERt&tB$chB#yX7+*@{D@(VDRK z+aHjNF|7?O`@y}P)x(^r#Q?s#*-&aCSG&$17mT0c8kK1)I4^@IsA ztd|1Io|b3de%|_v#i4w6+5hW@m+LC5IQ4XIGnev`ly9*|_vSnI=LvtFr>niOPt09t z@4-fc?T&0#4ED?$;@3ZVv3hE!^A@)B-Al{Z_jW0rPMIPd`$$>j zHT_c;oBly}v!P9A&5itbYhoF?b22WzPf|(0y|Ztcj-@Fym%=3;p&3smp86X0p_cK{ zDzTjY=m>H1_2GvlBm`aw<+a60$@{Uj1QqeDDmbpaQ}<}tzxIMY<^K*76ej!Mo9@Qr z@IfurAo|DM`H$+aM!cQ(Btgh||IT2kY3{E-n41f%>YJV6edFk!mj~anEs)w%v%~5M z+m^JH<-5z?2ZTPl+oZ(J`}!7Z$>D#EZH{ZY5))4{F*7nSE><*#Ul~6KDMOT5(J-{!wwtOqGb2$cYG;r+|rwk-^fuS4?@9D7XEG4&R5{Jku3TIZgJY zdKW5}avEzoZf=Nhc5cmRPN|nMYjp3piUG_LL=? zRYmRd8o$0(ha+UHd;X|Sy6z_GR{y>BQRU+BW!(iSUOd{zP8fRb?7PkCOIzOf**N+9(q6*S zQ60YT^rjQpR{vf(M^1n9r|7YF+P(7TzX|P%oIYOb=a|g)-G5hne$ee|)mc?6_g5xI zp8mt6!B+R=|9%G-S1R%#Ir80i4{%$U$fSG=i3#02lJk8YhS%f@vz@ycgxqC z6DK`g`<`!Sir}il74LQ&h%hs|;iI_s_ - + - - - + + + diff --git a/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java b/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java index c83bfff..fd4c29c 100644 --- a/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java +++ b/swagger/src/test/java/io/sinistral/proteus/swagger/test/controllers/Tests.java @@ -15,13 +15,23 @@ import io.swagger.annotations.ApiOperation; import io.undertow.server.HttpServerExchange; -import javax.ws.rs.*; +import javax.ws.rs.BeanParam; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.io.File; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.time.Instant; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import static io.sinistral.proteus.server.ServerResponse.response; diff --git a/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java b/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java index 131200b..178fdc2 100644 --- a/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java +++ b/swagger/src/test/java/io/sinistral/proteus/swagger/test/server/TestControllerEndpoints.java @@ -3,31 +3,26 @@ */ package io.sinistral.proteus.swagger.test.server; -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import io.restassured.http.ContentType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; -import java.io.InputStream; import java.nio.file.Files; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.stream.LongStream; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.restassured.http.ContentType; -import org.slf4j.LoggerFactory; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; /* * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; */ + /** * @author jbauer */ diff --git a/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java b/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java new file mode 100644 index 0000000..dc2b9aa --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java @@ -0,0 +1,261 @@ +/** + * + */ +package io.sinistral.proteus.test.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.sinistral.proteus.annotations.Blocking; +import io.sinistral.proteus.server.ServerRequest; +import io.sinistral.proteus.server.ServerResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.undertow.server.HttpServerExchange; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static io.sinistral.proteus.server.ServerResponse.response; + +/** + * @author jbauer + * + */ + +@Api(tags="tests") +@Path("/tests") +@Produces((MediaType.APPLICATION_JSON)) +@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) +@Singleton +public class Tests +{ + private static final ByteBuffer buffer; + static { + String message = "Hello, World!"; + byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + buffer = ByteBuffer.allocateDirect(messageBytes.length); + buffer.put(messageBytes); + buffer.flip(); + } + + @Inject + protected ObjectMapper objectMapper; + + + @GET + @Path("exchange/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @ApiOperation(value = "Plaintext endpoint" ) + public void exchangePlaintext(HttpServerExchange exchange) + { + response("Hello, World!").textPlain().send(exchange); + + } + + + @GET + @Path("response/plaintext") + @Produces((MediaType.TEXT_PLAIN)) + @ApiOperation(value = "Plaintext endpoint" ) + public ServerResponse responsePlaintext(ServerRequest request) + { + return response("Hello, World!").textPlain(); + + } + + @GET + @Path("response/future/map") + @ApiOperation(value = "Future map endpoint" ) + public CompletableFuture>> responseFutureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return CompletableFuture.completedFuture(response( map ).applicationJson()); + } + + @GET + @Path("response/map") + @ApiOperation(value = "Map endpoint" ) + public ServerResponse> futureMap( ServerRequest request ) + { + Map map = ImmutableMap.of("message", "success"); + return response( map ).applicationJson(); + } + + @POST + @Path("response/file/path") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @ApiOperation(value = "Upload file path endpoint" ) + public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception + { + return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); + } + + @POST + @Path("response/file/path/optional") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @ApiOperation(value = "Upload optional file path endpoint" ) + public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception + { + if(file.isPresent()) + { + return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); + } + else + { + return response().noContent(); + } + } + + @GET + @Path("generic/set") + @Produces((MediaType.APPLICATION_JSON)) + @ApiOperation(value = "Generic set endpoint" ) + public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/set/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Generic bean set endpoint" ) + public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + + @POST + @Path("generic/list/bean") + @Produces((MediaType.APPLICATION_JSON)) + @Consumes(MediaType.APPLICATION_JSON) + + @ApiOperation(value = "Generic bean list endpoint" ) + public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception + { + return response( ids ).applicationJson(); + } + + @GET + @Path("optional/set") + @Produces((MediaType.APPLICATION_JSON)) + @ApiOperation(value = "Generic optional set endpoint" ) + public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception + { + return response( ids.get() ).applicationJson(); + } + + + @POST + @Path("response/parse/ids") + @Blocking + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Convert ids") + public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception + { + + return response( ids ).applicationJson(); + + + } + + @GET + @Path("response/parse/timestamp") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Convert timestamp") + public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception + { + return response().body(timestamp.toString()).textPlain(); + } + + @GET + @Path("response/parse/instant") + @Blocking + @Produces(MediaType.TEXT_PLAIN) + @ApiOperation(value = "Convert instant") + public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception + { + + return response().body(instant.toString()).textPlain(); + + + } + + @POST + @Path("response/bytebuffer") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @ApiOperation(value = "Upload file path endpoint") + public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception + { + + return response(file).applicationOctetStream(); + + + } + + @POST + @Path("response/file") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @ApiOperation(value = "Upload file path endpoint") + public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception + { + + ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); + + + return response(response).applicationOctetStream(); + + + } + + @GET + @Path("response/debug") + @ApiOperation(value = "Debug endpoint") + public ServerResponse> debugEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + + @GET + @Path("response/debug/blocking") + @Blocking + @ApiOperation(value="Debug blocking endpoint") + public ServerResponse> debugBlockingEndpoint(ServerRequest request) + { + try + { + Map map = ImmutableMap.of("message", "Hello, World!"); + + return response( map ).applicationJson(); + } catch(Exception e) + { + return response().badRequest(e); + } + } + +} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java b/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java new file mode 100644 index 0000000..af414eb --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java @@ -0,0 +1,129 @@ +/** + * + */ +package io.sinistral.proteus.test.server; + +import io.restassured.RestAssured; +import io.sinistral.proteus.ProteusApplication; +import io.sinistral.proteus.services.AssetsService; +import io.sinistral.proteus.swagger.services.SwaggerService; +import io.sinistral.proteus.test.controllers.Tests; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @author jbauer + */ +public class DefaultServer extends BlockJUnit4ClassRunner +{ + private static Logger log = LoggerFactory.getLogger(DefaultServer.class.getCanonicalName()); + + private static boolean first = true; + + /** + * @param clazz + * @throws InitializationError + */ + public DefaultServer(Class clazz) throws InitializationError + { + super(clazz); + } + + @Override + public void run(final RunNotifier notifier) + { + notifier.addListener(new RunListener() + { + @Override + public void testStarted(Description description) throws Exception + { + + super.testStarted(description); + } + + @Override + public void testFinished(Description description) throws Exception + { + + super.testFinished(description); + } + }); + + runInternal(notifier); + + super.run(notifier); + } + + private static void runInternal(final RunNotifier notifier) + { + + if (first) + { + + first = false; + + final ProteusApplication app = new ProteusApplication(DefaultServer.class.getClassLoader().getResource("application.conf")); + + app.addService(SwaggerService.class); + app.addService(AssetsService.class); + + app.addController(Tests.class); + + app.start(); + + int port = 0; + + try + { + Thread.sleep(5000); + + System.out.println(app.getPorts()); + + List ports = app.getPorts(); + + port = ports.get(0); + + } catch (Exception e) + { + e.printStackTrace(); + } + + + + RestAssured.baseURI = String.format("http://localhost:%d/v1",port); + + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + + while (!app.isRunning()) + { + try + { + Thread.sleep(100L); + } catch (InterruptedException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + notifier.addListener(new RunListener() + { + @Override + public void testRunFinished(final Result result) throws Exception + { + app.shutdown(); + }; + }); + } + + } + +} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java b/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java new file mode 100644 index 0000000..4a4864f --- /dev/null +++ b/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java @@ -0,0 +1,88 @@ +/** + * + */ +package io.sinistral.proteus.test.server; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.stream.LongStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.restassured.http.ContentType; + +/* + * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; + */ +/** + * @author jbauer + */ +@RunWith(DefaultServer.class) +public class TestControllerEndpoints +{ + + private File file = null; + + private Set idSet = new HashSet<>(); + + + @Before + public void setUp() + { + try + { + byte[] bytes = new byte[8388608]; + Random random = new Random(); + random.nextBytes(bytes); + + file = Files.createTempFile("test-asset", ".mp4").toFile(); + + LongStream.range(1L,10L).forEach( l -> { + + idSet.add(l); + }); + + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void testSwaggerDocs() + { + given().accept(ContentType.JSON).when().get("swagger.json").then().statusCode(200).and().body("basePath", is("/v1")); + } + + + + @After + public void tearDown() + { + try + { + if(file.exists()) + { + file.delete(); + } + + } catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} From 6375bfa1910ca71dc5a329b31d654fcb9dccb0d2 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Tue, 23 Apr 2019 09:01:51 -0700 Subject: [PATCH 4/7] Added support for customizing OpenAPI type converters and externalized Jackson module. --- core/src/main/resources/reference.conf | 5 +- core/src/test/resources/logback-test.xml | 6 +- openapi/pom.xml | 1 + .../proteus/openapi/jaxrs2/Reader.java | 11 +- .../openapi/jaxrs2/ServerModelResolver.java | 46 +-- .../jaxrs2/ServerParameterExtension.java | 20 +- .../openapi/services/OpenAPIService.java | 72 ++--- .../test/controllers/OpenAPITests.java | 14 +- .../test/converters}/MoneyModelConverter.java | 50 ++- .../openapi/test/modules/JacksonModule.java | 35 +++ .../test/server/OpenAPIDefaultServer.java | 1 + .../TestOpenAPIControllerEndpoints.java | 18 +- .../proteus/test/controllers/Tests.java | 287 ------------------ .../proteus/test/server/DefaultServer.java | 129 -------- .../test/server/TestControllerEndpoints.java | 100 ------ openapi/src/test/resources/application.conf | 10 +- openapi/src/test/resources/logback-test.xml | 11 +- .../swagger/services/SwaggerService.java | 2 +- .../proteus/test/controllers/Tests.java | 261 ---------------- .../proteus/test/server/DefaultServer.java | 129 -------- .../test/server/TestControllerEndpoints.java | 88 ------ swagger/src/test/resources/logback-test.xml | 3 - 22 files changed, 126 insertions(+), 1173 deletions(-) rename openapi/src/{main/java/io/sinistral/proteus/openapi/models => test/java/io/sinistral/proteus/openapi/test/converters}/MoneyModelConverter.java (73%) create mode 100644 openapi/src/test/java/io/sinistral/proteus/openapi/test/modules/JacksonModule.java delete mode 100644 openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java delete mode 100644 openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java delete mode 100644 openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java delete mode 100644 swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java delete mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java delete mode 100644 swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 796bf00..a11b71a 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -63,7 +63,6 @@ assets { openapi { - basePath= ${application.path}"/openapi" redocPath= "redoc" @@ -95,6 +94,10 @@ openapi { description="Default Server" } ] + + converters = [ + "io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver" + ] } diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 8763c33..44a9e83 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -16,12 +16,8 @@ - - - + - - diff --git a/openapi/pom.xml b/openapi/pom.xml index 1758cea..f4ea1ef 100644 --- a/openapi/pom.xml +++ b/openapi/pom.xml @@ -114,6 +114,7 @@ org.zalando jackson-datatype-money 1.1.1 + test diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java index b3e3d7d..69522a4 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/Reader.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.type.TypeFactory; -import io.sinistral.proteus.openapi.models.MoneyModelConverter; import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; import io.swagger.v3.core.converter.AnnotatedType; @@ -1374,19 +1373,11 @@ else if (classType.getRawClass().isAssignableFrom(CompletableFuture.class)) } } - ModelConverters converters = ModelConverters.getInstance(); - - converters.addConverter(new ServerModelResolver()); - converters.addConverter(new MoneyModelConverter()); - - - ResolvedSchema resolvedSchema = converters + ResolvedSchema resolvedSchema = ModelConverters.getInstance() .resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation)); if (resolvedSchema.schema != null) { - System.out.println("resolvedSchema: " + resolvedSchema.schema); - Schema returnTypeSchema = resolvedSchema.schema; Content content = new Content(); MediaType mediaType = new MediaType().schema(returnTypeSchema); diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java index 0d364f5..b5981c2 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerModelResolver.java @@ -8,15 +8,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.type.TypeFactory; -import io.sinistral.proteus.openapi.models.MoneyModelConverter; 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; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.media.Schema; -import org.javamoney.moneta.Money; -import org.zalando.jackson.datatype.money.MoneyModule; import java.io.File; import java.lang.annotation.Annotation; @@ -33,19 +30,9 @@ public class ServerModelResolver extends io.swagger.v3.core.jackson.ModelResolve { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName()); - static final ObjectMapper mapper = Json.mapper(); - - static - { - mapper.registerModule(new MoneyModule()); - } - public ServerModelResolver() { - super(mapper); - - - System.out.println("not from super"); + super(Json.mapper()); } /** @@ -53,8 +40,7 @@ public ServerModelResolver() */ public ServerModelResolver(ObjectMapper mapper) { - super(ServerModelResolver.mapper); - + super(mapper); } /* @@ -72,7 +58,6 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if ((rawClass != null) &&!resolvedType.isPrimitive()) { - // log.debug("resolvedType in " + resolvedType); if (rawClass.isAssignableFrom(ServerResponse.class)) { resolvedType = classType.containedType(0); @@ -83,25 +68,18 @@ else if (rawClass.isAssignableFrom(CompletableFuture.class)) 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("org.javamoney.moneta.Money")) -// { -// resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Money.class.getName()); -// } - if (resolvedType.getTypeName().contains("java.lang.Void")) { resolvedType = TypeFactory.defaultInstance().constructFromCanonical(Void.class.getName()); @@ -133,30 +111,12 @@ else if (resolvedType.getTypeName().contains("Optional")) annotatedType.setType(resolvedType); - // log.debug("resolvedType out " + resolvedType); } } try { - - // log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName()); - - if(annotatedType.getType().getTypeName().contains("org.javamoney.moneta.Money")) - { - MoneyModelConverter.MoneySchema schema = new MoneyModelConverter.MoneySchema(); - - context.defineModel("money",schema); - - return schema; - } - else { - - Schema schema = super.resolve(annotatedType, context, next); - - return schema; - } - + return super.resolve(annotatedType, context, next); } catch (Exception e) { diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java index 56e9efb..2ba39a5 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/jaxrs2/ServerParameterExtension.java @@ -4,8 +4,6 @@ */ package io.sinistral.proteus.openapi.jaxrs2; -import org.zalando.jackson.datatype.money.MoneyModule; - import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; @@ -24,10 +22,19 @@ import io.swagger.v3.oas.models.parameters.Parameter; import org.apache.commons.lang3.StringUtils; -import javax.ws.rs.*; +import javax.ws.rs.BeanParam; +import javax.ws.rs.CookieParam; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.MatrixParam; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; /** * @author jbauer @@ -43,11 +50,6 @@ public class ServerParameterExtension extends AbstractOpenAPIExtension final static ObjectMapper mapper = Json.mapper(); - static - { - mapper.registerModule(new MoneyModule()); - } - @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) diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java index 0d5aeeb..c3daba8 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java @@ -1,55 +1,17 @@ package io.sinistral.proteus.openapi.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.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; -import java.util.jar.JarFile; -import java.util.stream.Collectors; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MediaType; - -import io.sinistral.proteus.openapi.jaxrs2.Reader; -import io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver; -import io.sinistral.proteus.openapi.jaxrs2.ServerParameterExtension; -import io.sinistral.proteus.openapi.models.MoneyModelConverter; -import io.sinistral.proteus.services.DefaultService; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.core.type.TypeReference; 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.openapi.jaxrs2.Reader; +import io.sinistral.proteus.openapi.jaxrs2.ServerParameterExtension; import io.sinistral.proteus.server.endpoints.EndpointInfo; - +import io.sinistral.proteus.services.DefaultService; import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions; @@ -62,7 +24,6 @@ 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.HttpServerExchange; import io.undertow.server.RoutingHandler; @@ -72,6 +33,31 @@ import io.undertow.util.CanonicalPathUtils; import io.undertow.util.Headers; import io.undertow.util.Methods; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; +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.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.jar.JarFile; +import java.util.stream.Collectors; /** * A service for generating and serving an OpenAPI v3 spec and ui. @@ -268,7 +254,7 @@ protected void generateSpec() throws Exception SwaggerConfiguration config = new SwaggerConfiguration().resourceClasses(classes.stream().map(Class::getName).collect(Collectors.toSet())).openAPI(openApi); - config.setModelConverterClassess(new HashSet<>(Arrays.asList(ServerModelResolver.class.getName()))); + config.setModelConverterClassess(new HashSet<>( openAPIConfig.getStringList("converters") )); OpenApiContext ctx = new GenericOpenApiContext().openApiConfiguration(config) .openApiReader(new Reader(config)) diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java index 9ddefb8..4796233 100644 --- a/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/controllers/OpenAPITests.java @@ -148,25 +148,17 @@ public ServerResponse> genericSet( ServerRequest request, @QueryParam @Operation(description = "Money type endpoint" ) public ServerResponse getMoney(ServerRequest request ) throws Exception { - return response( Money.of(100.0,"USD") ).applicationJson(); + return response( Money.of(123.23,"USD") ).applicationJson(); } - @GET - @Path("types/pojo2") - @Produces((MediaType.APPLICATION_JSON)) - @Operation(description = "Money type endpoint" ) - public ServerResponse getPojo2(ServerRequest request ) throws Exception - { - return response( new Pojo(100L,"USD") ).applicationJson(); - } @GET @Path("types/pojo") @Produces((MediaType.APPLICATION_JSON)) - @Operation(description = "Money type endpoint" ) + @Operation(description = "Pojo type endpoint" ) public ServerResponse getPojo(ServerRequest request ) throws Exception { - return response( new Pojo(100L,"USD") ).applicationJson(); + return response( new Pojo(100L,"John Doe") ).applicationJson(); } @POST diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/converters/MoneyModelConverter.java similarity index 73% rename from openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java rename to openapi/src/test/java/io/sinistral/proteus/openapi/test/converters/MoneyModelConverter.java index f37539e..b4c25fe 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/models/MoneyModelConverter.java +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/converters/MoneyModelConverter.java @@ -1,4 +1,4 @@ -package io.sinistral.proteus.openapi.models; +package io.sinistral.proteus.openapi.test.converters; import com.fasterxml.jackson.databind.JavaType; @@ -21,6 +21,8 @@ public class MoneyModelConverter implements ModelConverter { + private final Schema schema = new MoneySchema(); + @Override public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { @@ -28,39 +30,44 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato if(_type != null && (_type.getRawClass().equals(Money.class))) { - return new MoneySchema(); + context.defineModel("Money",schema, type, null); + + return schema; } if (type.getType() instanceof Class) { - System.out.println("MoneyModelConverter resolving class: " + ((Class) type.getType()).getName()); - Class cls = (Class) type.getType(); if(cls.isAssignableFrom(Money.class)) { - return new MoneySchema(); + context.defineModel("Money",schema, type, null); + + return schema; } } else if(type.getType().getTypeName().equals("[simple type, class org.javamoney.moneta.Money]")) { - System.out.println("is org.javamoney.moneta.Money"); - return new MoneySchema(); + context.defineModel("Money",schema, type, null); + + return schema; } else if (type.isSchemaProperty()) { + _type = Json.mapper().constructType(type.getType()); if (_type != null) { Class cls = _type.getRawClass(); if (Money.class.isAssignableFrom(cls)) { - return new MoneySchema(); + + context.defineModel("Money",schema, type, null); + + return schema; } } } if (chain.hasNext()) { - System.out.println("skipped resolving " + _type); - return chain.next().resolve(type, context, chain); } @@ -73,13 +80,13 @@ public static class MoneySchema extends Schema { private String _type = "object"; - private String _$ref = null;// "#/components/schemas/Money"; + private String _$ref = null; private String _description = "A monetary amount"; - private Map _properties = ImmutableMap.of("number",new NumberSchema(),"currency",new StringSchema()); + private Map _properties = ImmutableMap.of("amount",new NumberSchema(),"currency",new StringSchema()); - private List _required = Arrays.asList("number","currency"); + private List _required = Arrays.asList("amount","currency"); public MoneySchema() { @@ -89,6 +96,7 @@ public MoneySchema() super.set$ref(_$ref); super.description(_description); super.properties(_properties); + super.required(_required); } @Override @@ -117,6 +125,7 @@ public boolean equals(java.lang.Object o) MoneySchema MoneySchema = (MoneySchema) o; return Objects.equals(this._type, MoneySchema._type) && Objects.equals(this._properties, MoneySchema._properties) && + Objects.equals(this._description, MoneySchema._description) && super.equals(o); } @@ -126,21 +135,6 @@ public int hashCode() return Objects.hash(_type, _properties, super.hashCode()); } -// @Override -// public String toString() -// { -// StringBuilder sb = new StringBuilder(); -// sb.append("class MoneySchema {\n"); -// sb.append(" ").append(toIndentedString(super.toString())).append("\n"); -// sb.append(" title: ").append(toIndentedString(getTitle())).append("\n"); -// sb.append(" type: ").append(toIndentedString(_type)).append("\n"); -// sb.append(toIndentedString(_properties.toString())).append("\n"); -// sb.append(" }").append("\n"); -// -// sb.append("}"); -// return sb.toString(); -// } - /** * Convert the given object to string with each line indented by 4 spaces * (except the first line). diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/modules/JacksonModule.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/modules/JacksonModule.java new file mode 100644 index 0000000..4bf0036 --- /dev/null +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/modules/JacksonModule.java @@ -0,0 +1,35 @@ +package io.sinistral.proteus.openapi.test.modules; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import org.zalando.jackson.datatype.money.MoneyModule; + +@Singleton +public class JacksonModule extends AbstractModule +{ + + @Override + protected void configure() + { + + 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 MoneyModule()) + .registerModule(new AfterburnerModule()) + .registerModule(new Jdk8Module()); + + this.bind(ObjectMapper.class).toInstance(objectMapper); + + } +} \ No newline at end of file diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java index d22cac3..fbb6e1d 100644 --- a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/OpenAPIDefaultServer.java @@ -73,6 +73,7 @@ private static void runInternal(final RunNotifier notifier) final ProteusApplication app = new ProteusApplication(); app.addService(OpenAPIService.class); + app.addService(AssetsService.class); app.addController(OpenAPITests.class); diff --git a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java index 7f003ce..9961e44 100644 --- a/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java +++ b/openapi/src/test/java/io/sinistral/proteus/openapi/test/server/TestOpenAPIControllerEndpoints.java @@ -16,10 +16,7 @@ import java.util.stream.LongStream; import static io.restassured.RestAssured.when; - -/* - * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; - */ +import static org.hamcrest.CoreMatchers.equalTo; /** * @author jbauer @@ -61,22 +58,19 @@ public void setUp() @Test public void testOpenAPIDocs() { - when().get("openapi.yaml").then().log().all().statusCode(200); + when().get("openapi.yaml").then().statusCode(200); } + @Test public void testPojoType() { - when().get("tests/types/pojo").then().log().all().statusCode(200); - } - - @Test public void testPojoType2() - { - when().get("tests/types/pojo2").then().log().all().statusCode(200); + when().get("tests/types/pojo").then().statusCode(200).body("id", equalTo(100),"name", equalTo("John Doe")); } + @Test public void testMoneyType() { - when().get("tests/types/money").then().log().all().statusCode(200); + when().get("tests/types/money").then().statusCode(200).body("amount", equalTo(123.23f),"currency", equalTo("USD")); } diff --git a/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java b/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java deleted file mode 100644 index 6616627..0000000 --- a/openapi/src/test/java/io/sinistral/proteus/test/controllers/Tests.java +++ /dev/null @@ -1,287 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.controllers; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.Files; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import io.sinistral.proteus.annotations.Blocking; -import io.sinistral.proteus.server.ServerRequest; -import io.sinistral.proteus.server.ServerResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.tags.Tags; -import io.undertow.server.HttpServerExchange; -import org.javamoney.moneta.Money; - -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import java.io.File; -import java.nio.ByteBuffer; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import static io.sinistral.proteus.server.ServerResponse.response; - -/** - * @author jbauer - * - */ - -@Tags({@Tag(name = "tests")}) -@Path("/tests") -@Produces((MediaType.APPLICATION_JSON)) -@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) -@Singleton -public class Tests -{ - private static final ByteBuffer buffer; - static { - String message = "Hello, World!"; - byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - buffer = ByteBuffer.allocateDirect(messageBytes.length); - buffer.put(messageBytes); - buffer.flip(); - } - - @Inject - protected ObjectMapper objectMapper; - - - @GET - @Path("exchange/plaintext") - @Produces((MediaType.TEXT_PLAIN)) - @Operation(description = "Plaintext endpoint" ) - public void exchangePlaintext(HttpServerExchange exchange) - { - response("Hello, World!").textPlain().send(exchange); - - } - - - @GET - @Path("response/plaintext") - @Produces((MediaType.TEXT_PLAIN)) - @Operation(description = "Plaintext endpoint" ) - public ServerResponse responsePlaintext(ServerRequest request) - { - return response("Hello, World!").textPlain(); - - } - - @GET - @Path("response/future/map") - @Operation(description = "Future map endpoint" ) - public CompletableFuture>> responseFutureMap( ServerRequest request ) - { - Map map = ImmutableMap.of("message", "success"); - return CompletableFuture.completedFuture(response( map ).applicationJson()); - } - - @GET - @Path("response/map") - @Operation(description = "Map endpoint" ) - public ServerResponse> futureMap( ServerRequest request ) - { - Map map = ImmutableMap.of("message", "success"); - return response( map ).applicationJson(); - } - - @POST - @Path("response/file/path") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Operation(description = "Upload file path endpoint" ) - public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception - { - return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); - } - - @POST - @Path("response/file/path/optional") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Operation(description = "Upload optional file path endpoint" ) - public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception - { - if(file.isPresent()) - { - return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); - } - else - { - return response().noContent(); - } - } - - @GET - @Path("generic/set") - @Produces((MediaType.APPLICATION_JSON)) - @Operation(description = "Generic set endpoint" ) - public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - - @GET - @Path("types/money") - @Produces((MediaType.APPLICATION_JSON)) - @Operation(description = "Money type endpoint" ) - public ServerResponse getMoney(ServerRequest request ) throws Exception - { - return response( Money.of(100.0,"USD") ).applicationJson(); - } - - - @POST - @Path("generic/set/bean") - @Produces((MediaType.APPLICATION_JSON)) - @Consumes(MediaType.APPLICATION_JSON) - @Operation(description = "Generic bean set endpoint" ) - public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - - @POST - @Path("generic/list/bean") - @Produces((MediaType.APPLICATION_JSON)) - @Consumes(MediaType.APPLICATION_JSON) - - @Operation(description = "Generic bean list endpoint" ) - public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - @GET - @Path("optional/set") - @Produces((MediaType.APPLICATION_JSON)) - @Operation(description = "Generic optional set endpoint" ) - public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception - { - return response( ids.get() ).applicationJson(); - } - - - @POST - @Path("response/parse/ids") - @Blocking - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - @Operation(description = "Convert ids") - public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception - { - - return response( ids ).applicationJson(); - - - } - - @GET - @Path("response/parse/timestamp") - @Blocking - @Produces(MediaType.TEXT_PLAIN) - @Operation(description = "Convert timestamp") - public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception - { - return response().body(timestamp.toString()).textPlain(); - } - - @GET - @Path("response/parse/instant") - @Blocking - @Produces(MediaType.TEXT_PLAIN) - @Operation(description = "Convert instant") - public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception - { - - return response().body(instant.toString()).textPlain(); - - - } - - @POST - @Path("response/bytebuffer") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes("*/*") - @Operation(description = "Upload file path endpoint") - public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception - { - - return response(file).applicationOctetStream(); - - - } - - @POST - @Path("response/file") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes("*/*") - @Operation(description = "Upload file path endpoint") - public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception - { - - ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); - - - return response(response).applicationOctetStream(); - - - } - - @GET - @Path("response/debug") - @Operation(description = "Debug endpoint") - public ServerResponse> debugEndpoint(ServerRequest request) - { - try - { - Map map = ImmutableMap.of("message", "Hello, World!"); - - return response( map ).applicationJson(); - } catch(Exception e) - { - return response().badRequest(e); - } - } - - @GET - @Path("response/debug/blocking") - @Blocking - @Operation(description="Debug blocking endpoint") - public ServerResponse> debugBlockingEndpoint(ServerRequest request) - { - try - { - Map map = ImmutableMap.of("message", "Hello, World!"); - - return response( map ).applicationJson(); - } catch(Exception e) - { - return response().badRequest(e); - } - } - - - @GET - @SecurityRequirement(name = "testRequirement") - @Path("secure/resource") - @Operation(description="Secure resource") - @Produces(MediaType.APPLICATION_JSON) - public ServerResponse> responseSecureContext() - { - Map responseMap = new HashMap<>(); - responseMap.put("secure",true); - - return response(responseMap); - } -} diff --git a/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java b/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java deleted file mode 100644 index fa501cf..0000000 --- a/openapi/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.server; - -import io.restassured.RestAssured; -import io.sinistral.proteus.ProteusApplication; -import io.sinistral.proteus.openapi.services.OpenAPIService; -import io.sinistral.proteus.services.AssetsService; -import io.sinistral.proteus.test.controllers.Tests; -import org.junit.runner.Description; -import org.junit.runner.Result; -import org.junit.runner.notification.RunListener; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.InitializationError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -/** - * @author jbauer - */ -public class DefaultServer extends BlockJUnit4ClassRunner -{ - private static Logger log = LoggerFactory.getLogger(DefaultServer.class.getCanonicalName()); - - private static boolean first = true; - - /** - * @param clazz - * @throws InitializationError - */ - public DefaultServer(Class clazz) throws InitializationError - { - super(clazz); - } - - @Override - public void run(final RunNotifier notifier) - { - notifier.addListener(new RunListener() - { - @Override - public void testStarted(Description description) throws Exception - { - - super.testStarted(description); - } - - @Override - public void testFinished(Description description) throws Exception - { - - super.testFinished(description); - } - }); - - runInternal(notifier); - - super.run(notifier); - } - - private static void runInternal(final RunNotifier notifier) - { - - if (first) - { - - first = false; - - final ProteusApplication app = new ProteusApplication(); - - app.addService(OpenAPIService.class); - app.addService(AssetsService.class); - - app.addController(Tests.class); - - app.start(); - - int port = 0; - - try - { - Thread.sleep(5000); - - System.out.println(app.getPorts()); - - List ports = app.getPorts(); - - port = ports.get(0); - - } catch (Exception e) - { - e.printStackTrace(); - } - - - - RestAssured.baseURI = String.format("http://localhost:%d/v1",port); - - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); - - while (!app.isRunning()) - { - try - { - Thread.sleep(100L); - } catch (InterruptedException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - notifier.addListener(new RunListener() - { - @Override - public void testRunFinished(final Result result) throws Exception - { - app.shutdown(); - }; - }); - } - - } - -} diff --git a/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java b/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java deleted file mode 100644 index 2f03a19..0000000 --- a/openapi/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.server; - -import static io.restassured.RestAssured.given; -import static io.restassured.RestAssured.when; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.nio.file.Files; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; -import java.util.stream.LongStream; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/* - * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; - */ -/** - * @author jbauer - */ -@RunWith(DefaultServer.class) -public class TestControllerEndpoints -{ - - private File file = null; - - private Set idSet = new HashSet<>(); - - @Before - public void setUp() - { - try - { - byte[] bytes = new byte[8388608]; - Random random = new Random(); - random.nextBytes(bytes); - - file = Files.createTempFile("test-asset", ".mp4").toFile(); - - LongStream.range(1L,10L).forEach( l -> { - - idSet.add(l); - }); - - - } catch (Exception e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - - - @Test - public void testOpenAPIDocs() - { - when().get("openapi.yaml").then().statusCode(200); - } - - @Test - public void testMoneyType() - { - when().get("tests/types/money").then().statusCode(200); - } - - - @Test - public void testSecurityRequirementEndpoint() - { - when().get("tests/secure/resource").then().statusCode(200); - } - - - @After - public void tearDown() - { - try - { - if(file.exists()) - { - file.delete(); - } - - } catch (Exception e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - -} diff --git a/openapi/src/test/resources/application.conf b/openapi/src/test/resources/application.conf index ddd47d4..a3b5b1b 100644 --- a/openapi/src/test/resources/application.conf +++ b/openapi/src/test/resources/application.conf @@ -22,7 +22,7 @@ application { defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" - jacksonModule = "io.sinistral.proteus.modules.JacksonModule" + jacksonModule = "io.sinistral.proteus.openapi.test.modules.JacksonModule" tmpdir = ${java.io.tmpdir}/${application.name} @@ -33,6 +33,8 @@ application { api.version="v1" +openapi.converters = ["io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver","io.sinistral.proteus.openapi.test.converters.MoneyModelConverter"] + globalHeaders { # Access-Control-Allow-Origin: "*" @@ -50,14 +52,8 @@ assets { # cache timeout for the assets time = 500 } - - } - - - - undertow { server { diff --git a/openapi/src/test/resources/logback-test.xml b/openapi/src/test/resources/logback-test.xml index 83ea4ff..733e338 100644 --- a/openapi/src/test/resources/logback-test.xml +++ b/openapi/src/test/resources/logback-test.xml @@ -16,12 +16,11 @@ - - - - - - + + + + + " diff --git a/swagger/src/main/java/io/sinistral/proteus/swagger/services/SwaggerService.java b/swagger/src/main/java/io/sinistral/proteus/swagger/services/SwaggerService.java index 3bf5a2a..c88e217 100644 --- a/swagger/src/main/java/io/sinistral/proteus/swagger/services/SwaggerService.java +++ b/swagger/src/main/java/io/sinistral/proteus/swagger/services/SwaggerService.java @@ -187,7 +187,7 @@ public void generateSwaggerSpec() throws Exception // String name = apiKeyConfig.getString("name"); // String value = apiKeyConfig.getString("value"); // -// io.swagger.models.auth.In keyLocation = io.swagger.models.auth.In.valueOf(apiKeyConfig.getString("in")); +// io.swagger.converters.auth.In keyLocation = io.swagger.converters.auth.In.valueOf(apiKeyConfig.getString("in")); // // final Predicate predicate; // diff --git a/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java b/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java deleted file mode 100644 index dc2b9aa..0000000 --- a/swagger/src/test/java/io/sinistral/proteus/test/controllers/Tests.java +++ /dev/null @@ -1,261 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.controllers; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.Files; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import io.sinistral.proteus.annotations.Blocking; -import io.sinistral.proteus.server.ServerRequest; -import io.sinistral.proteus.server.ServerResponse; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.undertow.server.HttpServerExchange; - -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import java.io.File; -import java.nio.ByteBuffer; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -import static io.sinistral.proteus.server.ServerResponse.response; - -/** - * @author jbauer - * - */ - -@Api(tags="tests") -@Path("/tests") -@Produces((MediaType.APPLICATION_JSON)) -@Consumes((MediaType.MEDIA_TYPE_WILDCARD)) -@Singleton -public class Tests -{ - private static final ByteBuffer buffer; - static { - String message = "Hello, World!"; - byte[] messageBytes = message.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - buffer = ByteBuffer.allocateDirect(messageBytes.length); - buffer.put(messageBytes); - buffer.flip(); - } - - @Inject - protected ObjectMapper objectMapper; - - - @GET - @Path("exchange/plaintext") - @Produces((MediaType.TEXT_PLAIN)) - @ApiOperation(value = "Plaintext endpoint" ) - public void exchangePlaintext(HttpServerExchange exchange) - { - response("Hello, World!").textPlain().send(exchange); - - } - - - @GET - @Path("response/plaintext") - @Produces((MediaType.TEXT_PLAIN)) - @ApiOperation(value = "Plaintext endpoint" ) - public ServerResponse responsePlaintext(ServerRequest request) - { - return response("Hello, World!").textPlain(); - - } - - @GET - @Path("response/future/map") - @ApiOperation(value = "Future map endpoint" ) - public CompletableFuture>> responseFutureMap( ServerRequest request ) - { - Map map = ImmutableMap.of("message", "success"); - return CompletableFuture.completedFuture(response( map ).applicationJson()); - } - - @GET - @Path("response/map") - @ApiOperation(value = "Map endpoint" ) - public ServerResponse> futureMap( ServerRequest request ) - { - Map map = ImmutableMap.of("message", "success"); - return response( map ).applicationJson(); - } - - @POST - @Path("response/file/path") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes(MediaType.MULTIPART_FORM_DATA) - @ApiOperation(value = "Upload file path endpoint" ) - public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception - { - return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); - } - - @POST - @Path("response/file/path/optional") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes(MediaType.MULTIPART_FORM_DATA) - @ApiOperation(value = "Upload optional file path endpoint" ) - public ServerResponse responseUploadOptionalFilePath(ServerRequest request, @FormParam("file") Optional file ) throws Exception - { - if(file.isPresent()) - { - return response(ByteBuffer.wrap(Files.toByteArray(file.get().toFile()))).applicationOctetStream(); - } - else - { - return response().noContent(); - } - } - - @GET - @Path("generic/set") - @Produces((MediaType.APPLICATION_JSON)) - @ApiOperation(value = "Generic set endpoint" ) - public ServerResponse> genericSet( ServerRequest request, @QueryParam("ids") Set ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - - @POST - @Path("generic/set/bean") - @Produces((MediaType.APPLICATION_JSON)) - @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Generic bean set endpoint" ) - public ServerResponse> genericBeanSet( ServerRequest request, @BeanParam Set ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - - @POST - @Path("generic/list/bean") - @Produces((MediaType.APPLICATION_JSON)) - @Consumes(MediaType.APPLICATION_JSON) - - @ApiOperation(value = "Generic bean list endpoint" ) - public ServerResponse> genericBeanList( ServerRequest request, @BeanParam List ids ) throws Exception - { - return response( ids ).applicationJson(); - } - - @GET - @Path("optional/set") - @Produces((MediaType.APPLICATION_JSON)) - @ApiOperation(value = "Generic optional set endpoint" ) - public ServerResponse> genericOptionalSet( ServerRequest request, @QueryParam("ids") Optional> ids ) throws Exception - { - return response( ids.get() ).applicationJson(); - } - - - @POST - @Path("response/parse/ids") - @Blocking - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Convert ids") - public ServerResponse> listConversion( ServerRequest request, @BeanParam List ids ) throws Exception - { - - return response( ids ).applicationJson(); - - - } - - @GET - @Path("response/parse/timestamp") - @Blocking - @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Convert timestamp") - public ServerResponse timestampConversion( ServerRequest request, @QueryParam("timestamp") Timestamp timestamp ) throws Exception - { - return response().body(timestamp.toString()).textPlain(); - } - - @GET - @Path("response/parse/instant") - @Blocking - @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Convert instant") - public ServerResponse instantConversion( ServerRequest request, @QueryParam("instant") Instant instant ) throws Exception - { - - return response().body(instant.toString()).textPlain(); - - - } - - @POST - @Path("response/bytebuffer") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes("*/*") - @ApiOperation(value = "Upload file path endpoint") - public ServerResponse responseUploadByteBuffer(ServerRequest request, @FormParam("file") ByteBuffer file ) throws Exception - { - - return response(file).applicationOctetStream(); - - - } - - @POST - @Path("response/file") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - @Consumes("*/*") - @ApiOperation(value = "Upload file path endpoint") - public ServerResponse responseUploadFile(ServerRequest request, @FormParam("file") File file ) throws Exception - { - - ByteBuffer response = ByteBuffer.wrap(Files.asByteSource(file).read()); - - - return response(response).applicationOctetStream(); - - - } - - @GET - @Path("response/debug") - @ApiOperation(value = "Debug endpoint") - public ServerResponse> debugEndpoint(ServerRequest request) - { - try - { - Map map = ImmutableMap.of("message", "Hello, World!"); - - return response( map ).applicationJson(); - } catch(Exception e) - { - return response().badRequest(e); - } - } - - @GET - @Path("response/debug/blocking") - @Blocking - @ApiOperation(value="Debug blocking endpoint") - public ServerResponse> debugBlockingEndpoint(ServerRequest request) - { - try - { - Map map = ImmutableMap.of("message", "Hello, World!"); - - return response( map ).applicationJson(); - } catch(Exception e) - { - return response().badRequest(e); - } - } - -} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java b/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java deleted file mode 100644 index af414eb..0000000 --- a/swagger/src/test/java/io/sinistral/proteus/test/server/DefaultServer.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.server; - -import io.restassured.RestAssured; -import io.sinistral.proteus.ProteusApplication; -import io.sinistral.proteus.services.AssetsService; -import io.sinistral.proteus.swagger.services.SwaggerService; -import io.sinistral.proteus.test.controllers.Tests; -import org.junit.runner.Description; -import org.junit.runner.Result; -import org.junit.runner.notification.RunListener; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.InitializationError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -/** - * @author jbauer - */ -public class DefaultServer extends BlockJUnit4ClassRunner -{ - private static Logger log = LoggerFactory.getLogger(DefaultServer.class.getCanonicalName()); - - private static boolean first = true; - - /** - * @param clazz - * @throws InitializationError - */ - public DefaultServer(Class clazz) throws InitializationError - { - super(clazz); - } - - @Override - public void run(final RunNotifier notifier) - { - notifier.addListener(new RunListener() - { - @Override - public void testStarted(Description description) throws Exception - { - - super.testStarted(description); - } - - @Override - public void testFinished(Description description) throws Exception - { - - super.testFinished(description); - } - }); - - runInternal(notifier); - - super.run(notifier); - } - - private static void runInternal(final RunNotifier notifier) - { - - if (first) - { - - first = false; - - final ProteusApplication app = new ProteusApplication(DefaultServer.class.getClassLoader().getResource("application.conf")); - - app.addService(SwaggerService.class); - app.addService(AssetsService.class); - - app.addController(Tests.class); - - app.start(); - - int port = 0; - - try - { - Thread.sleep(5000); - - System.out.println(app.getPorts()); - - List ports = app.getPorts(); - - port = ports.get(0); - - } catch (Exception e) - { - e.printStackTrace(); - } - - - - RestAssured.baseURI = String.format("http://localhost:%d/v1",port); - - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); - - while (!app.isRunning()) - { - try - { - Thread.sleep(100L); - } catch (InterruptedException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - notifier.addListener(new RunListener() - { - @Override - public void testRunFinished(final Result result) throws Exception - { - app.shutdown(); - }; - }); - } - - } - -} diff --git a/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java b/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java deleted file mode 100644 index 4a4864f..0000000 --- a/swagger/src/test/java/io/sinistral/proteus/test/server/TestControllerEndpoints.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * - */ -package io.sinistral.proteus.test.server; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.nio.file.Files; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; -import java.util.stream.LongStream; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.restassured.http.ContentType; - -/* - * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; - */ -/** - * @author jbauer - */ -@RunWith(DefaultServer.class) -public class TestControllerEndpoints -{ - - private File file = null; - - private Set idSet = new HashSet<>(); - - - @Before - public void setUp() - { - try - { - byte[] bytes = new byte[8388608]; - Random random = new Random(); - random.nextBytes(bytes); - - file = Files.createTempFile("test-asset", ".mp4").toFile(); - - LongStream.range(1L,10L).forEach( l -> { - - idSet.add(l); - }); - - - } catch (Exception e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Test - public void testSwaggerDocs() - { - given().accept(ContentType.JSON).when().get("swagger.json").then().statusCode(200).and().body("basePath", is("/v1")); - } - - - - @After - public void tearDown() - { - try - { - if(file.exists()) - { - file.delete(); - } - - } catch (Exception e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - -} diff --git a/swagger/src/test/resources/logback-test.xml b/swagger/src/test/resources/logback-test.xml index aba346f..91e240d 100644 --- a/swagger/src/test/resources/logback-test.xml +++ b/swagger/src/test/resources/logback-test.xml @@ -16,11 +16,8 @@ - - - From 94b4dd4116d2bfca943cd585363ba3cef2658e05 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Tue, 23 Apr 2019 09:11:40 -0700 Subject: [PATCH 5/7] Always use base ServerModelResolver and add additional classes via converterClasses config property. --- core/src/main/resources/reference.conf | 4 ++-- .../proteus/openapi/services/OpenAPIService.java | 11 ++++++++++- openapi/src/test/resources/application.conf | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index a11b71a..610134f 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -95,8 +95,8 @@ openapi { } ] - converters = [ - "io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver" + converterClasses = [ + ] } diff --git a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java index c3daba8..6fa5c5a 100644 --- a/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java +++ b/openapi/src/main/java/io/sinistral/proteus/openapi/services/OpenAPIService.java @@ -9,6 +9,7 @@ import com.google.inject.name.Named; import com.typesafe.config.Config; import io.sinistral.proteus.openapi.jaxrs2.Reader; +import io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver; import io.sinistral.proteus.openapi.jaxrs2.ServerParameterExtension; import io.sinistral.proteus.server.endpoints.EndpointInfo; import io.sinistral.proteus.services.DefaultService; @@ -254,7 +255,15 @@ protected void generateSpec() throws Exception SwaggerConfiguration config = new SwaggerConfiguration().resourceClasses(classes.stream().map(Class::getName).collect(Collectors.toSet())).openAPI(openApi); - config.setModelConverterClassess(new HashSet<>( openAPIConfig.getStringList("converters") )); + Set modelConverterClasses = new HashSet<>(); + + modelConverterClasses.add(ServerModelResolver.class.getName()); + + List additionalConverterClasses = openAPIConfig.getStringList("converterClasses"); + + modelConverterClasses.addAll(additionalConverterClasses); + + config.setModelConverterClassess(modelConverterClasses); OpenApiContext ctx = new GenericOpenApiContext().openApiConfiguration(config) .openApiReader(new Reader(config)) diff --git a/openapi/src/test/resources/application.conf b/openapi/src/test/resources/application.conf index a3b5b1b..098b42b 100644 --- a/openapi/src/test/resources/application.conf +++ b/openapi/src/test/resources/application.conf @@ -33,7 +33,7 @@ application { api.version="v1" -openapi.converters = ["io.sinistral.proteus.openapi.jaxrs2.ServerModelResolver","io.sinistral.proteus.openapi.test.converters.MoneyModelConverter"] +openapi.converterClasses = ["io.sinistral.proteus.openapi.test.converters.MoneyModelConverter"] globalHeaders { From 13e6ce03a4cce8283a93114dac88ca4b4e5aac41 Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Tue, 14 May 2019 12:00:25 -0700 Subject: [PATCH 6/7] Improved MediaType handling. --- .../sinistral/proteus/server/MediaType.java | 35 ++++++++++++++++++- .../server/exceptions/ServerException.java | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/sinistral/proteus/server/MediaType.java b/core/src/main/java/io/sinistral/proteus/server/MediaType.java index cf8a13c..928b2ac 100644 --- a/core/src/main/java/io/sinistral/proteus/server/MediaType.java +++ b/core/src/main/java/io/sinistral/proteus/server/MediaType.java @@ -9,9 +9,14 @@ */ +import java.lang.reflect.Field; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; public class MediaType @@ -1210,6 +1215,11 @@ public static synchronized MediaType getByFileName(String filename) } } + public static synchronized MediaType getByMimeType(String type) + { + return Optional.ofNullable(getTypeMap().get(type)).orElse(MediaType.DEFAULT); + } + public String withCharset(String charset) { return charset != null ? String.format("%s; charset=%s", this.contentType, charset.toUpperCase()) : this.contentType; @@ -1262,6 +1272,30 @@ public String toString() return this.contentType; } + private static final Map TYPE_MAP = new HashMap<>(); + + public static Map getTypeMap() + { + if(TYPE_MAP.size() == 0) { + Class clazz = MediaType.class; + + Field[] fields = clazz.getDeclaredFields(); + + Arrays.stream(fields).filter(f -> f.getType().equals(MediaType.class)).forEach( f -> { + + try + { + MediaType mt = (MediaType)f.get(MediaType.class); + TYPE_MAP.put(mt.contentType,mt); + } catch( Exception e ) + { + e.printStackTrace(); + } + }); + } + + return TYPE_MAP; + } @Override public int hashCode() @@ -1307,5 +1341,4 @@ public String info() return toString(); } - } diff --git a/core/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java b/core/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java index f98a5d0..ef5e8a5 100644 --- a/core/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java +++ b/core/src/main/java/io/sinistral/proteus/server/exceptions/ServerException.java @@ -9,7 +9,7 @@ * @author jbauer * */ -public class ServerException extends Exception +public class ServerException extends RuntimeException { /** * From d024cc1bbe6e10146bb7b208686ac801eb5f8cef Mon Sep 17 00:00:00 2001 From: Joshua Bauer Date: Tue, 14 May 2019 12:48:44 -0700 Subject: [PATCH 7/7] Support for compiling on jdk > 8. --- core/pom.xml | 2 +- openapi/pom.xml | 2 +- pom.xml | 38 +++++++++++++++++++++++++++++++------- swagger/pom.xml | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index bd9a513..d345c70 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -62,7 +62,7 @@ maven-surefire-plugin - 2.20.1 + org.apache.maven.plugins diff --git a/openapi/pom.xml b/openapi/pom.xml index f4ea1ef..7cfa6a0 100644 --- a/openapi/pom.xml +++ b/openapi/pom.xml @@ -59,7 +59,7 @@ maven-surefire-plugin - 2.20.1 + org.apache.maven.plugins diff --git a/pom.xml b/pom.xml index 0d93f2b..5780273 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.0.1 attach-sources @@ -70,10 +70,14 @@ + org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + + 8 + + 3.1.0 attach-javadocs @@ -81,10 +85,11 @@ jar - -Xdoclint:none + none + @@ -130,22 +135,41 @@ org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.8.1 1.8 1.8 -parameters + - + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-versions + + enforce + + + + + 1.8 + + + + + + maven-surefire-plugin - 2.20.1 + 3.0.0-M3 org.apache.maven.surefire surefire-junit47 - 2.20.1 + 3.0.0-M3 diff --git a/swagger/pom.xml b/swagger/pom.xml index ff0a0fa..4406a84 100644 --- a/swagger/pom.xml +++ b/swagger/pom.xml @@ -58,7 +58,7 @@ maven-surefire-plugin - 2.20.1 + org.apache.maven.plugins