Repository: springdoc/springdoc-openapi
Pull request #149 (2019-11-08 10:48:06)

Open up for subclassing again + NPE fix
Previous PR to open base classes for subclassing outside of package was undone during recent refactorings.

Now added some subclasses in tests directory to make sure same thing doesn't happen again.

Also fixed an NPE in AbstractResponseBuilder when some endpoints have multiple media types and others don't.


Patch:
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
index be8f1ac36..d57081839 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java
@@ -3,12 +3,20 @@ package org.springdoc.api;
 import com.fasterxml.jackson.annotation.JsonView;
 import io.swagger.v3.core.util.ReflectionUtils;
 import io.swagger.v3.oas.annotations.Hidden;
-import io.swagger.v3.oas.models.*;
+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.PathItem.HttpMethod;
+import io.swagger.v3.oas.models.Paths;
 import io.swagger.v3.oas.models.responses.ApiResponses;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springdoc.core.*;
+import org.springdoc.core.AbstractRequestBuilder;
+import org.springdoc.core.AbstractResponseBuilder;
+import org.springdoc.core.MethodAttributes;
+import org.springdoc.core.OpenAPIBuilder;
+import org.springdoc.core.OperationBuilder;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -25,7 +33,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-abstract class AbstractOpenApiResource {
+public abstract class AbstractOpenApiResource {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOpenApiResource.class);
     final OpenAPIBuilder openAPIBuilder;
@@ -35,7 +43,7 @@ abstract class AbstractOpenApiResource {
     private final List<OpenApiCustomiser> openApiCustomisers;
     private boolean computeDone;
 
-     AbstractOpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
+    protected AbstractOpenApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
                                       AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
                                       List<OpenApiCustomiser> openApiCustomisers) {
         super();
@@ -46,7 +54,7 @@ abstract class AbstractOpenApiResource {
         this.openApiCustomisers = openApiCustomisers;
     }
 
-     OpenAPI getOpenApi() {
+    protected OpenAPI getOpenApi() {
         OpenAPI openApi;
         if (!computeDone) {
             Instant start = Instant.now();
@@ -81,8 +89,8 @@ abstract class AbstractOpenApiResource {
 
     protected abstract void getPaths(Map<String, Object> findRestControllers);
 
-     void calculatePath(OpenAPIBuilder openAPIBuilder, HandlerMethod handlerMethod, String operationPath,
-                                 Set<RequestMethod> requestMethods) {
+    protected void calculatePath(OpenAPIBuilder openAPIBuilder, HandlerMethod handlerMethod, String operationPath,
+                       Set<RequestMethod> requestMethods) {
         OpenAPI openAPI = openAPIBuilder.getOpenAPI();
         Components components = openAPIBuilder.getComponents();
         Paths paths = openAPIBuilder.getPaths();
@@ -260,4 +268,7 @@ abstract class AbstractOpenApiResource {
         return pathItemObject;
     }
 
+    protected void setServerBaseUrl(String setServerBaseUrl) {
+        this.openAPIBuilder.setServerBaseUrl(setServerBaseUrl);
+    }
 }
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractParameterBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractParameterBuilder.java
index 466267a8e..1a2e0b379 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractParameterBuilder.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractParameterBuilder.java
@@ -28,14 +28,18 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 
 @SuppressWarnings("rawtypes")
- abstract class AbstractParameterBuilder {
+public abstract class AbstractParameterBuilder {
 
     private LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer;
 
-    public AbstractParameterBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
+    protected AbstractParameterBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
         this.localSpringDocParameterNameDiscoverer = localSpringDocParameterNameDiscoverer;
     }
 
@@ -158,7 +162,7 @@ import java.util.*;
             returnType = parameter.getParameterizedType();
             ct = constructType(parameter.getType());
             schemaImplementation = parameter.getType();
-        } 
+        }
 
         if (isFile(ct)) {
             schemaN = getFileSchema(requestBodyInfo);
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
index 8423e5b6f..0f1473f07 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java
@@ -25,7 +25,7 @@ public abstract class AbstractRequestBuilder {
     private final RequestBodyBuilder requestBodyBuilder;
     private final OperationBuilder operationBuilder;
 
-    AbstractRequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
+    protected AbstractRequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
                            OperationBuilder operationBuilder) {
         super();
         this.parameterBuilder = parameterBuilder;
@@ -98,7 +98,7 @@ public abstract class AbstractRequestBuilder {
 
     protected abstract Operation customiseOperation(Operation operation, HandlerMethod handlerMethod);
 
-    private boolean isParamToIgnore(java.lang.reflect.Parameter parameter) {
+    protected boolean isParamToIgnore(java.lang.reflect.Parameter parameter) {
         if (parameter.isAnnotationPresent(PathVariable.class)) {
             return false;
         }
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractResponseBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractResponseBuilder.java
index 1deb09877..952f71513 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractResponseBuilder.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractResponseBuilder.java
@@ -25,8 +25,13 @@ import org.springframework.web.method.HandlerMethod;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -39,7 +44,7 @@ public abstract class AbstractResponseBuilder {
 
     private final OperationBuilder operationBuilder;
 
-     AbstractResponseBuilder(OperationBuilder operationBuilder) {
+    protected AbstractResponseBuilder(OperationBuilder operationBuilder) {
         super();
         this.operationBuilder = operationBuilder;
     }
@@ -83,7 +88,8 @@ public abstract class AbstractResponseBuilder {
 
     protected abstract Schema calculateSchemaFromParameterizedType(Components components, ParameterizedType returnType,
                                                                    JsonView jsonView);
-     Schema calculateSchemaParameterizedType(Components components, ParameterizedType parameterizedType,
+
+    protected Schema calculateSchemaParameterizedType(Components components, ParameterizedType parameterizedType,
                                                       JsonView jsonView) {
         Schema schemaN = null;
         Type type = parameterizedType.getActualTypeArguments()[0];
@@ -157,7 +163,9 @@ public abstract class AbstractResponseBuilder {
                 Content newContent = optionalContent.get();
                 for (String mediaTypeStr : methodAttributes.getAllProduces()) {
                     io.swagger.v3.oas.models.media.MediaType mediaType = newContent.get(mediaTypeStr);
-                    mergeSchema(existingContent, mediaType.getSchema(), mediaTypeStr);
+                    if (mediaType != null) {
+                        mergeSchema(existingContent, mediaType.getSchema(), mediaTypeStr);
+                    }
                 }
                 apiResponse.content(existingContent);
             }


Test patch:
diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/subclass/OpenApiResource.java b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/OpenApiResource.java
new file mode 100644
index 000000000..aa884b5b8
--- /dev/null
+++ b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/OpenApiResource.java
@@ -0,0 +1,35 @@
+package org.springdoc.subclass;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import org.springdoc.api.AbstractOpenApiResource;
+import org.springdoc.api.OpenApiCustomiser;
+import org.springdoc.core.AbstractRequestBuilder;
+import org.springdoc.core.AbstractResponseBuilder;
+import org.springdoc.core.OpenAPIBuilder;
+import org.springdoc.core.OperationBuilder;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class which sub class AbstractOpenApiResource in a different package and makes sure base class access is not changed.
+ */
+public class OpenApiResource extends AbstractOpenApiResource {
+
+    public OpenApiResource(OpenAPIBuilder openAPIBuilder,
+                           AbstractRequestBuilder requestBuilder,
+                           AbstractResponseBuilder responseBuilder,
+                           OperationBuilder operationParser,
+                           List<OpenApiCustomiser> openApiCustomisers) {
+        super(openAPIBuilder, requestBuilder, responseBuilder, operationParser, openApiCustomisers);
+    }
+
+    public OpenAPI getComputedApi() {
+        return super.getOpenApi();
+    }
+
+    @Override
+    protected void getPaths(Map<String, Object> findRestControllers) {
+        // do stuff
+    }
+}
diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ParameterBuilder.java b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ParameterBuilder.java
new file mode 100644
index 000000000..a937a8b88
--- /dev/null
+++ b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ParameterBuilder.java
@@ -0,0 +1,27 @@
+package org.springdoc.subclass;
+
+import com.fasterxml.jackson.databind.JavaType;
+import org.springdoc.core.AbstractParameterBuilder;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Class which sub class AbstractParameterBuilder in a different package and makes sure base class access is not changed. .
+ */
+public class ParameterBuilder extends AbstractParameterBuilder {
+
+    public ParameterBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
+        super(localSpringDocParameterNameDiscoverer);
+    }
+
+    @Override
+    protected boolean isFile(ParameterizedType parameterizedType) {
+        return false;
+    }
+
+    @Override
+    protected boolean isFile(JavaType ct) {
+        return false;
+    }
+}
diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/subclass/RequestBuilder.java b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/RequestBuilder.java
new file mode 100644
index 000000000..5a615aa50
--- /dev/null
+++ b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/RequestBuilder.java
@@ -0,0 +1,48 @@
+package org.springdoc.subclass;
+
+import io.swagger.v3.oas.models.Operation;
+import org.springdoc.core.AbstractParameterBuilder;
+import org.springdoc.core.AbstractRequestBuilder;
+import org.springdoc.core.OperationBuilder;
+import org.springdoc.core.RequestBodyBuilder;
+import org.springframework.web.method.HandlerMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Parameter;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Class which sub class AbstractRequestBuilder in a different package and makes sure base class access is not changed. .
+ */
+public class RequestBuilder extends AbstractRequestBuilder {
+
+    private static final Set<Class<?>> CLASSES_TO_IGNORE = Collections.emptySet();
+    private static final Set<Class<? extends Annotation>> ANNOTATIONS_TO_IGNORE = Collections.emptySet();
+
+    protected RequestBuilder(AbstractParameterBuilder parameterBuilder,
+                             RequestBodyBuilder requestBodyBuilder,
+                             OperationBuilder operationBuilder) {
+        super(parameterBuilder, requestBodyBuilder, operationBuilder);
+    }
+
+    @Override
+    protected boolean isParamTypeToIgnore(Class<?> paramType) {
+        return CLASSES_TO_IGNORE.contains(paramType);
+    }
+
+    @Override
+    protected Operation customiseOperation(Operation operation, HandlerMethod handlerMethod) {
+        return operation;
+    }
+
+    @Override
+    protected boolean isParamToIgnore(Parameter parameter) {
+        return hasAnnotationToIgnore(parameter) || super.isParamToIgnore(parameter);
+    }
+
+    private boolean hasAnnotationToIgnore(Parameter parameter) {
+        return ANNOTATIONS_TO_IGNORE.stream().anyMatch(parameter::isAnnotationPresent);
+    }
+
+}
diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ResponseBuilder.java b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ResponseBuilder.java
new file mode 100644
index 000000000..08f5584c3
--- /dev/null
+++ b/springdoc-openapi-common/src/test/java/org/springdoc/subclass/ResponseBuilder.java
@@ -0,0 +1,26 @@
+package org.springdoc.subclass;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.media.Schema;
+import org.springdoc.core.AbstractResponseBuilder;
+import org.springdoc.core.OperationBuilder;
+
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Class which sub class AbstractResponseBuilder in a different package and makes sure base class access is not changed. .
+ */
+public class ResponseBuilder extends AbstractResponseBuilder {
+
+    public ResponseBuilder(OperationBuilder operationBuilder) {
+        super(operationBuilder);
+    }
+
+    @Override
+    protected Schema calculateSchemaFromParameterizedType(Components components, ParameterizedType returnType, JsonView jsonView) {
+        return null;
+    }
+
+
+}