diff --git a/conf/logback-test.xml b/conf/logback-test.xml
deleted file mode 100644
index 0be7a5f..0000000
--- a/conf/logback-test.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
- true
-
- %date{ISO8601} %highlight(%-5level) [%boldCyan(%logger)]
- [%boldYellow(%method %F) ] - %boldWhite(%message) %n %red(%ex)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- io.sinistral.proteus.services
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/conf/logback.xml b/conf/logback.xml
index e5e2656..7f7c34d 100644
--- a/conf/logback.xml
+++ b/conf/logback.xml
@@ -11,36 +11,32 @@
-
-
+
-
-
+
+
+
-
+
-
-
-
-
+
-
-
+
diff --git a/pom.xml b/pom.xml
index 98f1584..3327465 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,9 +33,11 @@
1.8
1.8
UTF-8
- 3.0.0
- 2.0.13.Final
- 2.9.6
+ 3.0.0
+ 2.0.13.Final
+ 2.9.6
+ 2.0.6-SNAPSHOT
+ 1.5.21
@@ -119,7 +121,6 @@
maven-surefire-plugin
2.20.1
-
org.apache.maven.surefire
@@ -188,7 +189,7 @@
io.rest-assured
rest-assured
- 3.0.2
+ 3.1.1
test
@@ -199,38 +200,38 @@
test
-
-
io.undertow
undertow-core
- ${version.undertow}
+ ${undertow.version}
-
-
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+
javax.ws.rs
javax.ws.rs-api
2.1.1
-
- com.fasterxml.woodstox
- woodstox-core
- 5.1.0
-
-
-
- com.squareup
- javapoet
- 1.8.0
-
com.google.inject
guice
4.1.0
+
net.openhft
compiler
@@ -243,6 +244,12 @@
+
+ com.squareup
+ javapoet
+ 1.8.0
+
+
com.google.guava
guava
@@ -254,90 +261,97 @@
config
1.3.1
+
+
+ org.yaml
+ snakeyaml
+ 1.23
+
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+
+ org.apache.httpcomponents
+ httpcore
+ 4.4.6
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.25
+
+
+ org.slf4j
+ slf4j-ext
+ 1.7.25
+
+
+ org.fusesource.jansi
+ jansi
+ 1.15
+
+
+
+ com.fasterxml.woodstox
+ woodstox-core
+ 5.1.0
+
+
com.fasterxml.jackson.core
jackson-annotations
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.core
jackson-core
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.dataformat
jackson-dataformat-yaml
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.module
jackson-module-afterburner
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.datatype
jackson-datatype-jdk8
- ${version.jackson}
+ ${jackson.version}
com.fasterxml.jackson.core
jackson-databind
- ${version.jackson}
+ ${jackson.version}
-
-
- ch.qos.logback
- logback-classic
- 1.2.3
-
-
- org.slf4j
- slf4j-api
-
-
-
-
-
-
-
+
io.swagger
swagger-annotations
- 1.5.12
+ ${swagger.version}
io.swagger
swagger-core
- 1.5.12
-
-
- org.slf4j
- slf4j-api
-
-
-
-
- io.swagger
- swagger-parser
- 1.0.28
+ ${swagger.version}
org.slf4j
@@ -348,34 +362,47 @@
io.swagger
swagger-jaxrs
- 1.5.12
-
-
- org.apache.httpcomponents
- httpcore
- 4.4.6
+ ${swagger.version}
+
- org.slf4j
- slf4j-api
- 1.7.25
+ io.swagger.core.v3
+ swagger-annotations
+ ${openapi.version}
+
- org.slf4j
- slf4j-ext
- 1.7.25
+ io.swagger.core.v3
+ swagger-models
+ ${openapi.version}
+
- org.fusesource.jansi
- jansi
- 1.15
+ io.swagger.core.v3
+ swagger-jaxrs2
+ ${openapi.version}
+
- org.reactivestreams
- reactive-streams
- 1.0.0.final
+ io.swagger.core.v3
+ swagger-integration
+ ${openapi.version}
+
+
+
+
+ sonatype-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ true
+
+
+ false
+
+
+
ossrh
@@ -385,6 +412,7 @@
ossrh
https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
https://oss.sonatype.org/content/groups/public/io/sinistral/proteus-core
diff --git a/src/main/java/io/sinistral/proteus/ProteusApplication.java b/src/main/java/io/sinistral/proteus/ProteusApplication.java
index 5ff2c6d..43b4413 100644
--- a/src/main/java/io/sinistral/proteus/ProteusApplication.java
+++ b/src/main/java/io/sinistral/proteus/ProteusApplication.java
@@ -165,6 +165,7 @@ public void healthy()
for(ListenerInfo info : undertow.getListenerInfo())
{
+ log.debug("listener info: " + info);
SocketAddress address = info.getAddress();
if(address != null)
diff --git a/src/main/java/io/sinistral/proteus/server/tools/oas/Reader.java b/src/main/java/io/sinistral/proteus/server/tools/oas/Reader.java
new file mode 100644
index 0000000..35a4c65
--- /dev/null
+++ b/src/main/java/io/sinistral/proteus/server/tools/oas/Reader.java
@@ -0,0 +1,1829 @@
+/**
+ *
+ */
+package io.sinistral.proteus.server.tools.oas;
+
+/**
+ * @author jbauer
+ */
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Application;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JavaType;
+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.server.ServerResponse;
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverters;
+import io.swagger.v3.core.converter.ResolvedSchema;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.ParameterProcessor;
+import io.swagger.v3.core.util.PathUtils;
+import io.swagger.v3.core.util.ReflectionUtils;
+import io.swagger.v3.jaxrs2.OperationParser;
+import io.swagger.v3.jaxrs2.ReaderListener;
+import io.swagger.v3.jaxrs2.ResolvedParameter;
+import io.swagger.v3.jaxrs2.SecurityParser;
+import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
+import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions;
+import io.swagger.v3.jaxrs2.util.ReaderUtils;
+import io.swagger.v3.oas.annotations.ExternalDocumentation;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.servers.Server;
+import io.swagger.v3.oas.integration.ContextUtils;
+import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.callbacks.Callback;
+import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.ObjectSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.tags.Tag;
+import io.undertow.server.HttpServerExchange;
+
+public class Reader extends io.swagger.v3.jaxrs2.Reader
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);
+
+ public static final String DEFAULT_MEDIA_TYPE_VALUE = "*/*";
+ public static final String DEFAULT_DESCRIPTION = "default response";
+
+ protected OpenAPIConfiguration config;
+
+ private Application application;
+ private OpenAPI openAPI;
+ private Components components;
+ private Paths paths;
+ private Set openApiTags;
+
+ private static final String GET_METHOD = "get";
+ private static final String POST_METHOD = "post";
+ private static final String PUT_METHOD = "put";
+ private static final String DELETE_METHOD = "delete";
+ private static final String PATCH_METHOD = "patch";
+ private static final String TRACE_METHOD = "trace";
+ private static final String HEAD_METHOD = "head";
+ private static final String OPTIONS_METHOD = "options";
+
+ public Reader()
+ {
+ this.openAPI = new OpenAPI();
+ paths = new Paths();
+ openApiTags = new LinkedHashSet<>();
+ components = new Components();
+
+ }
+
+ public Reader(OpenAPI openAPI)
+ {
+ this();
+ setConfiguration(new SwaggerConfiguration().openAPI(openAPI));
+ }
+
+ public Reader(OpenAPIConfiguration openApiConfiguration)
+ {
+ this();
+ setConfiguration(openApiConfiguration);
+ }
+
+ public OpenAPI getOpenAPI()
+ {
+ return openAPI;
+ }
+
+ /**
+ * Scans a single class for Swagger annotations - does not invoke
+ * ReaderListeners
+ */
+ public OpenAPI read(Class> cls)
+ {
+ return read(cls, resolveApplicationPath(), null, false, null, null, new LinkedHashSet(), new ArrayList(), new HashSet>());
+ }
+
+ /**
+ * Scans a set of classes for both ReaderListeners and OpenAPI annotations.
+ * All found listeners will
+ * be instantiated before any of the classes are scanned for OpenAPI
+ * annotations - so they can be invoked
+ * accordingly.
+ * @param classes
+ * a set of classes to scan
+ * @return the generated OpenAPI definition
+ */
+ public OpenAPI read(Set> classes)
+ {
+ Set> sortedClasses = new TreeSet<>(new Comparator>()
+ {
+ @Override
+ public int compare(Class> class1, Class> class2)
+ {
+ if (class1.equals(class2))
+ {
+ return 0;
+ }
+ else if (class1.isAssignableFrom(class2))
+ {
+ return -1;
+ }
+ else if (class2.isAssignableFrom(class1))
+ {
+ return 1;
+ }
+ return class1.getName().compareTo(class2.getName());
+ }
+ });
+ sortedClasses.addAll(classes);
+
+ Map, ReaderListener> listeners = new HashMap<>();
+
+ for (Class> cls : sortedClasses)
+ {
+ if (ReaderListener.class.isAssignableFrom(cls) && !listeners.containsKey(cls))
+ {
+ try
+ {
+ listeners.put(cls, (ReaderListener) cls.newInstance());
+ } catch (Exception e)
+ {
+ LOGGER.error("Failed to create ReaderListener", e);
+ }
+ }
+ }
+
+ for (ReaderListener listener : listeners.values())
+ {
+ try
+ {
+ listener.beforeScan(this, openAPI);
+ } catch (Exception e)
+ {
+ LOGGER.error("Unexpected error invoking beforeScan listener [" + listener.getClass().getName() + "]", e);
+ }
+ }
+
+ for (Class> cls : sortedClasses)
+ {
+ read(cls, resolveApplicationPath(), null, false, null, null, new LinkedHashSet(), new ArrayList(), new HashSet>());
+ }
+
+ for (ReaderListener listener : listeners.values())
+ {
+ try
+ {
+ listener.afterScan(this, openAPI);
+ } catch (Exception e)
+ {
+ LOGGER.error("Unexpected error invoking afterScan listener [" + listener.getClass().getName() + "]", e);
+ }
+ }
+ return openAPI;
+ }
+
+ @Override
+ public void setConfiguration(OpenAPIConfiguration openApiConfiguration)
+ {
+ if (openApiConfiguration != null)
+ {
+ this.config = ContextUtils.deepCopy(openApiConfiguration);
+ if (openApiConfiguration.getOpenAPI() != null)
+ {
+ this.openAPI = this.config.getOpenAPI();
+ if (this.openAPI.getComponents() != null)
+ {
+ this.components = this.openAPI.getComponents();
+ }
+ }
+ }
+ }
+
+ public OpenAPI read(Set> classes, Map resources)
+ {
+ return read(classes);
+ }
+
+ protected String resolveApplicationPath()
+ {
+ if (application != null)
+ {
+ Class> applicationToScan = this.application.getClass();
+ ApplicationPath applicationPath;
+ // search up in the hierarchy until we find one with the annotation,
+ // this is needed because for example Weld proxies will not have the
+ // annotation and the right class will be the superClass
+ while ((applicationPath = applicationToScan.getAnnotation(ApplicationPath.class)) == null && !applicationToScan.getSuperclass().equals(Application.class))
+ {
+ applicationToScan = applicationToScan.getSuperclass();
+ }
+
+ if (applicationPath != null)
+ {
+ if (StringUtils.isNotBlank(applicationPath.value()))
+ {
+ return applicationPath.value();
+ }
+ }
+ // look for inner application, e.g. ResourceConfig
+ try
+ {
+ Application innerApp = application;
+ Method m = application.getClass().getMethod("getApplication", null);
+ while (m != null)
+ {
+ Application retrievedApp = (Application) m.invoke(innerApp, null);
+ if (retrievedApp == null)
+ {
+ break;
+ }
+ if (retrievedApp.getClass().equals(innerApp.getClass()))
+ {
+ break;
+ }
+ innerApp = retrievedApp;
+ applicationPath = innerApp.getClass().getAnnotation(ApplicationPath.class);
+ if (applicationPath != null)
+ {
+ if (StringUtils.isNotBlank(applicationPath.value()))
+ {
+ return applicationPath.value();
+ }
+ }
+ m = innerApp.getClass().getMethod("getApplication", null);
+ }
+ } catch (NoSuchMethodException e)
+ {
+ // no inner application found
+ } catch (Exception e)
+ {
+ // no inner application found
+ }
+ }
+ return "";
+ }
+
+ public OpenAPI read(Class> cls,
+ String parentPath,
+ String parentMethod,
+ boolean isSubresource,
+ RequestBody parentRequestBody,
+ ApiResponses parentResponses,
+ Set parentTags,
+ List parentParameters,
+ Set> scannedResources)
+ {
+
+ Hidden hidden = cls.getAnnotation(Hidden.class);
+ // class path
+ final javax.ws.rs.Path apiPath = ReflectionUtils.getAnnotation(cls, javax.ws.rs.Path.class);
+
+ if (hidden != null)
+ { // || (apiPath == null && !isSubresource)) {
+ return openAPI;
+ }
+
+ io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses = ReflectionUtils
+ .getRepeatableAnnotationsArray(cls, io.swagger.v3.oas.annotations.responses.ApiResponse.class);
+
+ List apiSecurityScheme = ReflectionUtils
+ .getRepeatableAnnotations(cls, io.swagger.v3.oas.annotations.security.SecurityScheme.class);
+ List apiSecurityRequirements = ReflectionUtils
+ .getRepeatableAnnotations(cls, io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
+
+ ExternalDocumentation apiExternalDocs = ReflectionUtils.getAnnotation(cls, ExternalDocumentation.class);
+ io.swagger.v3.oas.annotations.tags.Tag[] apiTags = ReflectionUtils.getRepeatableAnnotationsArray(cls, io.swagger.v3.oas.annotations.tags.Tag.class);
+ io.swagger.v3.oas.annotations.servers.Server[] apiServers = ReflectionUtils.getRepeatableAnnotationsArray(cls, io.swagger.v3.oas.annotations.servers.Server.class);
+
+ javax.ws.rs.Consumes classConsumes = ReflectionUtils.getAnnotation(cls, javax.ws.rs.Consumes.class);
+ javax.ws.rs.Produces classProduces = ReflectionUtils.getAnnotation(cls, javax.ws.rs.Produces.class);
+
+ // OpenApiDefinition
+ OpenAPIDefinition openAPIDefinition = ReflectionUtils.getAnnotation(cls, OpenAPIDefinition.class);
+
+ if (openAPIDefinition != null)
+ {
+
+ // info
+ AnnotationsUtils.getInfo(openAPIDefinition.info()).ifPresent(info -> openAPI.setInfo(info));
+
+ // OpenApiDefinition security requirements
+ SecurityParser
+ .getSecurityRequirements(openAPIDefinition.security())
+ .ifPresent(s -> openAPI.setSecurity(s));
+ //
+ // OpenApiDefinition external docs
+ AnnotationsUtils
+ .getExternalDocumentation(openAPIDefinition.externalDocs())
+ .ifPresent(docs -> openAPI.setExternalDocs(docs));
+
+ // OpenApiDefinition tags
+ AnnotationsUtils
+ .getTags(openAPIDefinition.tags(), false)
+ .ifPresent(tags -> openApiTags.addAll(tags));
+
+ // OpenApiDefinition servers
+ AnnotationsUtils.getServers(openAPIDefinition.servers()).ifPresent(servers -> openAPI.setServers(servers));
+
+ // OpenApiDefinition extensions
+ if (openAPIDefinition.extensions().length > 0)
+ {
+ openAPI.setExtensions(AnnotationsUtils
+ .getExtensions(openAPIDefinition.extensions()));
+ }
+
+ }
+
+ // class security schemes
+ if (apiSecurityScheme != null)
+ {
+ for (io.swagger.v3.oas.annotations.security.SecurityScheme securitySchemeAnnotation : apiSecurityScheme)
+ {
+ Optional securityScheme = SecurityParser.getSecurityScheme(securitySchemeAnnotation);
+ if (securityScheme.isPresent())
+ {
+ Map securitySchemeMap = new HashMap<>();
+ if (StringUtils.isNotBlank(securityScheme.get().key))
+ {
+ securitySchemeMap.put(securityScheme.get().key, securityScheme.get().securityScheme);
+ if (components.getSecuritySchemes() != null && components.getSecuritySchemes().size() != 0)
+ {
+ components.getSecuritySchemes().putAll(securitySchemeMap);
+ }
+ else
+ {
+ components.setSecuritySchemes(securitySchemeMap);
+ }
+ }
+ }
+ }
+ }
+
+ // class security requirements
+ List classSecurityRequirements = new ArrayList<>();
+ if (apiSecurityRequirements != null)
+ {
+ Optional> requirementsObject = SecurityParser.getSecurityRequirements(
+ apiSecurityRequirements.toArray(
+ new io.swagger.v3.oas.annotations.security.SecurityRequirement[apiSecurityRequirements
+ .size()]));
+ if (requirementsObject.isPresent())
+ {
+ classSecurityRequirements = requirementsObject.get();
+ }
+ }
+
+ // class tags, consider only name to add to class operations
+ final Set classTags = new LinkedHashSet<>();
+ if (apiTags != null)
+ {
+ AnnotationsUtils
+ .getTags(apiTags, false).ifPresent(tags -> tags
+ .stream()
+ .map(t -> t.getName())
+ .forEach(t -> classTags.add(t)));
+ }
+
+ // parent tags
+ if (isSubresource)
+ {
+ if (parentTags != null)
+ {
+ classTags.addAll(parentTags);
+ }
+ }
+
+ // servers
+ final List classServers = new ArrayList<>();
+ if (apiServers != null)
+ {
+ AnnotationsUtils.getServers(apiServers).ifPresent(servers -> classServers.addAll(servers));
+ }
+
+ // class external docs
+ Optional classExternalDocumentation = AnnotationsUtils.getExternalDocumentation(apiExternalDocs);
+
+ JavaType classType = TypeFactory.defaultInstance().constructType(cls);
+ BeanDescription bd = Json.mapper().getSerializationConfig().introspect(classType);
+
+ final List globalParameters = new ArrayList<>();
+
+ // look for constructor-level annotated properties
+ globalParameters.addAll(ReaderUtils.collectConstructorParameters(cls, components, classConsumes, null));
+
+ // look for field-level annotated properties
+ globalParameters.addAll(ReaderUtils.collectFieldParameters(cls, components, classConsumes, null));
+
+ // iterate class methods
+ Method methods[] = cls.getMethods();
+ for (Method method : methods)
+ {
+ if (isOperationHidden(method))
+ {
+ continue;
+ }
+ AnnotatedMethod annotatedMethod = bd.findMethod(method.getName(), method.getParameterTypes());
+ javax.ws.rs.Produces methodProduces = ReflectionUtils.getAnnotation(method, javax.ws.rs.Produces.class);
+ javax.ws.rs.Consumes methodConsumes = ReflectionUtils.getAnnotation(method, javax.ws.rs.Consumes.class);
+
+ if (ReflectionUtils.isOverriddenMethod(method, cls))
+ {
+ continue;
+ }
+
+ javax.ws.rs.Path methodPath = ReflectionUtils.getAnnotation(method, javax.ws.rs.Path.class);
+
+ String operationPath = ReaderUtils.getPath(apiPath, methodPath, parentPath, isSubresource);
+
+ // skip if path is the same as parent, e.g. for @ApplicationPath
+ // annotated application
+ // extending resource config.
+ if (ignoreOperationPath(operationPath, parentPath) && !isSubresource)
+ {
+ continue;
+ }
+
+ Map regexMap = new LinkedHashMap<>();
+ operationPath = PathUtils.parsePath(operationPath, regexMap);
+ if (operationPath != null)
+ {
+ if (config != null && ReaderUtils.isIgnored(operationPath, config))
+ {
+ continue;
+ }
+
+ final Class> subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method);
+
+ String httpMethod = ReaderUtils.extractOperationMethod(method, OpenAPIExtensions.chain());
+ httpMethod = (httpMethod == null && isSubresource) ? parentMethod : httpMethod;
+
+ if (StringUtils.isBlank(httpMethod) && subResource == null)
+ {
+ continue;
+ }
+ else if (StringUtils.isBlank(httpMethod) && subResource != null)
+ {
+ Type returnType = method.getGenericReturnType();
+ if (shouldIgnoreClass(returnType.getTypeName()) && !returnType.equals(subResource))
+ {
+ continue;
+ }
+ }
+
+ io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
+ JsonView jsonViewAnnotation = ReflectionUtils.getAnnotation(method, JsonView.class);
+ if (apiOperation != null && apiOperation.ignoreJsonView())
+ {
+ jsonViewAnnotation = null;
+ }
+
+ Operation operation = parseMethod(
+ method,
+ globalParameters,
+ methodProduces,
+ classProduces,
+ methodConsumes,
+ classConsumes,
+ classSecurityRequirements,
+ classExternalDocumentation,
+ classTags,
+ classServers,
+ isSubresource,
+ parentRequestBody,
+ parentResponses,
+ jsonViewAnnotation,
+ classResponses);
+ if (operation != null)
+ {
+
+ List operationParameters = new ArrayList<>();
+ List formParameters = new ArrayList<>();
+ Annotation[][] paramAnnotations = getParameterAnnotations(method);
+ if (annotatedMethod == null)
+ { // annotatedMethod not null only when method with 0-2
+ // parameters
+ Type[] genericParameterTypes = method.getGenericParameterTypes();
+ for (int i = 0; i < genericParameterTypes.length; i++)
+ {
+ final Type type = TypeFactory.defaultInstance().constructType(genericParameterTypes[i], cls);
+ io.swagger.v3.oas.annotations.Parameter paramAnnotation = AnnotationsUtils
+ .getAnnotation(io.swagger.v3.oas.annotations.Parameter.class, paramAnnotations[i]);
+ Type paramType = ParameterProcessor.getParameterType(paramAnnotation, true);
+ if (paramType == null)
+ {
+ paramType = type;
+ }
+ else
+ {
+ if (!(paramType instanceof Class))
+ {
+ paramType = type;
+ }
+ }
+ ResolvedParameter resolvedParameter = getParameters(
+ paramType, Arrays.asList(paramAnnotations[i]), operation, classConsumes, methodConsumes,
+ jsonViewAnnotation);
+ for (Parameter p : resolvedParameter.parameters)
+ {
+ operationParameters.add(p);
+ }
+ if (resolvedParameter.requestBody != null)
+ {
+ processRequestBody(
+ resolvedParameter.requestBody,
+ operation,
+ methodConsumes,
+ classConsumes,
+ operationParameters,
+ paramAnnotations[i],
+ type,
+ jsonViewAnnotation);
+ }
+ else if (resolvedParameter.formParameter != null)
+ {
+ // collect params to use together as request
+ // Body
+ formParameters.add(resolvedParameter.formParameter);
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < annotatedMethod.getParameterCount(); i++)
+ {
+ AnnotatedParameter param = annotatedMethod.getParameter(i);
+ final Type type = TypeFactory.defaultInstance().constructType(param.getParameterType(), cls);
+ io.swagger.v3.oas.annotations.Parameter paramAnnotation = AnnotationsUtils
+ .getAnnotation(io.swagger.v3.oas.annotations.Parameter.class, paramAnnotations[i]);
+ Type paramType = ParameterProcessor.getParameterType(paramAnnotation, true);
+ if (paramType == null)
+ {
+ paramType = type;
+ }
+ else
+ {
+ if (!(paramType instanceof Class))
+ {
+ paramType = type;
+ }
+ }
+ ResolvedParameter resolvedParameter = getParameters(
+ paramType, Arrays.asList(paramAnnotations[i]), operation, classConsumes, methodConsumes,
+ jsonViewAnnotation);
+ for (Parameter p : resolvedParameter.parameters)
+ {
+ operationParameters.add(p);
+ }
+ if (resolvedParameter.requestBody != null)
+ {
+ processRequestBody(
+ resolvedParameter.requestBody,
+ operation,
+ methodConsumes,
+ classConsumes,
+ operationParameters,
+ paramAnnotations[i],
+ type,
+ jsonViewAnnotation);
+ }
+ else if (resolvedParameter.formParameter != null)
+ {
+ // collect params to use together as request
+ // Body
+ formParameters.add(resolvedParameter.formParameter);
+ }
+ }
+ }
+ // if we have form parameters, need to merge them into
+ // single schema and use as request body..
+ if (formParameters.size() > 0)
+ {
+ Schema mergedSchema = new ObjectSchema();
+ for (Parameter formParam : formParameters)
+ {
+ mergedSchema.addProperties(formParam.getName(), formParam.getSchema());
+ }
+ Parameter merged = new Parameter().schema(mergedSchema);
+ processRequestBody(
+ merged,
+ operation,
+ methodConsumes,
+ classConsumes,
+ operationParameters,
+ new Annotation[0],
+ null,
+ jsonViewAnnotation);
+
+ }
+ if (operationParameters.size() > 0)
+ {
+ for (Parameter operationParameter : operationParameters)
+ {
+ operation.addParametersItem(operationParameter);
+ }
+ }
+
+ // if subresource, merge parent parameters
+ if (parentParameters != null)
+ {
+ for (Parameter parentParameter : parentParameters)
+ {
+ operation.addParametersItem(parentParameter);
+ }
+ }
+
+ if (subResource != null && !scannedResources.contains(subResource))
+ {
+ scannedResources.add(subResource);
+ read(
+ subResource, operationPath, httpMethod, true, operation.getRequestBody(), operation.getResponses(), classTags, operation.getParameters(),
+ scannedResources);
+ // remove the sub resource so that it can visit it later
+ // in another path
+ // but we have a room for optimization in the future to
+ // reuse the scanned result
+ // by caching the scanned resources in the reader
+ // instance to avoid actual scanning
+ // the the resources again
+ scannedResources.remove(subResource);
+ // don't proceed with root resource operation, as it's
+ // handled by subresource
+ continue;
+ }
+
+ final Iterator chain = OpenAPIExtensions.chain();
+ if (chain.hasNext())
+ {
+ final OpenAPIExtension extension = chain.next();
+ extension.decorateOperation(operation, method, chain);
+ }
+
+ PathItem pathItemObject;
+ if (openAPI.getPaths() != null && openAPI.getPaths().get(operationPath) != null)
+ {
+ pathItemObject = openAPI.getPaths().get(operationPath);
+ }
+ else
+ {
+ pathItemObject = new PathItem();
+ }
+
+ if (StringUtils.isBlank(httpMethod))
+ {
+ continue;
+ }
+ setPathItemOperation(pathItemObject, httpMethod, operation);
+
+ paths.addPathItem(operationPath, pathItemObject);
+ if (openAPI.getPaths() != null)
+ {
+ this.paths.putAll(openAPI.getPaths());
+ }
+
+ openAPI.setPaths(this.paths);
+
+ }
+ }
+ }
+
+ // if no components object is defined in openApi instance passed by
+ // client, set openAPI.components to resolved components (if not empty)
+ if (!isEmptyComponents(components) && openAPI.getComponents() == null)
+ {
+ openAPI.setComponents(components);
+ }
+
+ // add tags from class to definition tags
+ AnnotationsUtils
+ .getTags(apiTags, true).ifPresent(tags -> openApiTags.addAll(tags));
+
+ if (!openApiTags.isEmpty())
+ {
+ Set tagsSet = new LinkedHashSet<>();
+ if (openAPI.getTags() != null)
+ {
+ for (Tag tag : openAPI.getTags())
+ {
+ if (tagsSet.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+ {
+ tagsSet.add(tag);
+ }
+ }
+ }
+ for (Tag tag : openApiTags)
+ {
+ if (tagsSet.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+ {
+ tagsSet.add(tag);
+ }
+ }
+ openAPI.setTags(new ArrayList<>(tagsSet));
+ }
+
+ return openAPI;
+ }
+
+ public static Annotation[][] getParameterAnnotations(Method method)
+ {
+
+ Annotation[][] methodAnnotations = method.getParameterAnnotations();
+
+ java.lang.reflect.Parameter[] params = method.getParameters();
+
+ for (int i = 0; i < params.length; i++)
+ {
+ Annotation[] paramAnnotations = methodAnnotations[i];
+
+ if (!params[i].getType().isAssignableFrom(io.sinistral.proteus.server.ServerRequest.class) && !params[i].getType().getName().startsWith("io.undertow"))
+ {
+ String annotationStrings = Arrays.stream(paramAnnotations).map(a -> a.annotationType().getName()).collect(Collectors.joining(" "));
+
+ LOGGER.debug("\nparameter: " + params[i] + " | name: " + params[i].getName() + " type: " + params[i].getType() + " -> " + annotationStrings);
+
+ if (paramAnnotations.length == 0)
+ {
+ final String parameterName = params[i].getName();
+
+ LOGGER.debug("creating query parameter for " + parameterName);
+
+ QueryParam queryParam = new QueryParam()
+ {
+
+ @Override
+ public String value()
+ {
+ return parameterName;
+ }
+
+ @Override
+ public Class extends Annotation> annotationType()
+ {
+ return QueryParam.class;
+ }
+ };
+
+ methodAnnotations[i] = new Annotation[] { queryParam };
+ }
+ }
+
+ }
+
+ Method overriddenmethod = ReflectionUtils.getOverriddenMethod(method);
+
+ if (overriddenmethod != null)
+ {
+ Annotation[][] overriddenAnnotations = overriddenmethod
+ .getParameterAnnotations();
+
+ for (int i = 0; i < methodAnnotations.length; i++)
+ {
+ List types = new ArrayList<>();
+ for (int j = 0; j < methodAnnotations[i].length; j++)
+ {
+ types.add(methodAnnotations[i][j].annotationType());
+ }
+ for (int j = 0; j < overriddenAnnotations[i].length; j++)
+ {
+ if (!types.contains(overriddenAnnotations[i][j]
+ .annotationType()))
+ {
+ methodAnnotations[i] = ArrayUtils.add(
+ methodAnnotations[i],
+ overriddenAnnotations[i][j]);
+ }
+ }
+
+ }
+ }
+ return methodAnnotations;
+ }
+
+ protected Content processContent(Content content, Schema schema, Consumes methodConsumes, Consumes classConsumes)
+ {
+ if (content == null)
+ {
+ content = new Content();
+ }
+ if (methodConsumes != null)
+ {
+ for (String value : methodConsumes.value())
+ {
+ setMediaTypeToContent(schema, content, value);
+ }
+ }
+ else if (classConsumes != null)
+ {
+ for (String value : classConsumes.value())
+ {
+ setMediaTypeToContent(schema, content, value);
+ }
+ }
+ else
+ {
+ setMediaTypeToContent(schema, content, DEFAULT_MEDIA_TYPE_VALUE);
+ }
+ return content;
+ }
+
+ protected void processRequestBody( Parameter requestBodyParameter, Operation operation,
+ Consumes methodConsumes, Consumes classConsumes,
+ List operationParameters,
+ Annotation[] paramAnnotations, Type type,
+ JsonView jsonViewAnnotation)
+ {
+
+ io.swagger.v3.oas.annotations.parameters.RequestBody requestBodyAnnotation = getRequestBody(Arrays.asList(paramAnnotations));
+ if (requestBodyAnnotation != null)
+ {
+ Optional optionalRequestBody = OperationParser.getRequestBody(requestBodyAnnotation, classConsumes, methodConsumes, components, jsonViewAnnotation);
+ if (optionalRequestBody.isPresent())
+ {
+ RequestBody requestBody = optionalRequestBody.get();
+ if (StringUtils.isBlank(requestBody.get$ref()) &&
+ (requestBody.getContent() == null || requestBody.getContent().isEmpty()))
+ {
+ if (requestBodyParameter.getSchema() != null)
+ {
+ Content content = processContent(requestBody.getContent(), requestBodyParameter.getSchema(), methodConsumes, classConsumes);
+ requestBody.setContent(content);
+ }
+ }
+ else if (StringUtils.isBlank(requestBody.get$ref()) &&
+ requestBody.getContent() != null &&
+ !requestBody.getContent().isEmpty())
+ {
+ if (requestBodyParameter.getSchema() != null)
+ {
+ for (MediaType mediaType : requestBody.getContent().values())
+ {
+ if (mediaType.getSchema() == null)
+ {
+ if (requestBodyParameter.getSchema() == null)
+ {
+ mediaType.setSchema(new Schema());
+ }
+ else
+ {
+ mediaType.setSchema(requestBodyParameter.getSchema());
+ }
+ }
+ if (StringUtils.isBlank(mediaType.getSchema().getType()))
+ {
+ mediaType.getSchema().setType(requestBodyParameter.getSchema().getType());
+ }
+ }
+ }
+ }
+ operation.setRequestBody(requestBody);
+ }
+ }
+ else
+ {
+ if (operation.getRequestBody() == null)
+ {
+ boolean isRequestBodyEmpty = true;
+ RequestBody requestBody = new RequestBody();
+ if (StringUtils.isNotBlank(requestBodyParameter.get$ref()))
+ {
+ requestBody.set$ref(requestBodyParameter.get$ref());
+ isRequestBodyEmpty = false;
+ }
+ if (StringUtils.isNotBlank(requestBodyParameter.getDescription()))
+ {
+ requestBody.setDescription(requestBodyParameter.getDescription());
+ isRequestBodyEmpty = false;
+ }
+ if (Boolean.TRUE.equals(requestBodyParameter.getRequired()))
+ {
+ requestBody.setRequired(requestBodyParameter.getRequired());
+ isRequestBodyEmpty = false;
+ }
+
+ if (requestBodyParameter.getSchema() != null)
+ {
+ Content content = processContent(null, requestBodyParameter.getSchema(), methodConsumes, classConsumes);
+ requestBody.setContent(content);
+ isRequestBodyEmpty = false;
+ }
+ if (!isRequestBodyEmpty)
+ {
+ // requestBody.setExtensions(extensions);
+ operation.setRequestBody(requestBody);
+ }
+ }
+ }
+ }
+
+ private io.swagger.v3.oas.annotations.parameters.RequestBody getRequestBody(List annotations)
+ {
+ if (annotations == null)
+ {
+ return null;
+ }
+ for (Annotation a : annotations)
+ {
+ if (a instanceof io.swagger.v3.oas.annotations.parameters.RequestBody)
+ {
+ return (io.swagger.v3.oas.annotations.parameters.RequestBody) a;
+ }
+ }
+ return null;
+ }
+
+ private void setMediaTypeToContent(Schema schema, Content content, String value)
+ {
+ MediaType mediaTypeObject = new MediaType();
+ mediaTypeObject.setSchema(schema);
+ content.addMediaType(value, mediaTypeObject);
+ }
+
+ public Operation parseMethod(
+ Method method,
+ List globalParameters,
+ JsonView jsonViewAnnotation)
+ {
+ JavaType classType = TypeFactory.defaultInstance().constructType(method.getDeclaringClass());
+ return parseMethod(
+ classType.getClass(),
+ method,
+ globalParameters,
+ null,
+ null,
+ null,
+ null,
+ new ArrayList<>(),
+ Optional.empty(),
+ new HashSet<>(),
+ new ArrayList<>(),
+ false,
+ null,
+ null,
+ jsonViewAnnotation,
+ null);
+ }
+
+ public Operation parseMethod(
+ Method method,
+ List globalParameters,
+ Produces methodProduces,
+ Produces classProduces,
+ Consumes methodConsumes,
+ Consumes classConsumes,
+ List classSecurityRequirements,
+ Optional classExternalDocs,
+ Set classTags,
+ List classServers,
+ boolean isSubresource,
+ RequestBody parentRequestBody,
+ ApiResponses parentResponses,
+ JsonView jsonViewAnnotation,
+ io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses)
+ {
+ JavaType classType = TypeFactory.defaultInstance().constructType(method.getDeclaringClass());
+ return parseMethod(
+ classType.getClass(),
+ method,
+ globalParameters,
+ methodProduces,
+ classProduces,
+ methodConsumes,
+ classConsumes,
+ classSecurityRequirements,
+ classExternalDocs,
+ classTags,
+ classServers,
+ isSubresource,
+ parentRequestBody,
+ parentResponses,
+ jsonViewAnnotation,
+ classResponses);
+ }
+
+ private Operation parseMethod(
+ Class> cls,
+ Method method,
+ List globalParameters,
+ Produces methodProduces,
+ Produces classProduces,
+ Consumes methodConsumes,
+ Consumes classConsumes,
+ List classSecurityRequirements,
+ Optional classExternalDocs,
+ Set classTags,
+ List classServers,
+ boolean isSubresource,
+ RequestBody parentRequestBody,
+ ApiResponses parentResponses,
+ JsonView jsonViewAnnotation,
+ io.swagger.v3.oas.annotations.responses.ApiResponse[] classResponses)
+ {
+
+ if (Arrays.stream(method.getParameters()).filter(p -> p.getType().isAssignableFrom(HttpServerExchange.class)).count() > 0L)
+ {
+ return null;
+ }
+
+ Operation operation = new Operation();
+
+ io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
+
+ List apiSecurity = ReflectionUtils
+ .getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
+
+ List apiCallbacks = ReflectionUtils
+ .getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.callbacks.Callback.class);
+
+ List apiServers = ReflectionUtils.getRepeatableAnnotations(method, Server.class);
+
+ List apiTags = ReflectionUtils.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class);
+
+ List apiParameters = ReflectionUtils.getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.Parameter.class);
+
+ List apiResponses = ReflectionUtils
+ .getRepeatableAnnotations(method, io.swagger.v3.oas.annotations.responses.ApiResponse.class);
+ io.swagger.v3.oas.annotations.parameters.RequestBody apiRequestBody = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.parameters.RequestBody.class);
+
+ ExternalDocumentation apiExternalDocumentation = ReflectionUtils.getAnnotation(method, ExternalDocumentation.class);
+
+ // callbacks
+ Map callbacks = new LinkedHashMap<>();
+
+ if (apiCallbacks != null)
+ {
+ for (io.swagger.v3.oas.annotations.callbacks.Callback methodCallback : apiCallbacks)
+ {
+ Map currentCallbacks = getCallbacks(methodCallback, methodProduces, classProduces, methodConsumes, classConsumes, jsonViewAnnotation);
+ callbacks.putAll(currentCallbacks);
+ }
+ }
+ if (callbacks.size() > 0)
+ {
+ operation.setCallbacks(callbacks);
+ }
+
+ // security
+ classSecurityRequirements.forEach(operation::addSecurityItem);
+ if (apiSecurity != null)
+ {
+ Optional> requirementsObject = SecurityParser.getSecurityRequirements(apiSecurity.toArray(
+ new io.swagger.v3.oas.annotations.security.SecurityRequirement[apiSecurity
+ .size()]));
+ if (requirementsObject.isPresent())
+ {
+ requirementsObject.get().stream()
+ .filter(r -> operation.getSecurity() == null || !operation.getSecurity().contains(r))
+ .forEach(operation::addSecurityItem);
+ }
+ }
+
+ // servers
+ if (classServers != null)
+ {
+ classServers.forEach(operation::addServersItem);
+ }
+
+ if (apiServers != null)
+ {
+ AnnotationsUtils.getServers(apiServers.toArray(new Server[apiServers.size()])).ifPresent(servers -> servers.forEach(operation::addServersItem));
+ }
+
+ // external docs
+ AnnotationsUtils.getExternalDocumentation(apiExternalDocumentation).ifPresent(operation::setExternalDocs);
+
+ // method tags
+ if (apiTags != null)
+ {
+ apiTags.stream()
+ .filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t.name())))
+ .map(t -> t.name())
+ .forEach(operation::addTagsItem);
+ AnnotationsUtils.getTags(apiTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[apiTags.size()]), true).ifPresent(tags -> openApiTags.addAll(tags));
+ }
+
+ // parameters
+ if (globalParameters != null)
+ {
+ for (Parameter globalParameter : globalParameters)
+ {
+ operation.addParametersItem(globalParameter);
+ }
+ }
+ if (apiParameters != null)
+ {
+
+ getParametersListFromAnnotation(
+ apiParameters.toArray(new io.swagger.v3.oas.annotations.Parameter[apiParameters.size()]),
+ classConsumes,
+ methodConsumes,
+ operation,
+ jsonViewAnnotation).ifPresent(p -> p.forEach(operation::addParametersItem));
+ }
+
+ // RequestBody in Method
+ if (apiRequestBody != null && operation.getRequestBody() == null)
+ {
+ OperationParser.getRequestBody(apiRequestBody, classConsumes, methodConsumes, components, jsonViewAnnotation).ifPresent(
+ operation::setRequestBody);
+ }
+
+ // operation id
+ if (StringUtils.isBlank(operation.getOperationId()))
+ {
+ operation.setOperationId(getOperationId(method.getName()));
+ }
+
+ // classResponses
+ if (classResponses != null && classResponses.length > 0)
+ {
+ OperationParser.getApiResponses(
+ classResponses,
+ classProduces,
+ methodProduces,
+ components,
+ jsonViewAnnotation)
+ .ifPresent(responses ->
+ {
+ if (operation.getResponses() == null)
+ {
+ operation.setResponses(responses);
+ }
+ else
+ {
+ responses.forEach(operation.getResponses()::addApiResponse);
+ }
+ });
+ }
+
+ if (apiOperation != null)
+ {
+ setOperationObjectFromApiOperationAnnotation(operation, apiOperation, methodProduces, classProduces, methodConsumes, classConsumes, jsonViewAnnotation);
+ }
+
+ // apiResponses
+ if (apiResponses != null && apiResponses.size() > 0)
+ {
+ OperationParser.getApiResponses(
+ apiResponses.toArray(new io.swagger.v3.oas.annotations.responses.ApiResponse[apiResponses.size()]),
+ classProduces,
+ methodProduces,
+ components,
+ jsonViewAnnotation)
+ .ifPresent(responses ->
+ {
+ if (operation.getResponses() == null)
+ {
+ operation.setResponses(responses);
+ }
+ else
+ {
+ responses.forEach(operation.getResponses()::addApiResponse);
+ }
+ });
+ }
+
+ // class tags after tags defined as field of @Operation
+ if (classTags != null)
+ {
+ classTags.stream()
+ .filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t)))
+ .forEach(operation::addTagsItem);
+ }
+
+ // external docs of class if not defined in annotation of method or as
+ // field of Operation annotation
+ if (operation.getExternalDocs() == null)
+ {
+ classExternalDocs.ifPresent(operation::setExternalDocs);
+ }
+
+ // if subresource, merge parent requestBody
+ if (isSubresource && parentRequestBody != null)
+ {
+ if (operation.getRequestBody() == null)
+ {
+ operation.requestBody(parentRequestBody);
+ }
+ else
+ {
+ Content content = operation.getRequestBody().getContent();
+ if (content == null)
+ {
+ content = parentRequestBody.getContent();
+ operation.getRequestBody().setContent(content);
+ }
+ else if (parentRequestBody.getContent() != null)
+ {
+ for (String parentMediaType : parentRequestBody.getContent().keySet())
+ {
+ if (content.get(parentMediaType) == null)
+ {
+ content.addMediaType(parentMediaType, parentRequestBody.getContent().get(parentMediaType));
+ }
+ }
+ }
+ }
+ }
+
+ // handle return type, add as response in case.
+ Type returnType = method.getGenericReturnType();
+ final Class> subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method);
+
+ if (!shouldIgnoreClass(returnType.getTypeName()) && !returnType.equals(subResource))
+ {
+ LOGGER.debug("processing class " + returnType + " " + returnType.getTypeName());
+
+ JavaType classType = TypeFactory.defaultInstance().constructType(returnType);
+
+ if (classType != null && classType.getRawClass() != null)
+ {
+ if (classType.getRawClass().isAssignableFrom(ServerResponse.class))
+ {
+ if(classType.containedType(0) != null)
+ {
+ returnType = classType.containedType(0);
+ }
+ }
+ else if (classType.getRawClass().isAssignableFrom(CompletableFuture.class))
+ {
+ Class> futureCls = classType.containedType(0).getRawClass();
+
+ if (futureCls.isAssignableFrom(ServerResponse.class))
+ {
+ final JavaType futureType = TypeFactory.defaultInstance().constructType(classType.containedType(0));
+ returnType = futureType.containedType(0);
+ }
+ else
+ {
+ returnType = classType.containedType(0);
+ }
+ }
+ }
+
+ ResolvedSchema resolvedSchema = ModelConverters.getInstance()
+ .resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation));
+
+ if (resolvedSchema.schema != null)
+ {
+ Schema returnTypeSchema = resolvedSchema.schema;
+ Content content = new Content();
+ MediaType mediaType = new MediaType().schema(returnTypeSchema);
+ AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
+ methodProduces == null ? new String[0] : methodProduces.value(), content, mediaType);
+ if (operation.getResponses() == null)
+ {
+ operation.responses(
+ new ApiResponses()._default(
+ new ApiResponse().description(DEFAULT_DESCRIPTION)
+ .content(content)));
+ }
+ if (operation.getResponses().getDefault() != null &&
+ StringUtils.isBlank(operation.getResponses().getDefault().get$ref()))
+ {
+ if (operation.getResponses().getDefault().getContent() == null)
+ {
+ operation.getResponses().getDefault().content(content);
+ }
+ else
+ {
+ for (String key : operation.getResponses().getDefault().getContent().keySet())
+ {
+ if (operation.getResponses().getDefault().getContent().get(key).getSchema() == null)
+ {
+ operation.getResponses().getDefault().getContent().get(key).setSchema(returnTypeSchema);
+ }
+ }
+ }
+ }
+ Map schemaMap = resolvedSchema.referencedSchemas;
+ if (schemaMap != null)
+ {
+ schemaMap.forEach((key, schema) -> components.addSchemas(key, schema));
+ }
+
+ }
+ }
+ if (operation.getResponses() == null || operation.getResponses().isEmpty())
+ {
+ Content content = new Content();
+ MediaType mediaType = new MediaType();
+ AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
+ methodProduces == null ? new String[0] : methodProduces.value(), content, mediaType);
+
+ ApiResponse apiResponseObject = new ApiResponse().description(DEFAULT_DESCRIPTION).content(content);
+ operation.setResponses(new ApiResponses()._default(apiResponseObject));
+ }
+
+ return operation;
+ }
+
+ private boolean shouldIgnoreClass(String className)
+ {
+ if (StringUtils.isBlank(className))
+ {
+ return true;
+ }
+ boolean ignore = false;
+ ignore = ignore || className.startsWith("javax.ws.rs.");
+ ignore = ignore || className.equalsIgnoreCase("void");
+ ignore = ignore || className.startsWith("io.undertow");
+ ignore = ignore || className.startsWith("java.lang.Void");
+ return ignore;
+ }
+
+ private Map getCallbacks(
+ io.swagger.v3.oas.annotations.callbacks.Callback apiCallback,
+ Produces methodProduces,
+ Produces classProduces,
+ Consumes methodConsumes,
+ Consumes classConsumes,
+ JsonView jsonViewAnnotation)
+ {
+ Map callbackMap = new HashMap<>();
+ if (apiCallback == null)
+ {
+ return callbackMap;
+ }
+
+ Callback callbackObject = new Callback();
+ if (StringUtils.isNotBlank(apiCallback.ref()))
+ {
+ callbackObject.set$ref(apiCallback.ref());
+ callbackMap.put(apiCallback.name(), callbackObject);
+ return callbackMap;
+ }
+ PathItem pathItemObject = new PathItem();
+ for (io.swagger.v3.oas.annotations.Operation callbackOperation : apiCallback.operation())
+ {
+ Operation callbackNewOperation = new Operation();
+ setOperationObjectFromApiOperationAnnotation(
+ callbackNewOperation,
+ callbackOperation,
+ methodProduces,
+ classProduces,
+ methodConsumes,
+ classConsumes,
+ jsonViewAnnotation);
+ setPathItemOperation(pathItemObject, callbackOperation.method(), callbackNewOperation);
+ }
+
+ callbackObject.addPathItem(apiCallback.callbackUrlExpression(), pathItemObject);
+ callbackMap.put(apiCallback.name(), callbackObject);
+
+ return callbackMap;
+ }
+
+ private void setPathItemOperation(PathItem pathItemObject, String method, Operation operation)
+ {
+ switch (method)
+ {
+ case POST_METHOD:
+ pathItemObject.post(operation);
+ break;
+ case GET_METHOD:
+ pathItemObject.get(operation);
+ break;
+ case DELETE_METHOD:
+ pathItemObject.delete(operation);
+ break;
+ case PUT_METHOD:
+ pathItemObject.put(operation);
+ break;
+ case PATCH_METHOD:
+ pathItemObject.patch(operation);
+ break;
+ case TRACE_METHOD:
+ pathItemObject.trace(operation);
+ break;
+ case HEAD_METHOD:
+ pathItemObject.head(operation);
+ break;
+ case OPTIONS_METHOD:
+ pathItemObject.options(operation);
+ break;
+ default:
+ // Do nothing here
+ break;
+ }
+ }
+
+ private void setOperationObjectFromApiOperationAnnotation(
+ Operation operation,
+ io.swagger.v3.oas.annotations.Operation apiOperation,
+ Produces methodProduces,
+ Produces classProduces,
+ Consumes methodConsumes,
+ Consumes classConsumes,
+ JsonView jsonViewAnnotation)
+ {
+ if (StringUtils.isNotBlank(apiOperation.summary()))
+ {
+ operation.setSummary(apiOperation.summary());
+ }
+ if (StringUtils.isNotBlank(apiOperation.description()))
+ {
+ operation.setDescription(apiOperation.description());
+ }
+ if (StringUtils.isNotBlank(apiOperation.operationId()))
+ {
+ operation.setOperationId(getOperationId(apiOperation.operationId()));
+ }
+ if (apiOperation.deprecated())
+ {
+ operation.setDeprecated(apiOperation.deprecated());
+ }
+
+ ReaderUtils.getStringListFromStringArray(apiOperation.tags()).ifPresent(tags ->
+ {
+ tags.stream()
+ .filter(t -> operation.getTags() == null || (operation.getTags() != null && !operation.getTags().contains(t)))
+ .forEach(operation::addTagsItem);
+ });
+
+ if (operation.getExternalDocs() == null)
+ { // if not set in root annotation
+ AnnotationsUtils.getExternalDocumentation(apiOperation.externalDocs()).ifPresent(operation::setExternalDocs);
+ }
+
+ OperationParser.getApiResponses(apiOperation.responses(), classProduces, methodProduces, components, jsonViewAnnotation).ifPresent(responses ->
+ {
+ if (operation.getResponses() == null)
+ {
+ operation.setResponses(responses);
+ }
+ else
+ {
+ responses.forEach(operation.getResponses()::addApiResponse);
+ }
+ });
+ AnnotationsUtils.getServers(apiOperation.servers()).ifPresent(servers -> servers.forEach(operation::addServersItem));
+
+ getParametersListFromAnnotation(
+ apiOperation.parameters(),
+ classConsumes,
+ methodConsumes,
+ operation,
+ jsonViewAnnotation).ifPresent(p -> p.forEach(operation::addParametersItem));
+
+ // security
+ Optional> requirementsObject = SecurityParser.getSecurityRequirements(apiOperation.security());
+ if (requirementsObject.isPresent())
+ {
+ requirementsObject.get().stream()
+ .filter(r -> operation.getSecurity() == null || !operation.getSecurity().contains(r))
+ .forEach(operation::addSecurityItem);
+ }
+
+ // RequestBody in Operation
+ if (apiOperation != null && apiOperation.requestBody() != null && operation.getRequestBody() == null)
+ {
+ OperationParser.getRequestBody(apiOperation.requestBody(), classConsumes, methodConsumes, components, jsonViewAnnotation).ifPresent(
+ requestBodyObject -> operation
+ .setRequestBody(
+ requestBodyObject));
+ }
+
+ // Extensions in Operation
+ if (apiOperation.extensions().length > 0)
+ {
+ Map extensions = AnnotationsUtils.getExtensions(apiOperation.extensions());
+ if (extensions != null)
+ {
+ for (String ext : extensions.keySet())
+ {
+ operation.addExtension(ext, extensions.get(ext));
+ }
+ }
+ }
+ }
+
+ protected String getOperationId(String operationId)
+ {
+ boolean operationIdUsed = existOperationId(operationId);
+ String operationIdToFind = null;
+ int counter = 0;
+ while (operationIdUsed)
+ {
+ operationIdToFind = String.format("%s_%d", operationId, ++counter);
+ operationIdUsed = existOperationId(operationIdToFind);
+ }
+ if (operationIdToFind != null)
+ {
+ operationId = operationIdToFind;
+ }
+ return operationId;
+ }
+
+ private boolean existOperationId(String operationId)
+ {
+ if (openAPI == null)
+ {
+ return false;
+ }
+ if (openAPI.getPaths() == null || openAPI.getPaths().isEmpty())
+ {
+ return false;
+ }
+ for (PathItem path : openAPI.getPaths().values())
+ {
+ String pathOperationId = extractOperationIdFromPathItem(path);
+ if (operationId.equalsIgnoreCase(pathOperationId))
+ {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ protected Optional> getParametersListFromAnnotation(io.swagger.v3.oas.annotations.Parameter[] parameters, Consumes classConsumes, Consumes methodConsumes,
+ Operation operation, JsonView jsonViewAnnotation)
+ {
+ if (parameters == null)
+ {
+ return Optional.empty();
+ }
+ List parametersObject = new ArrayList<>();
+ for (io.swagger.v3.oas.annotations.Parameter parameter : parameters)
+ {
+
+ ResolvedParameter resolvedParameter = getParameters(
+ ParameterProcessor.getParameterType(parameter), Collections.singletonList(parameter), operation, classConsumes,
+ methodConsumes, jsonViewAnnotation);
+ parametersObject.addAll(resolvedParameter.parameters);
+ }
+ if (parametersObject.size() == 0)
+ {
+ return Optional.empty();
+ }
+ return Optional.of(parametersObject);
+ }
+
+ protected ResolvedParameter getParameters( Type type, List annotations, Operation operation, javax.ws.rs.Consumes classConsumes,
+ javax.ws.rs.Consumes methodConsumes, JsonView jsonViewAnnotation)
+ {
+
+ final Iterator chain = OpenAPIExtensions.chain();
+ if (!chain.hasNext())
+ {
+ return new ResolvedParameter();
+ }
+ LOGGER.debug("getParameters for {}", type);
+ Set typesToSkip = new HashSet<>();
+ final OpenAPIExtension extension = chain.next();
+ LOGGER.debug("trying extension {}", extension);
+
+ final ResolvedParameter extractParametersResult = extension
+ .extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain);
+ return extractParametersResult;
+ }
+
+ private String extractOperationIdFromPathItem(PathItem path)
+ {
+ if (path.getGet() != null)
+ {
+ return path.getGet().getOperationId();
+ }
+ else if (path.getPost() != null)
+ {
+ return path.getPost().getOperationId();
+ }
+ else if (path.getPut() != null)
+ {
+ return path.getPut().getOperationId();
+ }
+ else if (path.getDelete() != null)
+ {
+ return path.getDelete().getOperationId();
+ }
+ else if (path.getOptions() != null)
+ {
+ return path.getOptions().getOperationId();
+ }
+ else if (path.getHead() != null)
+ {
+ return path.getHead().getOperationId();
+ }
+ else if (path.getPatch() != null)
+ {
+ return path.getPatch().getOperationId();
+ }
+ return "";
+ }
+
+ private boolean isEmptyComponents(Components components)
+ {
+ if (components == null)
+ {
+ return true;
+ }
+ if (components.getSchemas() != null && components.getSchemas().size() > 0)
+ {
+ return false;
+ }
+ if (components.getSecuritySchemes() != null && components.getSecuritySchemes().size() > 0)
+ {
+ return false;
+ }
+ if (components.getCallbacks() != null && components.getCallbacks().size() > 0)
+ {
+ return false;
+ }
+ if (components.getExamples() != null && components.getExamples().size() > 0)
+ {
+ return false;
+ }
+ if (components.getExtensions() != null && components.getExtensions().size() > 0)
+ {
+ return false;
+ }
+ if (components.getHeaders() != null && components.getHeaders().size() > 0)
+ {
+ return false;
+ }
+ if (components.getLinks() != null && components.getLinks().size() > 0)
+ {
+ return false;
+ }
+ if (components.getParameters() != null && components.getParameters().size() > 0)
+ {
+ return false;
+ }
+ if (components.getRequestBodies() != null && components.getRequestBodies().size() > 0)
+ {
+ return false;
+ }
+ if (components.getResponses() != null && components.getResponses().size() > 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected boolean isOperationHidden(Method method)
+ {
+ io.swagger.v3.oas.annotations.Operation apiOperation = ReflectionUtils.getAnnotation(method, io.swagger.v3.oas.annotations.Operation.class);
+ if (apiOperation != null && apiOperation.hidden())
+ {
+ return true;
+ }
+ Hidden hidden = method.getAnnotation(Hidden.class);
+ if (hidden != null)
+ {
+ return true;
+ }
+ if (config != null && !Boolean.TRUE.equals(config.isReadAllResources()) && apiOperation == null)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public void setApplication(Application application)
+ {
+ this.application = application;
+ }
+
+ protected boolean ignoreOperationPath(String path, String parentPath)
+ {
+
+ if (StringUtils.isBlank(path) && StringUtils.isBlank(parentPath))
+ {
+ return true;
+ }
+ else if (StringUtils.isNotBlank(path) && StringUtils.isBlank(parentPath))
+ {
+ return false;
+ }
+ else if (StringUtils.isBlank(path) && StringUtils.isNotBlank(parentPath))
+ {
+ return false;
+ }
+ if (parentPath != null && !"".equals(parentPath) && !"/".equals(parentPath))
+ {
+ if (!parentPath.startsWith("/"))
+ {
+ parentPath = "/" + parentPath;
+ }
+ if (parentPath.endsWith("/"))
+ {
+ parentPath = parentPath.substring(0, parentPath.length() - 1);
+ }
+ }
+ if (path != null && !"".equals(path) && !"/".equals(path))
+ {
+ if (!path.startsWith("/"))
+ {
+ path = "/" + path;
+ }
+ if (path.endsWith("/"))
+ {
+ path = path.substring(0, path.length() - 1);
+ }
+ }
+ if (path.equals(parentPath))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ protected Class> getSubResourceWithJaxRsSubresourceLocatorSpecs(Method method)
+ {
+ final Class> rawType = method.getReturnType();
+ final Class> type;
+ if (Class.class.equals(rawType))
+ {
+ type = getClassArgument(method.getGenericReturnType());
+ if (type == null)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ type = rawType;
+ }
+
+ if (method.getAnnotation(javax.ws.rs.Path.class) != null)
+ {
+ if (ReaderUtils.extractOperationMethod(method, null) == null)
+ {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ private static Class> getClassArgument(Type cls)
+ {
+ if (cls instanceof ParameterizedType)
+ {
+ final ParameterizedType parameterized = (ParameterizedType) cls;
+ final Type[] args = parameterized.getActualTypeArguments();
+ if (args.length != 1)
+ {
+ LOGGER.error("Unexpected class definition: {}", cls);
+ return null;
+ }
+ final Type first = args[0];
+ if (first instanceof Class)
+ {
+ return (Class>) first;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ LOGGER.error("Unknown class definition: {}", cls);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/io/sinistral/proteus/server/tools/oas/ServerModelResolver.java b/src/main/java/io/sinistral/proteus/server/tools/oas/ServerModelResolver.java
new file mode 100644
index 0000000..806a5a8
--- /dev/null
+++ b/src/main/java/io/sinistral/proteus/server/tools/oas/ServerModelResolver.java
@@ -0,0 +1,166 @@
+/**
+ *
+ */
+package io.sinistral.proteus.server.tools.oas;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.introspect.Annotated;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import io.sinistral.proteus.server.ServerResponse;
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.converter.ModelConverterContext;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.oas.models.media.Schema;
+
+/**
+ * @author jbauer
+ */
+public class ServerModelResolver extends io.swagger.v3.core.jackson.ModelResolver
+{
+
+ private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServerModelResolver.class.getCanonicalName());
+
+
+ public ServerModelResolver()
+ {
+ super(io.swagger.v3.core.util.Json.mapper());
+ }
+
+ /**
+ * @param mapper
+ */
+ public ServerModelResolver(ObjectMapper mapper)
+ {
+ super(mapper);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see io.swagger.v3.core.jackson.ModelResolver#resolve(io.swagger.v3.core.
+ * converter.AnnotatedType,
+ * io.swagger.v3.core.converter.ModelConverterContext, java.util.Iterator)
+ */
+ @Override
+ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context, Iterator next)
+ {
+
+ JavaType classType = TypeFactory.defaultInstance().constructType(annotatedType.getType());
+
+ Class> rawClass = classType.getRawClass();
+
+ JavaType resolvedType = classType;
+
+ if (rawClass != null && !resolvedType.isPrimitive())
+ {
+ if (rawClass.isAssignableFrom(ServerResponse.class))
+ {
+ resolvedType = classType.containedType(0);
+ }
+ else if (rawClass.isAssignableFrom(CompletableFuture.class))
+ {
+ Class> futureCls = classType.containedType(0).getRawClass();
+
+ if (futureCls.isAssignableFrom(ServerResponse.class))
+ {
+ System.out.println("class is assignable from ServerResponse");
+ final JavaType futureType = TypeFactory.defaultInstance().constructType(classType.containedType(0));
+ resolvedType = futureType.containedType(0);
+ }
+ else
+ {
+ System.out.println("class is NOT assignable from ServerResponse");
+ resolvedType = classType.containedType(0);
+ }
+ }
+
+ if(resolvedType != null)
+ {
+ if (resolvedType.getTypeName().contains("ByteBuffer"))
+ {
+ resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName());
+ }
+
+ if (resolvedType.getTypeName().contains("java.nio.file.Path"))
+ {
+ resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.io.File.class.getName());
+ }
+
+// if (resolvedType.getTypeName().contains("java.lang.Void"))
+// {
+// resolvedType = TypeFactory.defaultInstance().constructFromCanonical(java.lang.Void.class.getName());
+// }
+
+ annotatedType.setType(resolvedType);
+
+ System.out.println("resolvedType out " + resolvedType);
+ }
+
+ }
+
+ try
+ {
+ log.info("Processing " + annotatedType + " " + classType + " " + annotatedType.getName());
+
+ return super.resolve(annotatedType, context, next);
+
+ } catch (Exception e)
+ {
+ log.error("Error processing " + annotatedType + " " + classType + " " + annotatedType.getName(), e);
+ return null;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * io.swagger.v3.core.jackson.ModelResolver#resolveRequiredProperties(com.
+ * fasterxml.jackson.databind.introspect.Annotated,
+ * java.lang.annotation.Annotation[],
+ * io.swagger.v3.oas.annotations.media.Schema)
+ */
+ @Override
+ protected List resolveRequiredProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema)
+ {
+ // TODO Auto-generated method stub
+ return super.resolveRequiredProperties(a, annotations, schema);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * io.swagger.v3.core.jackson.ModelResolver#shouldIgnoreClass(java.lang.
+ * reflect.Type)
+ */
+ @Override
+ protected boolean shouldIgnoreClass(Type type)
+ {
+ //System.out.println("should ignore " + type);
+
+ JavaType classType = TypeFactory.defaultInstance().constructType(type);
+
+ String canonicalName = classType.toCanonical();
+
+ if (canonicalName.startsWith("io.undertow") || canonicalName.startsWith("org.xnio") || canonicalName.equals("io.sinistral.proteus.server.ServerRequest") || canonicalName.contains(java.lang.Void.class.getName()))
+ {
+ return true;
+ }
+
+ // TODO Auto-generated method stub
+ return super.shouldIgnoreClass(type);
+ }
+
+}
diff --git a/src/main/java/io/sinistral/proteus/server/tools/oas/ServerParameterExtension.java b/src/main/java/io/sinistral/proteus/server/tools/oas/ServerParameterExtension.java
new file mode 100644
index 0000000..45e1ab1
--- /dev/null
+++ b/src/main/java/io/sinistral/proteus/server/tools/oas/ServerParameterExtension.java
@@ -0,0 +1,323 @@
+/**
+ *
+ */
+package io.sinistral.proteus.server.tools.oas;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+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 org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.introspect.AnnotatedField;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.introspect.AnnotationMap;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.ParameterProcessor;
+import io.swagger.v3.jaxrs2.ResolvedParameter;
+import io.swagger.v3.jaxrs2.ext.AbstractOpenAPIExtension;
+import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
+import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.parameters.Parameter;
+
+/**
+ * @author jbauer
+ *
+ */
+
+public class ServerParameterExtension extends AbstractOpenAPIExtension {
+ private static String QUERY_PARAM = "query";
+ private static String HEADER_PARAM = "header";
+ private static String COOKIE_PARAM = "cookie";
+ private static String PATH_PARAM = "path";
+ private static String FORM_PARAM = "form";
+
+ final ObjectMapper mapper = Json.mapper();
+
+ @Override
+ public ResolvedParameter extractParameters(List annotations,
+ Type type,
+ Set typesToSkip,
+ Components components,
+ javax.ws.rs.Consumes classConsumes,
+ javax.ws.rs.Consumes methodConsumes,
+ boolean includeRequestBody,
+ JsonView jsonViewAnnotation,
+ Iterator chain) {
+
+ if (shouldIgnoreType(type, typesToSkip)) {
+ return new ResolvedParameter();
+ }
+
+ JavaType javaType = constructType(type);
+
+ boolean isRequired = true;
+
+ if(_isOptionalType(javaType))
+ {
+ isRequired = false;
+ }
+
+ List parameters = new ArrayList<>();
+ Parameter parameter = null;
+ ResolvedParameter extractParametersResult = new ResolvedParameter();
+
+
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof QueryParam) {
+ QueryParam param = (QueryParam) annotation;
+ Parameter qp = new Parameter();
+ qp.setIn(QUERY_PARAM);
+ qp.setName(param.value());
+ parameter = qp;
+ } else if (annotation instanceof PathParam) {
+ PathParam param = (PathParam) annotation;
+ Parameter pp = new Parameter();
+ pp.setIn(PATH_PARAM);
+ pp.setName(param.value());
+ parameter = pp;
+ } else if (annotation instanceof MatrixParam) {
+ MatrixParam param = (MatrixParam) annotation;
+ Parameter pp = new Parameter();
+ pp.setIn(PATH_PARAM);
+ pp.setStyle(Parameter.StyleEnum.MATRIX);
+ pp.setName(param.value());
+ parameter = pp;
+ } else if (annotation instanceof HeaderParam) {
+ HeaderParam param = (HeaderParam) annotation;
+ Parameter pp = new Parameter();
+ pp.setIn(HEADER_PARAM);
+ pp.setName(param.value());
+ parameter = pp;
+ } else if (annotation instanceof CookieParam) {
+ CookieParam param = (CookieParam) annotation;
+ Parameter pp = new Parameter();
+ pp.setIn(COOKIE_PARAM);
+ pp.setName(param.value());
+ parameter = pp;
+ } else if (annotation instanceof io.swagger.v3.oas.annotations.Parameter) {
+ if (((io.swagger.v3.oas.annotations.Parameter) annotation).hidden()) {
+ extractParametersResult.parameters = parameters;
+ return extractParametersResult;
+ }
+ if (parameter == null) {
+ Parameter pp = new Parameter();
+ parameter = pp;
+ }
+ } else {
+ if (handleAdditionalAnnotation(parameters, annotation, type, typesToSkip, classConsumes, methodConsumes, components, includeRequestBody, jsonViewAnnotation)) {
+
+ extractParametersResult.parameters.addAll(parameters);
+ }
+ }
+ }
+
+
+
+ if (parameter != null && StringUtils.isNotBlank(parameter.getIn())) {
+ parameter.setRequired(isRequired);
+ parameters.add(parameter);
+ } else if (includeRequestBody) {
+ Parameter unknownParameter = ParameterProcessor.applyAnnotations(
+ null,
+ type,
+ annotations,
+ components,
+ classConsumes == null ? new String[0] : classConsumes.value(),
+ methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation);
+ if (unknownParameter != null) {
+
+ if (StringUtils.isNotBlank(unknownParameter.getIn()) && !"form".equals(unknownParameter.getIn())) {
+ extractParametersResult.parameters.add(unknownParameter);
+ } else if ("form".equals(unknownParameter.getIn())) {
+ unknownParameter.setIn(null);
+ extractParametersResult.formParameter = unknownParameter;
+ } else { // return as request body
+ extractParametersResult.requestBody = unknownParameter;
+
+ }
+ }
+ }
+ for (Parameter p : parameters) {
+
+
+ Parameter processedParameter = ParameterProcessor.applyAnnotations(
+ p,
+ type,
+ annotations,
+ components,
+ classConsumes == null ? new String[0] : classConsumes.value(),
+ methodConsumes == null ? new String[0] : methodConsumes.value(),
+ jsonViewAnnotation);
+
+ if (processedParameter != null) {
+ processedParameter.setRequired(isRequired);
+ extractParametersResult.parameters.add(processedParameter);
+ }
+ }
+ return extractParametersResult;
+ }
+
+ protected boolean _isOptionalType(JavaType propType) {
+ return Arrays.asList("com.google.common.base.Optional", "java.util.Optional")
+ .contains(propType.getRawClass().getCanonicalName());
+ }
+
+
+ /**
+ * Adds additional annotation processing support
+ *
+ * @param parameters
+ * @param annotation
+ * @param type
+ * @param typesToSkip
+ */
+
+ private boolean handleAdditionalAnnotation(List parameters, Annotation annotation,
+ final Type type, Set typesToSkip, javax.ws.rs.Consumes classConsumes,
+ javax.ws.rs.Consumes methodConsumes, Components components, boolean includeRequestBody, JsonView jsonViewAnnotation) {
+ boolean processed = false;
+ if (BeanParam.class.isAssignableFrom(annotation.getClass())) {
+
+
+ // Use Jackson's logic for processing Beans
+ JavaType javaType = constructType(type);
+
+ final BeanDescription beanDesc = mapper.getSerializationConfig().introspect(javaType);
+ final List properties = beanDesc.findProperties();
+
+// if(extracted.size() == 0)
+// {
+// System.out.println("Unable to find parameters...");
+//
+// Parameter processedParam = ParameterProcessor.applyAnnotations(
+// p,
+// paramType,
+// paramAnnotations,
+// components,
+// classConsumes == null ? new String[0] : classConsumes.value(),
+// methodConsumes == null ? new String[0] : methodConsumes.value(),
+// jsonViewAnnotation);
+// }
+
+ for (final BeanPropertyDefinition propDef : properties) {
+ final AnnotatedField field = propDef.getField();
+ final AnnotatedMethod setter = propDef.getSetter();
+ final AnnotatedMethod getter = propDef.getGetter();
+ final List paramAnnotations = new ArrayList();
+ final Iterator extensions = OpenAPIExtensions.chain();
+ Type paramType = null;
+
+ // Gather the field's details
+ if (field != null) {
+ paramType = field.getType();
+
+ AnnotationMap annotationMap = field.getAllAnnotations();
+
+ for (final Annotation fieldAnnotation : annotationMap.annotations()) {
+ if (!paramAnnotations.contains(fieldAnnotation)) {
+ paramAnnotations.add(fieldAnnotation);
+ }
+ }
+ }
+
+ // Gather the setter's details but only the ones we need
+ if (setter != null) {
+ // Do not set the param class/type from the setter if the values are already identified
+ if (paramType == null) {
+ // paramType will stay null if there is no parameter
+ paramType = setter.getParameterType(0);
+ }
+
+ AnnotationMap annotationMap = setter.getAllAnnotations();
+
+ for (final Annotation fieldAnnotation : annotationMap.annotations()) {
+ if (!paramAnnotations.contains(fieldAnnotation)) {
+ paramAnnotations.add(fieldAnnotation);
+ }
+ }
+ }
+
+ // Gather the getter's details but only the ones we need
+ if (getter != null) {
+ // Do not set the param class/type from the getter if the values are already identified
+ if (paramType == null) {
+ paramType = getter.getType();
+ }
+
+ AnnotationMap annotationMap = getter.getAllAnnotations();
+
+ for (final Annotation fieldAnnotation : annotationMap.annotations()) {
+ if (!paramAnnotations.contains(fieldAnnotation)) {
+ paramAnnotations.add(fieldAnnotation);
+ }
+ }
+ }
+
+ if (paramType == null) {
+ continue;
+ }
+
+
+ // Re-process all Bean fields and let the default swagger-jaxrs/swagger-jersey-jaxrs processors do their thing
+ List extracted =
+ extensions.next().extractParameters(
+ paramAnnotations,
+ paramType,
+ typesToSkip,
+ components,
+ classConsumes,
+ methodConsumes,
+ includeRequestBody,
+ jsonViewAnnotation,
+ extensions).parameters;
+
+
+
+
+ for (Parameter p : extracted) {
+ Parameter processedParam = ParameterProcessor.applyAnnotations(
+ p,
+ paramType,
+ paramAnnotations,
+ components,
+ classConsumes == null ? new String[0] : classConsumes.value(),
+ methodConsumes == null ? new String[0] : methodConsumes.value(),
+ jsonViewAnnotation);
+ if (processedParam != null) {
+
+ System.out.println("added new parameters: " + processedParam);
+ parameters.add(processedParam);
+ }
+ }
+
+ processed = true;
+ }
+ }
+ return processed;
+ }
+
+ @Override
+ protected boolean shouldIgnoreClass(Class> cls) {
+ return cls.getName().startsWith("javax.ws.rs.") || cls.getName().startsWith("io.undertow");
+
+ }
+}
diff --git a/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java
similarity index 98%
rename from src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java
rename to src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java
index 50d7990..33997ed 100644
--- a/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java
+++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/AnnotationHelper.java
@@ -1,7 +1,7 @@
/**
*
*/
-package io.sinistral.proteus.server.swagger;
+package io.sinistral.proteus.server.tools.swagger;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
@@ -136,7 +136,6 @@ else if( formParam != null )
{
return parameter.getName();
}
-
}
@Override
diff --git a/src/main/java/io/sinistral/proteus/server/swagger/Reader.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java
similarity index 99%
rename from src/main/java/io/sinistral/proteus/server/swagger/Reader.java
rename to src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java
index b22653e..9d13929 100644
--- a/src/main/java/io/sinistral/proteus/server/swagger/Reader.java
+++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/Reader.java
@@ -1,7 +1,7 @@
/**
*
*/
-package io.sinistral.proteus.server.swagger;
+package io.sinistral.proteus.server.tools.swagger;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
diff --git a/src/main/java/io/sinistral/proteus/server/swagger/ServerParameterExtension.java b/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java
similarity index 97%
rename from src/main/java/io/sinistral/proteus/server/swagger/ServerParameterExtension.java
rename to src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java
index dbf1e70..8b51250 100644
--- a/src/main/java/io/sinistral/proteus/server/swagger/ServerParameterExtension.java
+++ b/src/main/java/io/sinistral/proteus/server/tools/swagger/ServerParameterExtension.java
@@ -1,7 +1,7 @@
/**
*
*/
-package io.sinistral.proteus.server.swagger;
+package io.sinistral.proteus.server.tools.swagger;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
diff --git a/src/main/java/io/sinistral/proteus/services/OpenAPIService.java b/src/main/java/io/sinistral/proteus/services/OpenAPIService.java
new file mode 100644
index 0000000..a344046
--- /dev/null
+++ b/src/main/java/io/sinistral/proteus/services/OpenAPIService.java
@@ -0,0 +1,444 @@
+
+package io.sinistral.proteus.services;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.HttpMethod;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.typesafe.config.Config;
+
+import io.sinistral.proteus.server.endpoints.EndpointInfo;
+import io.sinistral.proteus.server.tools.oas.Reader;
+import io.sinistral.proteus.server.tools.oas.ServerModelResolver;
+import io.sinistral.proteus.server.tools.oas.ServerParameterExtension;
+import io.swagger.v3.jaxrs2.ext.OpenAPIExtensions;
+import io.swagger.v3.jaxrs2.integration.JaxrsApplicationAndAnnotationScanner;
+import io.swagger.v3.oas.integration.GenericOpenApiContext;
+import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.integration.api.OpenApiContext;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.RoutingHandler;
+import io.undertow.util.Headers;
+import io.undertow.util.Methods;
+
+
+@Singleton
+public class OpenAPIService extends BaseService implements Supplier
+{
+
+ private static Logger log = LoggerFactory.getLogger(OpenAPIService.class.getCanonicalName());
+
+ protected final String resourcePathPrefix = "oas";
+
+
+ @Inject
+ @Named("openapi.resourcePrefix")
+ protected String resourcePrefix;
+
+ @Inject
+ @Named("openapi.basePath")
+ protected String basePath;
+
+ @Inject
+ @Named("openapi.specFilename")
+ protected String specFilename;
+
+ @Inject
+ @Named("openapi")
+ protected Config openAPIConfig;
+
+ @Inject
+ @Named("application.name")
+ protected String applicationName;
+
+ @Inject
+ @Named("openapi.port")
+ protected Integer port;
+
+ @Inject
+ @Named("application.path")
+ protected String applicationPath;
+
+ @Inject
+ protected RoutingHandler router;
+
+ @Inject
+ @Named("registeredEndpoints")
+ protected Set registeredEndpoints;
+
+ @Inject
+ @Named("registeredControllers")
+ protected Set> registeredControllers;
+
+ @Inject
+ @Named("registeredHandlerWrappers")
+ protected Map registeredHandlerWrappers;
+
+ protected ObjectMapper mapper = new ObjectMapper();
+
+ protected ObjectWriter writer = null;
+
+ protected YAMLMapper yamlMapper = new YAMLMapper();
+
+ protected Path resourcePath = null;
+
+ protected ClassLoader serviceClassLoader = null;
+
+ protected OpenAPI openApi = null;
+
+ protected String spec = null;
+
+ protected String indexHTML = null;
+
+ @SuppressWarnings("deprecation")
+ public OpenAPIService( )
+ {
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
+ mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
+ mapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH,true);
+ mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(Include.NON_NULL);
+
+ mapper.registerModule(new Jdk8Module());
+
+
+ yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ yamlMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
+ yamlMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
+ yamlMapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH,true);
+ yamlMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ yamlMapper.setSerializationInclusion(Include.NON_NULL);
+
+ writer = yamlMapper.writerWithDefaultPrettyPrinter();
+ writer = writer.without(SerializationFeature.WRITE_NULL_MAP_VALUES);
+ }
+
+
+ @SuppressWarnings("rawtypes")
+ public void generateSpec() throws Exception
+ {
+
+ Set> classes = this.registeredControllers;
+
+ OpenAPIExtensions.setExtensions(Collections.singletonList(new ServerParameterExtension()));
+
+ OpenAPI openApi = new OpenAPI();
+
+ Info info = mapper.convertValue(openAPIConfig.getValue("info").unwrapped(), Info.class);
+
+ openApi.setInfo(info);
+
+ Map securitySchemes = mapper.convertValue(openAPIConfig.getValue("securitySchemes").unwrapped(), new TypeReference