Repository: springdoc/springdoc-openapi
Pull request #293 (2019-12-23 19:26:50)

SPI for customizing operations, parameters and properties
make customizing operations, parameters and properties available through an SPI


Patch:
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 cacfc62b8..3dbc0034d 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
@@ -8,6 +8,8 @@ import io.swagger.v3.oas.models.media.Schema;
 import io.swagger.v3.oas.models.parameters.Parameter;
 import org.apache.commons.lang3.StringUtils;
 import org.springdoc.core.RequestInfo.ParameterType;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springdoc.core.customizer.ParameterCustomizer;
 import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
 import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.core.annotation.AnnotationUtils;
@@ -65,13 +67,18 @@ public abstract class AbstractRequestBuilder {
     private final AbstractParameterBuilder parameterBuilder;
     private final RequestBodyBuilder requestBodyBuilder;
     private final OperationBuilder operationBuilder;
+    private final List<OperationCustomizer> operationCustomizers;
+    private final List<ParameterCustomizer> parameterCustomizers;
 
     protected AbstractRequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
-                                     OperationBuilder operationBuilder) {
+                                     OperationBuilder operationBuilder, List<OperationCustomizer> operationCustomizers,
+                                     List<ParameterCustomizer> parameterCustomizers) {
         super();
         this.parameterBuilder = parameterBuilder;
         this.requestBodyBuilder = requestBodyBuilder;
         this.operationBuilder = operationBuilder;
+        this.operationCustomizers = operationCustomizers;
+        this.parameterCustomizers = parameterCustomizers;
     }
 
 
@@ -156,13 +163,31 @@ public abstract class AbstractRequestBuilder {
         return operation;
     }
 
-    protected abstract Operation customiseOperation(Operation operation, HandlerMethod handlerMethod);
+    protected Operation customiseOperation(Operation operation, HandlerMethod handlerMethod) {
+        if (!CollectionUtils.isEmpty(operationCustomizers)) {
+            for (OperationCustomizer customizer : operationCustomizers) {
+                operation = customizer.customize(operation, handlerMethod);
+            }
+        }
+        return operation;
+    }
+
+    protected Parameter customiseParameter(Parameter parameter, ParameterInfo parameterInfo, HandlerMethod handlerMethod) {
+        if (!CollectionUtils.isEmpty(parameterCustomizers)) {
+            for (ParameterCustomizer customizer : parameterCustomizers) {
+                parameter = customizer.customize(parameter, parameterInfo.getParameter(), handlerMethod);
+            }
+        }
+        return parameter;
+    }
+
+    ;
 
     protected boolean isParamToIgnore(java.lang.reflect.Parameter parameter) {
         if (parameter.isAnnotationPresent(PathVariable.class)) {
             return false;
         }
-        return parameterBuilder.isAnnotationToIgnore(parameter)  || PARAM_TYPES_TO_IGNORE.contains(parameter.getType()) || (AnnotationUtils.findAnnotation(parameter.getType(), Hidden.class) != null);
+        return parameterBuilder.isAnnotationToIgnore(parameter) || PARAM_TYPES_TO_IGNORE.contains(parameter.getType()) || (AnnotationUtils.findAnnotation(parameter.getType(), Hidden.class) != null);
     }
 
     private void setParams(Operation operation, List<Parameter> operationParameters, RequestBodyInfo requestBodyInfo) {
@@ -214,6 +239,8 @@ public abstract class AbstractRequestBuilder {
         if (RequestMethod.GET.equals(requestMethod)) {
             parameter = this.buildParam(QUERY_PARAM, components, parameterInfo, Boolean.TRUE, null, jsonView);
         }
+
+        parameter = customiseParameter(parameter, parameterInfo, handlerMethod);
         return parameter;
     }
 
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
index 72c801dbf..ef1b4baa4 100644
--- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java
@@ -3,6 +3,8 @@ package org.springdoc.core;
 import io.swagger.v3.core.converter.ModelConverter;
 import io.swagger.v3.oas.models.OpenAPI;
 import org.springdoc.core.converters.ObjectNodeConverter;
+import org.springdoc.core.converters.PropertyCustomizingConverter;
+import org.springdoc.core.customizer.PropertyCustomizer;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.ApplicationContext;
@@ -29,6 +31,11 @@ public class SpringDocConfiguration {
         return new ObjectNodeConverter();
     }
 
+    @Bean
+    PropertyCustomizingConverter propertyCustomizingConverter(List<PropertyCustomizer> customizers){
+        return new PropertyCustomizingConverter(customizers);
+    }
+
     @Bean
     IgnoredParameterAnnotationsDefault ignoredParameterAnnotationsDefault() {
         return new IgnoredParameterAnnotationsDefault();
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/PropertyCustomizingConverter.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/PropertyCustomizingConverter.java
new file mode 100644
index 000000000..db711f5d4
--- /dev/null
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/PropertyCustomizingConverter.java
@@ -0,0 +1,34 @@
+package org.springdoc.core.converters;
+
+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.oas.models.media.Schema;
+import org.springdoc.core.customizer.PropertyCustomizer;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class PropertyCustomizingConverter implements ModelConverter {
+
+    private final List<PropertyCustomizer> customizers;
+
+    public PropertyCustomizingConverter(List<PropertyCustomizer> customizers) {
+        this.customizers = customizers;
+    }
+
+    @Override
+    public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
+        if (chain.hasNext()) {
+            Schema<?> resolvedSchema = chain.next().resolve(type, context, chain);
+            if (type.isSchemaProperty() && !CollectionUtils.isEmpty(customizers)) {
+                for (PropertyCustomizer customizer : customizers)
+                    resolvedSchema = customizer.customize(resolvedSchema, type);
+            }
+            return resolvedSchema;
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/OperationCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/OperationCustomizer.java
new file mode 100644
index 000000000..b685043c3
--- /dev/null
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/OperationCustomizer.java
@@ -0,0 +1,19 @@
+package org.springdoc.core.customizer;
+
+import io.swagger.v3.oas.models.Operation;
+import org.springframework.web.method.HandlerMethod;
+
+/**
+ * Implement and register a bean of type {@link OperationCustomizer} to customize an operation
+ * based on the handler method input
+ */
+public interface OperationCustomizer {
+
+    /**
+     * @param operation     input operation
+     * @param handlerMethod original handler method
+     * @return customized operation
+     */
+    Operation customize(Operation operation, HandlerMethod handlerMethod);
+}
+
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/ParameterCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/ParameterCustomizer.java
new file mode 100644
index 000000000..7e49e7bb5
--- /dev/null
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/ParameterCustomizer.java
@@ -0,0 +1,18 @@
+package org.springdoc.core.customizer;
+
+import io.swagger.v3.oas.models.parameters.Parameter;
+import org.springframework.web.method.HandlerMethod;
+
+/**
+ * Implement and register a bean of type {@link ParameterCustomizer} to customize a parameter
+ * based on the parameter and handler method input
+ */
+public interface ParameterCustomizer {
+    /**
+     * @param parameterModel to be customized
+     * @param parameter original parameter from handler method
+     * @param handlerMethod handler method
+     * @return customized parameter
+     */
+    Parameter customize(Parameter parameterModel, java.lang.reflect.Parameter parameter, HandlerMethod handlerMethod);
+}
diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/PropertyCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/PropertyCustomizer.java
new file mode 100644
index 000000000..5ecddb24b
--- /dev/null
+++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizer/PropertyCustomizer.java
@@ -0,0 +1,18 @@
+package org.springdoc.core.customizer;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.oas.models.media.Schema;
+
+/**
+ * Implement and register a bean of type {@link PropertyCustomizer} to customize a schema property
+ * based on annotated type
+ */
+public interface PropertyCustomizer {
+
+    /**
+     * @param property to be customized
+     * @param type     form the model class
+     * @return customized property
+     */
+    Schema<?> customize(Schema<?> property, AnnotatedType type);
+}
diff --git a/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/KotlinCoroutinesRequestBuilder.java b/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/KotlinCoroutinesRequestBuilder.java
index ffddb31a2..115d4c011 100644
--- a/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/KotlinCoroutinesRequestBuilder.java
+++ b/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/KotlinCoroutinesRequestBuilder.java
@@ -1,24 +1,22 @@
 package org.springdoc.core;
 
-import io.swagger.v3.oas.models.Operation;
 import kotlin.coroutines.Continuation;
-import org.springframework.web.method.HandlerMethod;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springdoc.core.customizer.ParameterCustomizer;
+
+import java.util.List;
 
 
 public class KotlinCoroutinesRequestBuilder extends AbstractRequestBuilder {
 
 
     public KotlinCoroutinesRequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
-                                          OperationBuilder operationBuilder) {
-        super(parameterBuilder, requestBodyBuilder, operationBuilder);
+                                          OperationBuilder operationBuilder, List<OperationCustomizer> customizers,
+                                          List<ParameterCustomizer> parameterCustomizers) {
+        super(parameterBuilder, requestBodyBuilder, operationBuilder, customizers, parameterCustomizers);
     }
 
     static {
         PARAM_TYPES_TO_IGNORE.add(Continuation.class);
     }
-
-    @Override
-    protected Operation customiseOperation(Operation operation, HandlerMethod handlerMethod) {
-        return operation;
-    }
 }
diff --git a/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/SpringDocKotlinConfiguration.java b/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/SpringDocKotlinConfiguration.java
index 9f8a5c454..e5a6c4397 100644
--- a/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/SpringDocKotlinConfiguration.java
+++ b/springdoc-openapi-kotlin/src/main/java/org/springdoc/core/SpringDocKotlinConfiguration.java
@@ -1,5 +1,10 @@
 package org.springdoc.core;
 
+import java.util.List;
+
+import org.springdoc.core.customizer.ParameterCustomizer;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -11,12 +16,18 @@ import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
 @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
 public class SpringDocKotlinConfiguration {
 
+    @Autowired(required = false)
+    private List<OperationCustomizer> operationCustomizers;
+
+    @Autowired(required = false)
+    private List<ParameterCustomizer> parameterCustomizers;
+
     @Bean
     @Primary
     KotlinCoroutinesRequestBuilder kotlinCoroutinesRequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
                                                                   OperationBuilder operationBuilder) {
         return new KotlinCoroutinesRequestBuilder(parameterBuilder, requestBodyBuilder,
-                operationBuilder);
+                operationBuilder, operationCustomizers, parameterCustomizers);
     }
 
 
diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/RequestBuilder.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/RequestBuilder.java
index 93a77b20f..fa84dce2a 100644
--- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/RequestBuilder.java
+++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/RequestBuilder.java
@@ -1,11 +1,13 @@
 package org.springdoc.core;
 
-import io.swagger.v3.oas.models.Operation;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springdoc.core.customizer.ParameterCustomizer;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.http.server.reactive.ServerHttpResponse;
-import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.server.ServerWebExchange;
 
+import java.util.List;
+
 public class RequestBuilder extends AbstractRequestBuilder {
 
     static {
@@ -15,12 +17,8 @@ public class RequestBuilder extends AbstractRequestBuilder {
     }
 
     public RequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
-                          OperationBuilder operationBuilder) {
-        super(parameterBuilder, requestBodyBuilder, operationBuilder);
-    }
-
-    @Override
-    protected Operation customiseOperation(Operation operation, HandlerMethod handlerMethod) {
-        return operation;
+                          OperationBuilder operationBuilder, List<OperationCustomizer> operationCustomizers,
+                          List<ParameterCustomizer> parameterCustomizers) {
+        super(parameterBuilder, requestBodyBuilder, operationBuilder, operationCustomizers, parameterCustomizers);
     }
 }
diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/SpringDocWebFluxConfiguration.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/SpringDocWebFluxConfiguration.java
index 4180aba6c..850ad6404 100644
--- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/SpringDocWebFluxConfiguration.java
+++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/core/SpringDocWebFluxConfiguration.java
@@ -2,6 +2,9 @@ package org.springdoc.core;
 
 import org.springdoc.api.OpenApiCustomiser;
 import org.springdoc.api.OpenApiResource;
+import org.springdoc.core.customizer.ParameterCustomizer;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -17,6 +20,12 @@ import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
 @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
 public class SpringDocWebFluxConfiguration {
 
+    @Autowired(required = false)
+    private List<OperationCustomizer> operationCustomizers;
+
+    @Autowired(required = false)
+    private List<ParameterCustomizer> parameterCustomizers;
+
     @Bean
     @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
     public OpenApiResource openApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
@@ -38,7 +47,7 @@ public class SpringDocWebFluxConfiguration {
     public RequestBuilder requestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
                                          OperationBuilder operationBuilder) {
         return new RequestBuilder(parameterBuilder, requestBodyBuilder,
-                operationBuilder);
+                operationBuilder, operationCustomizers, parameterCustomizers);
     }
 
     @Bean
diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/RequestBuilder.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/RequestBuilder.java
index f973e4e59..3e9dd9dbb 100644
--- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/RequestBuilder.java
+++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/RequestBuilder.java
@@ -1,7 +1,9 @@
 package org.springdoc.core;
 
-import io.swagger.v3.oas.models.Operation;
-import org.springframework.web.method.HandlerMethod;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springdoc.core.customizer.ParameterCustomizer;
+
+import java.util.List;
 
 public class RequestBuilder extends AbstractRequestBuilder {
 
@@ -14,13 +16,10 @@ public class RequestBuilder extends AbstractRequestBuilder {
         PARAM_TYPES_TO_IGNORE.add(javax.servlet.http.HttpSession.class);
     }
 
-    public RequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
-                          OperationBuilder operationBuilder) {
-        super(parameterBuilder, requestBodyBuilder, operationBuilder);
-    }
 
-    @Override
-    protected Operation customiseOperation(Operation operation, HandlerMethod handlerMethod) {
-        return operation;
+    public RequestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
+                          OperationBuilder operationBuilder, List<OperationCustomizer> customizers,
+                          List<ParameterCustomizer> parameterCustomizers) {
+        super(parameterBuilder, requestBodyBuilder, operationBuilder, customizers, parameterCustomizers);
     }
 }
diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/SpringDocWebMvcConfiguration.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/SpringDocWebMvcConfiguration.java
index 6f5414e21..d4115512f 100644
--- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/SpringDocWebMvcConfiguration.java
+++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/core/SpringDocWebMvcConfiguration.java
@@ -3,6 +3,11 @@ package org.springdoc.core;
 import org.springdoc.api.ActuatorProvider;
 import org.springdoc.api.OpenApiCustomiser;
 import org.springdoc.api.OpenApiResource;
+import org.springdoc.core.customizer.OperationCustomizer;
+import org.springdoc.core.customizer.ParameterCustomizer;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -18,6 +23,12 @@ import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
 @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
 public class SpringDocWebMvcConfiguration {
 
+    @Autowired(required = false)
+    private List<OperationCustomizer> operationCustomizers;
+
+    @Autowired(required = false)
+    private List<ParameterCustomizer> parameterCustomizers;
+
     @Bean
     public OpenApiResource openApiResource(OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
                                            AbstractResponseBuilder responseBuilder, OperationBuilder operationParser,
@@ -30,18 +41,21 @@ public class SpringDocWebMvcConfiguration {
     }
 
     @Bean
+    @ConditionalOnMissingBean
     public ParameterBuilder parameterBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer, IgnoredParameterAnnotations ignoredParameterAnnotations) {
         return new ParameterBuilder(localSpringDocParameterNameDiscoverer, ignoredParameterAnnotations);
     }
 
     @Bean
+    @ConditionalOnMissingBean
     public RequestBuilder requestBuilder(AbstractParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
                                          OperationBuilder operationBuilder) {
         return new RequestBuilder(parameterBuilder, requestBodyBuilder,
-                operationBuilder);
+                operationBuilder, operationCustomizers, parameterCustomizers);
     }
 
     @Bean
+    @ConditionalOnMissingBean
     public ResponseBuilder responseBuilder(OperationBuilder operationBuilder) {
         return new ResponseBuilder(operationBuilder);
     }


Test patch:
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/HelloController.java
new file mode 100644
index 000000000..01ca45263
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/HelloController.java
@@ -0,0 +1,20 @@
+package test.org.springdoc.api.app70;
+
+import org.springframework.web.bind.annotation.*;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import test.org.springdoc.api.app70.customizer.CustomizedOperation;
+import test.org.springdoc.api.app70.customizer.CustomizedParameter;
+import test.org.springdoc.api.app70.model.ApiType;
+
+@RestController
+public class HelloController {
+
+		@CustomizedOperation
+		@Operation(description = "Some operation")
+    @GetMapping("/example/{test}")
+    public ApiType test(@PathVariable @CustomizedParameter @Parameter(description = "Parameter description") String test) {
+    	return new ApiType();
+    }
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocApp70Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocApp70Test.java
new file mode 100644
index 000000000..2e8f04bc1
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocApp70Test.java
@@ -0,0 +1,6 @@
+package test.org.springdoc.api.app70;
+
+import test.org.springdoc.api.AbstractSpringDocTest;
+
+public class SpringDocApp70Test extends AbstractSpringDocTest {
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocTestApp.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocTestApp.java
new file mode 100644
index 000000000..1e1461f1a
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/SpringDocTestApp.java
@@ -0,0 +1,11 @@
+package test.org.springdoc.api.app70;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringDocTestApp {
+    public static void main(String[] args) {
+        SpringApplication.run(SpringDocTestApp.class, args);
+    }
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedOperation.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedOperation.java
new file mode 100644
index 000000000..fcc628a48
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedOperation.java
@@ -0,0 +1,9 @@
+package test.org.springdoc.api.app70.customizer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CustomizedOperation {
+	String addition() default "customized operation!";
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedParameter.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedParameter.java
new file mode 100644
index 000000000..1405eee27
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedParameter.java
@@ -0,0 +1,9 @@
+package test.org.springdoc.api.app70.customizer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CustomizedParameter {
+	String addition() default "customized parameter!";
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedProperty.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedProperty.java
new file mode 100644
index 000000000..49a60b0a2
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/CustomizedProperty.java
@@ -0,0 +1,9 @@
+package test.org.springdoc.api.app70.customizer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CustomizedProperty {
+	String addition() default "customized property!";
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/OperationCustomizer.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/OperationCustomizer.java
new file mode 100644
index 000000000..9b698eb6c
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/OperationCustomizer.java
@@ -0,0 +1,18 @@
+package test.org.springdoc.api.app70.customizer;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+
+import io.swagger.v3.oas.models.Operation;
+
+@Component
+public class OperationCustomizer implements org.springdoc.core.customizer.OperationCustomizer {
+	@Override
+	public Operation customize(Operation operation, HandlerMethod handlerMethod) {
+		CustomizedOperation annotation = handlerMethod.getMethodAnnotation(CustomizedOperation.class);
+		if(annotation != null){
+			operation.description(operation.getDescription() + ", " + annotation.addition());
+		}
+		return operation;
+	}
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/ParameterCustomizer.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/ParameterCustomizer.java
new file mode 100644
index 000000000..5b88cf1bf
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/ParameterCustomizer.java
@@ -0,0 +1,18 @@
+package test.org.springdoc.api.app70.customizer;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+
+import io.swagger.v3.oas.models.parameters.Parameter;
+
+@Component
+public class ParameterCustomizer implements org.springdoc.core.customizer.ParameterCustomizer {
+	@Override
+	public Parameter customize(Parameter parameterModel, java.lang.reflect.Parameter parameter, HandlerMethod handlerMethod) {
+		CustomizedParameter annotation = parameter.getAnnotation(CustomizedParameter.class);
+		if(annotation != null){
+			parameterModel.description(parameterModel.getDescription() + ", " + annotation.addition());
+		}
+		return parameterModel;
+	}
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/PropertyCustomizer.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/PropertyCustomizer.java
new file mode 100644
index 000000000..5c6486e20
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/customizer/PropertyCustomizer.java
@@ -0,0 +1,31 @@
+package test.org.springdoc.api.app70.customizer;
+
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.stereotype.Component;
+
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.oas.models.media.Schema;
+
+@Component
+public class PropertyCustomizer implements org.springdoc.core.customizer.PropertyCustomizer {
+	@Override
+	public Schema<?> customize(Schema<?> property, AnnotatedType type) {
+		Annotation[] ctxAnnotations = type.getCtxAnnotations();
+		if(ctxAnnotations == null) {
+			return property;
+		}
+
+		Optional<CustomizedProperty> propertyAnnotation = Stream.of(ctxAnnotations)
+				.filter(CustomizedProperty.class::isInstance)
+				.findFirst()
+				.map(CustomizedProperty.class::cast);
+
+		propertyAnnotation
+				.ifPresent(annotation -> property.description(property.getDescription() + ", " + annotation.addition()));
+
+		return property;
+	}
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/model/ApiType.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/model/ApiType.java
new file mode 100644
index 000000000..2f0baad14
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app70/model/ApiType.java
@@ -0,0 +1,18 @@
+package test.org.springdoc.api.app70.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import test.org.springdoc.api.app70.customizer.CustomizedProperty;
+
+public class ApiType {
+	@CustomizedProperty
+	@Schema(description = "Test description")
+	private String someProperty;
+
+	public String getSomeProperty() {
+		return someProperty;
+	}
+
+	public void setSomeProperty(String someProperty) {
+		this.someProperty = someProperty;
+	}
+}
diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app70.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app70.json
new file mode 100644
index 000000000..6f122a735
--- /dev/null
+++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app70.json
@@ -0,0 +1,60 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "OpenAPI definition",
+    "version": "v0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost",
+      "description": "Generated server url"
+    }
+  ],
+  "paths": {
+    "/example/{test}": {
+      "get": {
+        "tags": [
+          "hello-controller"
+        ],
+        "description": "Some operation, customized operation!",
+        "operationId": "test",
+        "parameters": [
+          {
+            "name": "test",
+            "in": "path",
+            "description": "Parameter description, customized parameter!",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "default response",
+            "content": {
+              "*/*": {
+                "schema": {
+                  "$ref": "#/components/schemas/ApiType"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "ApiType": {
+        "type": "object",
+        "properties": {
+          "someProperty": {
+            "type": "string",
+            "description": "Test description, customized property!"
+          }
+        }
+      }
+    }
+  }
+}