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 fd4729656..a2639e4e1 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 @@ -110,6 +110,7 @@ import static org.springdoc.core.Constants.OPERATION_ATTRIBUTE; import static org.springdoc.core.Constants.SPRING_MVC_SERVLET_PATH; import static org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.isDeprecated; +import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** * The type Abstract open api resource. @@ -188,6 +189,20 @@ public abstract class AbstractOpenApiResource extends SpecFilter { */ protected final String groupName; + /** + * The constant MODEL_AND_VIEW_CLASS. + */ + private static Class modelAndViewClass; + + static { + try { + modelAndViewClass = Class.forName("org.springframework.web.servlet.ModelAndView"); + } + catch (ClassNotFoundException classNotFoundException) { + LOGGER.trace(classNotFoundException.getMessage()); + } + } + /** * Instantiates a new Abstract open api resource. * @@ -586,7 +601,7 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis else routerOperationList.addAll(Arrays.asList(routerOperations.value())); if (routerOperationList.size() == 1) - calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()),locale); + calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()), locale); else { List operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList()); mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList); @@ -723,6 +738,23 @@ public static boolean containsResponseBody(HandlerMethod handlerMethod) { } + /** + * Is rest controller boolean. + * + * @param restControllers the rest controllers + * @param handlerMethod the handler method + * @param operationPath the operation path + * @return the boolean + */ + protected boolean isRestController(Map restControllers, HandlerMethod handlerMethod, + String operationPath) { + boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), io.swagger.v3.oas.annotations.Operation.class); + + return ((containsResponseBody(handlerMethod) || hasOperationAnnotation) && restControllers.containsKey(handlerMethod.getBean().toString()) || isAdditionalRestController(handlerMethod.getBeanType())) + && operationPath.startsWith(DEFAULT_PATH_SEPARATOR) + && (springDocConfigProperties.isModelAndViewAllowed() || modelAndViewClass == null || !modelAndViewClass.isAssignableFrom(handlerMethod.getMethod().getReturnType())); + } + /** * Is hidden rest controllers boolean. * @@ -1137,6 +1169,17 @@ protected boolean isShowActuator() { return springDocConfigProperties.isShowActuator() && optionalActuatorProvider.isPresent(); } + /** + * Is actuator rest controller boolean. + * + * @param operationPath the operation path + * @param handlerMethod the handler method + * @return the boolean + */ + protected boolean isActuatorRestController(String operationPath, HandlerMethod handlerMethod) { + return isShowActuator() && optionalActuatorProvider.get().isRestController(operationPath, handlerMethod); + } + /** * Write json value string. * diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java index b1e153217..96a1d6724 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java @@ -105,6 +105,20 @@ public class GenericResponseService { */ private static final Logger LOGGER = LoggerFactory.getLogger(GenericResponseService.class); + /** + * The Response entity exception handler class. + */ + private static Class responseEntityExceptionHandlerClass; + + static { + try { + responseEntityExceptionHandlerClass = Class.forName("org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler"); + } + catch (ClassNotFoundException classNotFoundException) { + LOGGER.trace(classNotFoundException.getMessage()); + } + } + /** * Instantiates a new Generic response builder. * @@ -202,16 +216,8 @@ public void buildGenericResponse(Components components, Map find * @return the boolean */ private boolean isResponseEntityExceptionHandlerMethod(Method m) { - if (AnnotatedElementUtils.hasAnnotation(m.getDeclaringClass(), ControllerAdvice.class)) { - try { - Class aClass = Class.forName("org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler"); - if (aClass.isAssignableFrom(m.getDeclaringClass()) && ReflectionUtils.findMethod(aClass, m.getName(), m.getParameterTypes()) != null) - return true; - } - catch (ClassNotFoundException e) { - LOGGER.trace(e.getMessage()); - } - } + if (AnnotatedElementUtils.hasAnnotation(m.getDeclaringClass(), ControllerAdvice.class)) + return responseEntityExceptionHandlerClass != null && ((responseEntityExceptionHandlerClass.isAssignableFrom(m.getDeclaringClass()) && ReflectionUtils.findMethod(responseEntityExceptionHandlerClass, m.getName(), m.getParameterTypes()) != null)); return false; } @@ -238,7 +244,7 @@ private Map computeResponseFromDoc(Components components, M apiResponsesOp.addApiResponse(apiResponseAnnotations.responseCode(), apiResponse); continue; } - apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(),methodAttributes.getLocale())); + apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(), methodAttributes.getLocale())); buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotations, apiResponse); Map extensions = AnnotationsUtils.getExtensions(apiResponseAnnotations.extensions()); if (!CollectionUtils.isEmpty(extensions)) diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java index 52ef8f6b0..0db0ce30f 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java @@ -146,6 +146,25 @@ public class OpenAPIService { */ private String serverBaseUrl; + private static Class basicErrorController; + + static { + try { + //spring-boot 2 + basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController"); + } + catch (ClassNotFoundException e) { + //spring-boot 1 + try { + basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.BasicErrorController"); + } + catch (ClassNotFoundException classNotFoundException) { + //Basic error controller class not found + LOGGER.trace(classNotFoundException.getMessage()); + } + } + } + /** * Instantiates a new Open api builder. * @@ -228,21 +247,6 @@ else if (calculatedOpenAPI.getInfo() == null) { } private void initializeHiddenRestController() { - Class basicErrorController = null; - try { - //spring-boot 2 - basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController"); - } - catch (ClassNotFoundException e) { - //spring-boot 1 - try { - basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.BasicErrorController"); - } - catch (ClassNotFoundException classNotFoundException) { - //Basic error controller class not found - LOGGER.warn(classNotFoundException.getMessage()); - } - } if (basicErrorController != null) getConfig().addHiddenRestControllers(basicErrorController); List> hiddenRestControllers = this.mappingsMap.entrySet().parallelStream() diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java index fc765b31b..4aae80c30 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java @@ -158,6 +158,25 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou */ private SpringDocDataRestUtils springDocDataRestUtils; + /** + * The constant delegatingHandlerMappingClass. + */ + private static Class delegatingHandlerMappingClass; + + static { + try { + delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_CLASS); + } + catch (ClassNotFoundException e) { + try { + delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_INTERFACE); + } + catch (ClassNotFoundException exception) { + LOGGER.trace(e.getMessage()); + } + } + } + /** * Instantiates a new Spring repository rest resource provider. * @@ -302,18 +321,6 @@ public void customize(OpenAPI openAPI) { private List getHandlerMappingList() { if (handlerMappingList == null) { handlerMappingList = new ArrayList<>(); - Class delegatingHandlerMappingClass = null; - try { - delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_CLASS); - } - catch (ClassNotFoundException e) { - try { - delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_INTERFACE); - } - catch (ClassNotFoundException exception) { - LOGGER.warn(e.getMessage()); - } - } if (delegatingHandlerMappingClass != null) { Object object = applicationContext.getBean(delegatingHandlerMappingClass); try { @@ -395,7 +402,7 @@ private List findControllers(List routerOperat Map handlerMethodMap, ResourceMetadata resourceMetadata, DataRestRepository dataRestRepository, OpenAPI openAPI, Locale locale) { dataRestRouterOperationService.buildEntityRouterOperationList(routerOperationList, handlerMethodMap, resourceMetadata, - dataRestRepository, openAPI,locale ); + dataRestRepository, openAPI, locale); return routerOperationList; } diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java index ac9870e4b..7c0f6d5ab 100644 --- a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java @@ -21,10 +21,12 @@ import test.org.springdoc.api.AbstractSpringDocTest; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; /** * The type Spring doc app 149 test. */ +@TestPropertySource(properties = "springdoc.model-and-view-allowed=true") public class SpringDocApp149Test extends AbstractSpringDocTest { /** diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java index 5cc21fb74..cf9582a7d 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java @@ -33,7 +33,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.v3.core.util.PathUtils; -import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.models.OpenAPI; import org.springdoc.api.AbstractOpenApiResource; import org.springdoc.core.AbstractRequestService; @@ -49,7 +48,6 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.MimeType; import org.springframework.web.bind.annotation.RequestMethod; @@ -62,7 +60,6 @@ import static org.springdoc.core.ActuatorProvider.getTag; import static org.springdoc.core.Constants.DEFAULT_GROUP_NAME; -import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** * The type Open api resource. @@ -192,8 +189,7 @@ protected void calculatePath(Map restControllers, Map requestMethods = requestMappingInfo.getMethodsCondition().getMethods(); // default allowed requestmethods @@ -253,15 +249,4 @@ protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String ap */ protected abstract String getServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl); - /** - * Is rest controller boolean. - * - * @param restControllers the rest controllers - * @param handlerMethod the handler method - * @return the boolean - */ - private boolean isRestController(Map restControllers, HandlerMethod handlerMethod) { - boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), Operation.class); - return hasOperationAnnotation || restControllers.containsKey(handlerMethod.getBean().toString()); - } } diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java new file mode 100644 index 000000000..addf06c49 --- /dev/null +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java @@ -0,0 +1,22 @@ +package test.org.springdoc.api.app149; + +import io.swagger.v3.oas.annotations.Operation; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author bnasslahsen + */ +@RestController +@RequestMapping("/api") +public class HiddenHelloController { + + @Operation(description = "I want here some custom config") + @GetMapping("/{entity}/{id}") + public ResponseEntity getEntity() { + throw new UnsupportedOperationException("the body is not relevant now"); + } +} diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java index a017f6efd..5af07a3f1 100644 --- a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java @@ -18,6 +18,7 @@ package test.org.springdoc.api.app149; +import org.springdoc.core.SpringDocUtils; import test.org.springdoc.api.AbstractSpringDocTest; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -28,4 +29,8 @@ public class SpringDocApp149Test extends AbstractSpringDocTest { @SpringBootApplication @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app149" }) static class SpringDocTestApp {} + + static { + SpringDocUtils.getConfig().addHiddenRestControllers(HiddenHelloController.class); + } } diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java index 391895716..e7e8078f4 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java @@ -35,7 +35,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.v3.core.util.PathUtils; -import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.models.OpenAPI; import org.springdoc.api.AbstractOpenApiResource; import org.springdoc.core.AbstractRequestService; @@ -52,12 +51,10 @@ import org.springdoc.webmvc.core.RouterFunctionProvider; import org.springframework.beans.factory.ObjectFactory; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; @@ -65,7 +62,6 @@ import static org.springdoc.core.ActuatorProvider.getTag; import static org.springdoc.core.Constants.DEFAULT_GROUP_NAME; -import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** * The type Web mvc open api resource. @@ -205,7 +201,7 @@ protected void getPaths(Map restControllers, Locale locale) { calculatePath(operationList, locale); restResourceProvider.customize(openAPIService.getCalculatedOpenAPI()); Map mapDataRest = restResourceProvider.getHandlerMethods(); - Map requestMappingMap = restResourceProvider.getRepositoryRestControllerEndpoints(); + Map requestMappingMap = restResourceProvider.getRepositoryRestControllerEndpoints(); Class[] additionalRestClasses = requestMappingMap.values().stream().map(Object::getClass).toArray(Class[]::new); AbstractOpenApiResource.addRestControllers(additionalRestClasses); calculatePath(requestMappingMap, mapDataRest, locale); @@ -253,8 +249,7 @@ protected void calculatePath(Map restControllers, Map requestMethods = requestMappingInfo.getMethodsCondition().getMethods(); // default allowed requestmethods @@ -293,29 +288,10 @@ public static Set getActivePatterns(RequestMappingInfo requestMappingInf */ private Comparator> byReversedRequestMappingInfos() { return Comparator., String> - comparing(a -> a.getKey().toString()) + comparing(a -> a.getKey().toString()) .reversed(); } - /** - * Is rest controller boolean. - * - * @param restControllers the rest controllers - * @param handlerMethod the handler method - * @param operationPath the operation path - * @return the boolean - */ - protected boolean isRestController(Map restControllers, HandlerMethod handlerMethod, - String operationPath) { - boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), Operation.class); - if (hasOperationAnnotation) - return true; - - return (containsResponseBody(handlerMethod) && restControllers.containsKey(handlerMethod.getBean().toString()) || isAdditionalRestController(handlerMethod.getBeanType())) - && operationPath.startsWith(DEFAULT_PATH_SEPARATOR) - && (springDocConfigProperties.isModelAndViewAllowed() || !ModelAndView.class.isAssignableFrom(handlerMethod.getMethod().getReturnType())); - } - /** * Calculate server url. * @@ -337,5 +313,4 @@ protected void calculateServerUrl(HttpServletRequest request, String apiDocsUrl) */ protected abstract String getServerUrl(HttpServletRequest request, String apiDocsUrl); - } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java new file mode 100644 index 000000000..addf06c49 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/HiddenHelloController.java @@ -0,0 +1,22 @@ +package test.org.springdoc.api.app149; + +import io.swagger.v3.oas.annotations.Operation; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author bnasslahsen + */ +@RestController +@RequestMapping("/api") +public class HiddenHelloController { + + @Operation(description = "I want here some custom config") + @GetMapping("/{entity}/{id}") + public ResponseEntity getEntity() { + throw new UnsupportedOperationException("the body is not relevant now"); + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java index f988dbd2c..c56f7a7c2 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java @@ -18,12 +18,19 @@ package test.org.springdoc.api.app149; +import org.springdoc.core.SpringDocUtils; import test.org.springdoc.api.AbstractSpringDocTest; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +@TestPropertySource(properties = "springdoc.model-and-view-allowed=true") public class SpringDocApp149Test extends AbstractSpringDocTest { @SpringBootApplication static class SpringDocTestApp {} + + static { + SpringDocUtils.getConfig().addHiddenRestControllers(HiddenHelloController.class); + } }