Skip to content

Commit 8d0ce84

Browse files
committed
Hidden controller exposes Operation annotated method. Fixes #1316.
1 parent 5592430 commit 8d0ce84

File tree

11 files changed

+162
-84
lines changed

11 files changed

+162
-84
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
import static org.springdoc.core.Constants.OPERATION_ATTRIBUTE;
111111
import static org.springdoc.core.Constants.SPRING_MVC_SERVLET_PATH;
112112
import static org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.isDeprecated;
113+
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
113114

114115
/**
115116
* The type Abstract open api resource.
@@ -188,6 +189,20 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
188189
*/
189190
protected final String groupName;
190191

192+
/**
193+
* The constant MODEL_AND_VIEW_CLASS.
194+
*/
195+
private static Class<?> modelAndViewClass;
196+
197+
static {
198+
try {
199+
modelAndViewClass = Class.forName("org.springframework.web.servlet.ModelAndView");
200+
}
201+
catch (ClassNotFoundException classNotFoundException) {
202+
LOGGER.trace(classNotFoundException.getMessage());
203+
}
204+
}
205+
191206
/**
192207
* Instantiates a new Abstract open api resource.
193208
*
@@ -586,7 +601,7 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis
586601
else
587602
routerOperationList.addAll(Arrays.asList(routerOperations.value()));
588603
if (routerOperationList.size() == 1)
589-
calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()),locale);
604+
calculatePath(routerOperationList.stream().map(routerOperation -> new RouterOperation(routerOperation, routerFunctionVisitor.getRouterFunctionDatas().get(0))).collect(Collectors.toList()), locale);
590605
else {
591606
List<RouterOperation> operationList = routerOperationList.stream().map(RouterOperation::new).collect(Collectors.toList());
592607
mergeRouters(routerFunctionVisitor.getRouterFunctionDatas(), operationList);
@@ -723,6 +738,23 @@ public static boolean containsResponseBody(HandlerMethod handlerMethod) {
723738
}
724739

725740

741+
/**
742+
* Is rest controller boolean.
743+
*
744+
* @param restControllers the rest controllers
745+
* @param handlerMethod the handler method
746+
* @param operationPath the operation path
747+
* @return the boolean
748+
*/
749+
protected boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod,
750+
String operationPath) {
751+
boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), io.swagger.v3.oas.annotations.Operation.class);
752+
753+
return ((containsResponseBody(handlerMethod) || hasOperationAnnotation) && restControllers.containsKey(handlerMethod.getBean().toString()) || isAdditionalRestController(handlerMethod.getBeanType()))
754+
&& operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
755+
&& (springDocConfigProperties.isModelAndViewAllowed() || modelAndViewClass == null || !modelAndViewClass.isAssignableFrom(handlerMethod.getMethod().getReturnType()));
756+
}
757+
726758
/**
727759
* Is hidden rest controllers boolean.
728760
*
@@ -1137,6 +1169,17 @@ protected boolean isShowActuator() {
11371169
return springDocConfigProperties.isShowActuator() && optionalActuatorProvider.isPresent();
11381170
}
11391171

1172+
/**
1173+
* Is actuator rest controller boolean.
1174+
*
1175+
* @param operationPath the operation path
1176+
* @param handlerMethod the handler method
1177+
* @return the boolean
1178+
*/
1179+
protected boolean isActuatorRestController(String operationPath, HandlerMethod handlerMethod) {
1180+
return isShowActuator() && optionalActuatorProvider.get().isRestController(operationPath, handlerMethod);
1181+
}
1182+
11401183
/**
11411184
* Write json value string.
11421185
*

springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ public class GenericResponseService {
105105
*/
106106
private static final Logger LOGGER = LoggerFactory.getLogger(GenericResponseService.class);
107107

108+
/**
109+
* The Response entity exception handler class.
110+
*/
111+
private static Class<?> responseEntityExceptionHandlerClass;
112+
113+
static {
114+
try {
115+
responseEntityExceptionHandlerClass = Class.forName("org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler");
116+
}
117+
catch (ClassNotFoundException classNotFoundException) {
118+
LOGGER.trace(classNotFoundException.getMessage());
119+
}
120+
}
121+
108122
/**
109123
* Instantiates a new Generic response builder.
110124
*
@@ -202,16 +216,8 @@ public void buildGenericResponse(Components components, Map<String, Object> find
202216
* @return the boolean
203217
*/
204218
private boolean isResponseEntityExceptionHandlerMethod(Method m) {
205-
if (AnnotatedElementUtils.hasAnnotation(m.getDeclaringClass(), ControllerAdvice.class)) {
206-
try {
207-
Class aClass = Class.forName("org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler");
208-
if (aClass.isAssignableFrom(m.getDeclaringClass()) && ReflectionUtils.findMethod(aClass, m.getName(), m.getParameterTypes()) != null)
209-
return true;
210-
}
211-
catch (ClassNotFoundException e) {
212-
LOGGER.trace(e.getMessage());
213-
}
214-
}
219+
if (AnnotatedElementUtils.hasAnnotation(m.getDeclaringClass(), ControllerAdvice.class))
220+
return responseEntityExceptionHandlerClass != null && ((responseEntityExceptionHandlerClass.isAssignableFrom(m.getDeclaringClass()) && ReflectionUtils.findMethod(responseEntityExceptionHandlerClass, m.getName(), m.getParameterTypes()) != null));
215221
return false;
216222
}
217223

@@ -238,7 +244,7 @@ private Map<String, ApiResponse> computeResponseFromDoc(Components components, M
238244
apiResponsesOp.addApiResponse(apiResponseAnnotations.responseCode(), apiResponse);
239245
continue;
240246
}
241-
apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(),methodAttributes.getLocale()));
247+
apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(), methodAttributes.getLocale()));
242248
buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotations, apiResponse);
243249
Map<String, Object> extensions = AnnotationsUtils.getExtensions(apiResponseAnnotations.extensions());
244250
if (!CollectionUtils.isEmpty(extensions))

springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,25 @@ public class OpenAPIService {
146146
*/
147147
private String serverBaseUrl;
148148

149+
private static Class<?> basicErrorController;
150+
151+
static {
152+
try {
153+
//spring-boot 2
154+
basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController");
155+
}
156+
catch (ClassNotFoundException e) {
157+
//spring-boot 1
158+
try {
159+
basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.BasicErrorController");
160+
}
161+
catch (ClassNotFoundException classNotFoundException) {
162+
//Basic error controller class not found
163+
LOGGER.trace(classNotFoundException.getMessage());
164+
}
165+
}
166+
}
167+
149168
/**
150169
* Instantiates a new Open api builder.
151170
*
@@ -228,21 +247,6 @@ else if (calculatedOpenAPI.getInfo() == null) {
228247
}
229248

230249
private void initializeHiddenRestController() {
231-
Class basicErrorController = null;
232-
try {
233-
//spring-boot 2
234-
basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController");
235-
}
236-
catch (ClassNotFoundException e) {
237-
//spring-boot 1
238-
try {
239-
basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.BasicErrorController");
240-
}
241-
catch (ClassNotFoundException classNotFoundException) {
242-
//Basic error controller class not found
243-
LOGGER.warn(classNotFoundException.getMessage());
244-
}
245-
}
246250
if (basicErrorController != null)
247251
getConfig().addHiddenRestControllers(basicErrorController);
248252
List<Class<?>> hiddenRestControllers = this.mappingsMap.entrySet().parallelStream()

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,25 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou
158158
*/
159159
private SpringDocDataRestUtils springDocDataRestUtils;
160160

161+
/**
162+
* The constant delegatingHandlerMappingClass.
163+
*/
164+
private static Class delegatingHandlerMappingClass;
165+
166+
static {
167+
try {
168+
delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_CLASS);
169+
}
170+
catch (ClassNotFoundException e) {
171+
try {
172+
delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_INTERFACE);
173+
}
174+
catch (ClassNotFoundException exception) {
175+
LOGGER.trace(e.getMessage());
176+
}
177+
}
178+
}
179+
161180
/**
162181
* Instantiates a new Spring repository rest resource provider.
163182
*
@@ -302,18 +321,6 @@ public void customize(OpenAPI openAPI) {
302321
private List<HandlerMapping> getHandlerMappingList() {
303322
if (handlerMappingList == null) {
304323
handlerMappingList = new ArrayList<>();
305-
Class delegatingHandlerMappingClass = null;
306-
try {
307-
delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_CLASS);
308-
}
309-
catch (ClassNotFoundException e) {
310-
try {
311-
delegatingHandlerMappingClass = Class.forName(DELEGATING_HANDLER_MAPPING_INTERFACE);
312-
}
313-
catch (ClassNotFoundException exception) {
314-
LOGGER.warn(e.getMessage());
315-
}
316-
}
317324
if (delegatingHandlerMappingClass != null) {
318325
Object object = applicationContext.getBean(delegatingHandlerMappingClass);
319326
try {
@@ -395,7 +402,7 @@ private List<RouterOperation> findControllers(List<RouterOperation> routerOperat
395402
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
396403
DataRestRepository dataRestRepository, OpenAPI openAPI, Locale locale) {
397404
dataRestRouterOperationService.buildEntityRouterOperationList(routerOperationList, handlerMethodMap, resourceMetadata,
398-
dataRestRepository, openAPI,locale );
405+
dataRestRepository, openAPI, locale);
399406
return routerOperationList;
400407
}
401408

springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import test.org.springdoc.api.AbstractSpringDocTest;
2222

2323
import org.springframework.boot.autoconfigure.SpringBootApplication;
24+
import org.springframework.test.context.TestPropertySource;
2425

2526
/**
2627
* The type Spring doc app 149 test.
2728
*/
29+
@TestPropertySource(properties = "springdoc.model-and-view-allowed=true")
2830
public class SpringDocApp149Test extends AbstractSpringDocTest {
2931

3032
/**

springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
import com.fasterxml.jackson.core.JsonProcessingException;
3535
import io.swagger.v3.core.util.PathUtils;
36-
import io.swagger.v3.oas.annotations.Operation;
3736
import io.swagger.v3.oas.models.OpenAPI;
3837
import org.springdoc.api.AbstractOpenApiResource;
3938
import org.springdoc.core.AbstractRequestService;
@@ -49,7 +48,6 @@
4948

5049
import org.springframework.beans.factory.ObjectFactory;
5150
import org.springframework.context.ApplicationContext;
52-
import org.springframework.core.annotation.AnnotatedElementUtils;
5351
import org.springframework.http.server.reactive.ServerHttpRequest;
5452
import org.springframework.util.MimeType;
5553
import org.springframework.web.bind.annotation.RequestMethod;
@@ -62,7 +60,6 @@
6260

6361
import static org.springdoc.core.ActuatorProvider.getTag;
6462
import static org.springdoc.core.Constants.DEFAULT_GROUP_NAME;
65-
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
6663

6764
/**
6865
* The type Open api resource.
@@ -192,8 +189,7 @@ protected void calculatePath(Map<String, Object> restControllers, Map<RequestMap
192189
String[] produces = requestMappingInfo.getProducesCondition().getProducibleMediaTypes().stream().map(MimeType::toString).toArray(String[]::new);
193190
String[] consumes = requestMappingInfo.getConsumesCondition().getConsumableMediaTypes().stream().map(MimeType::toString).toArray(String[]::new);
194191
String[] headers = requestMappingInfo.getHeadersCondition().getExpressions().stream().map(Object::toString).toArray(String[]::new);
195-
if ((operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
196-
&& (isRestController(restControllers,handlerMethod) || (isShowActuator())))
192+
if ((isRestController(restControllers, handlerMethod, operationPath) || isActuatorRestController(operationPath, handlerMethod))
197193
&& isFilterCondition(handlerMethod, operationPath, produces, consumes, headers)) {
198194
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
199195
// default allowed requestmethods
@@ -253,15 +249,4 @@ protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String ap
253249
*/
254250
protected abstract String getServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl);
255251

256-
/**
257-
* Is rest controller boolean.
258-
*
259-
* @param restControllers the rest controllers
260-
* @param handlerMethod the handler method
261-
* @return the boolean
262-
*/
263-
private boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod) {
264-
boolean hasOperationAnnotation = AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), Operation.class);
265-
return hasOperationAnnotation || restControllers.containsKey(handlerMethod.getBean().toString());
266-
}
267252
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package test.org.springdoc.api.app149;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
/**
11+
* @author bnasslahsen
12+
*/
13+
@RestController
14+
@RequestMapping("/api")
15+
public class HiddenHelloController {
16+
17+
@Operation(description = "I want here some custom config")
18+
@GetMapping("/{entity}/{id}")
19+
public ResponseEntity getEntity() {
20+
throw new UnsupportedOperationException("the body is not relevant now");
21+
}
22+
}

springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app149/SpringDocApp149Test.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package test.org.springdoc.api.app149;
2020

21+
import org.springdoc.core.SpringDocUtils;
2122
import test.org.springdoc.api.AbstractSpringDocTest;
2223

2324
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -28,4 +29,8 @@ public class SpringDocApp149Test extends AbstractSpringDocTest {
2829
@SpringBootApplication
2930
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app149" })
3031
static class SpringDocTestApp {}
32+
33+
static {
34+
SpringDocUtils.getConfig().addHiddenRestControllers(HiddenHelloController.class);
35+
}
3136
}

0 commit comments

Comments
 (0)