Skip to content

Commit

Permalink
Hidden controller exposes Operation annotated method. Fixes #1316.
Browse files Browse the repository at this point in the history
  • Loading branch information
bnasslahsen committed Oct 20, 2021
1 parent 5592430 commit 8d0ce84
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<RouterOperation> operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList());
mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList);
Expand Down Expand Up @@ -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<String, Object> 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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -202,16 +216,8 @@ public void buildGenericResponse(Components components, Map<String, Object> 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;
}

Expand All @@ -238,7 +244,7 @@ private Map<String, ApiResponse> 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<String, Object> extensions = AnnotationsUtils.getExtensions(apiResponseAnnotations.extensions());
if (!CollectionUtils.isEmpty(extensions))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<Class<?>> hiddenRestControllers = this.mappingsMap.entrySet().parallelStream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -302,18 +321,6 @@ public void customize(OpenAPI openAPI) {
private List<HandlerMapping> 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 {
Expand Down Expand Up @@ -395,7 +402,7 @@ private List<RouterOperation> findControllers(List<RouterOperation> routerOperat
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
DataRestRepository dataRestRepository, OpenAPI openAPI, Locale locale) {
dataRestRouterOperationService.buildEntityRouterOperationList(routerOperationList, handlerMethodMap, resourceMetadata,
dataRestRepository, openAPI,locale );
dataRestRepository, openAPI, locale);
return routerOperationList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -192,8 +189,7 @@ protected void calculatePath(Map<String, Object> restControllers, Map<RequestMap
String[] produces = requestMappingInfo.getProducesCondition().getProducibleMediaTypes().stream().map(MimeType::toString).toArray(String[]::new);
String[] consumes = requestMappingInfo.getConsumesCondition().getConsumableMediaTypes().stream().map(MimeType::toString).toArray(String[]::new);
String[] headers = requestMappingInfo.getHeadersCondition().getExpressions().stream().map(Object::toString).toArray(String[]::new);
if ((operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
&& (isRestController(restControllers,handlerMethod) || (isShowActuator())))
if ((isRestController(restControllers, handlerMethod, operationPath) || isActuatorRestController(operationPath, handlerMethod))
&& isFilterCondition(handlerMethod, operationPath, produces, consumes, headers)) {
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
// default allowed requestmethods
Expand Down Expand Up @@ -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<String, Object> restControllers, HandlerMethod handlerMethod) {
boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), Operation.class);
return hasOperationAnnotation || restControllers.containsKey(handlerMethod.getBean().toString());
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}
Loading

0 comments on commit 8d0ce84

Please sign in to comment.