Skip to content

Commit 0529008

Browse files
author
bnasslahsen
committed
initial support of @RepositoryRestResource. Fixes #644
1 parent 44acd06 commit 0529008

File tree

48 files changed

+4126
-115
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4126
-115
lines changed

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

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
3131
import java.util.Collection;
32+
import java.util.Collections;
3233
import java.util.HashSet;
3334
import java.util.LinkedHashMap;
3435
import java.util.List;
@@ -42,6 +43,7 @@
4243
import io.swagger.v3.core.filter.SpecFilter;
4344
import io.swagger.v3.core.util.ReflectionUtils;
4445
import io.swagger.v3.oas.annotations.Hidden;
46+
import io.swagger.v3.oas.annotations.callbacks.Callback;
4547
import io.swagger.v3.oas.annotations.enums.ParameterIn;
4648
import io.swagger.v3.oas.models.Components;
4749
import io.swagger.v3.oas.models.OpenAPI;
@@ -244,9 +246,7 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
244246
Set<io.swagger.v3.oas.annotations.callbacks.Callback> apiCallbacks = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, io.swagger.v3.oas.annotations.callbacks.Callback.class);
245247

246248
// callbacks
247-
if (!CollectionUtils.isEmpty(apiCallbacks))
248-
operationParser.buildCallbacks(apiCallbacks, openAPI, methodAttributes)
249-
.ifPresent(operation::setCallbacks);
249+
buildCallbacks(openAPI, methodAttributes, operation, apiCallbacks);
250250

251251
// allow for customisation
252252
customiseOperation(operation, handlerMethod);
@@ -256,11 +256,18 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
256256
}
257257
}
258258

259+
private void buildCallbacks(OpenAPI openAPI, MethodAttributes methodAttributes, Operation operation, Set<Callback> apiCallbacks) {
260+
if (!CollectionUtils.isEmpty(apiCallbacks))
261+
operationParser.buildCallbacks(apiCallbacks, openAPI, methodAttributes)
262+
.ifPresent(operation::setCallbacks);
263+
}
264+
259265
protected void calculatePath(List<RouterOperation> routerOperationList) {
260266
ApplicationContext applicationContext = openAPIBuilder.getContext();
261267
if (!CollectionUtils.isEmpty(routerOperationList)) {
268+
Collections.sort(routerOperationList);
262269
for (RouterOperation routerOperation : routerOperationList) {
263-
if (!Void.class.equals(routerOperation.getBeanClass())) {
270+
if (routerOperation.getBeanClass() != null && !Void.class.equals(routerOperation.getBeanClass())) {
264271
Object handlerBean = applicationContext.getBean(routerOperation.getBeanClass());
265272
HandlerMethod handlerMethod = null;
266273
if (StringUtils.isNotBlank(routerOperation.getBeanMethod())) {
@@ -286,7 +293,10 @@ protected void calculatePath(List<RouterOperation> routerOperationList) {
286293
calculatePath(handlerMethod, routerOperation);
287294
}
288295
}
289-
else if (StringUtils.isNotBlank(routerOperation.getOperation().operationId()) && isPathToMatch(routerOperation.getPath())) {
296+
else if (routerOperation.getOperation() != null && StringUtils.isNotBlank(routerOperation.getOperation().operationId()) && isPathToMatch(routerOperation.getPath())) {
297+
calculatePath(routerOperation);
298+
}
299+
else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(routerOperation.getOperationModel().getOperationId()) && isPathToMatch(routerOperation.getPath())) {
290300
calculatePath(routerOperation);
291301
}
292302
}
@@ -295,7 +305,6 @@ else if (StringUtils.isNotBlank(routerOperation.getOperation().operationId()) &&
295305

296306
protected void calculatePath(RouterOperation routerOperation) {
297307
String operationPath = routerOperation.getPath();
298-
Set<RequestMethod> requestMethods = new HashSet<>(Arrays.asList(routerOperation.getMethods()));
299308
io.swagger.v3.oas.annotations.Operation apiOperation = routerOperation.getOperation();
300309
String[] methodConsumes = routerOperation.getConsumes();
301310
String[] methodProduces = routerOperation.getProduces();
@@ -309,12 +318,17 @@ protected void calculatePath(RouterOperation routerOperation) {
309318
PathItem pathItem = paths.get(operationPath);
310319
operationMap = pathItem.readOperationsMap();
311320
}
312-
for (RequestMethod requestMethod : requestMethods) {
321+
for (RequestMethod requestMethod : routerOperation.getMethods()) {
313322
Operation existingOperation = getExistingOperation(operationMap, requestMethod);
314323
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces, headers);
315324
methodAttributes.setMethodOverloaded(existingOperation != null);
316-
Operation operation = (existingOperation != null) ? existingOperation : new Operation();
317-
openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);
325+
Operation operation = getOperation(routerOperation, existingOperation);
326+
if (apiOperation != null)
327+
openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);
328+
329+
String operationId = operationParser.getOperationId(operation.getOperationId(),openAPI);
330+
operation.setOperationId(operationId);
331+
318332
fillParametersList(operation, queryParams, methodAttributes);
319333
if (!CollectionUtils.isEmpty(operation.getParameters()))
320334
operation.getParameters().forEach(parameter -> {
@@ -331,7 +345,7 @@ protected void calculatePath(RouterOperation routerOperation) {
331345

332346
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
333347
Set<RequestMethod> requestMethods) {
334-
this.calculatePath(handlerMethod, new RouterOperation(operationPath,requestMethods.toArray(new RequestMethod[requestMethods.size()])));
348+
this.calculatePath(handlerMethod, new RouterOperation(operationPath, requestMethods.toArray(new RequestMethod[requestMethods.size()])));
335349
}
336350

337351
protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVisitor routerFunctionVisitor) {
@@ -415,7 +429,7 @@ protected boolean isHiddenRestControllers(Class<?> rawClass) {
415429
return HIDDEN_REST_CONTROLLERS.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass));
416430
}
417431

418-
protected Set getDefaultAllowedHttpMethods() {
432+
protected Set<RequestMethod> getDefaultAllowedHttpMethods() {
419433
RequestMethod[] allowedRequestMethods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.HEAD };
420434
return new HashSet<>(Arrays.asList(allowedRequestMethods));
421435
}
@@ -550,7 +564,7 @@ private void fillParametersList(Operation operation, Map<String, String> queryPa
550564
List<Parameter> parametersList = operation.getParameters();
551565
if (parametersList == null)
552566
parametersList = new ArrayList<>();
553-
Collection headersMap = AbstractRequestBuilder.getHeaders(methodAttributes, new LinkedHashMap<>());
567+
Collection<Parameter> headersMap = AbstractRequestBuilder.getHeaders(methodAttributes, new LinkedHashMap<>());
554568
parametersList.addAll(headersMap);
555569
if (!CollectionUtils.isEmpty(queryParams)) {
556570
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
@@ -652,4 +666,22 @@ private Operation getExistingOperation(Map<HttpMethod, Operation> operationMap,
652666
return existingOperation;
653667
}
654668

669+
private Operation getOperation(RouterOperation routerOperation, Operation existingOperation) {
670+
Operation operationModel = routerOperation.getOperationModel();
671+
Operation operation;
672+
if (existingOperation != null && operationModel == null) {
673+
operation = existingOperation;
674+
}
675+
else if (existingOperation == null && operationModel != null) {
676+
operation = operationModel;
677+
}
678+
else if (existingOperation != null) {
679+
operation = operationParser.mergeOperation(existingOperation, operationModel);
680+
}
681+
else {
682+
operation = new Operation();
683+
}
684+
return operation;
685+
}
686+
655687
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585

8686
public abstract class AbstractRequestBuilder {
8787

88-
private static final List<Class> PARAM_TYPES_TO_IGNORE = new ArrayList<>();
88+
private static final List<Class<?>> PARAM_TYPES_TO_IGNORE = new ArrayList<>();
8989

9090
// using string litterals to support both validation-api v1 and v2
9191
private static final String[] ANNOTATIONS_FOR_REQUIRED = { NotNull.class.getName(), "javax.validation.constraints.NotBlank", "javax.validation.constraints.NotEmpty" };
@@ -144,7 +144,7 @@ public static void addRequestWrapperToIgnore(Class<?>... classes) {
144144
}
145145

146146
public static void removeRequestWrapperToIgnore(Class<?>... classes) {
147-
List classesToIgnore = Arrays.asList(classes);
147+
List<Class<?>> classesToIgnore = Arrays.asList(classes);
148148
if (PARAM_TYPES_TO_IGNORE.containsAll(classesToIgnore))
149149
PARAM_TYPES_TO_IGNORE.removeAll(Arrays.asList(classes));
150150
}
@@ -256,7 +256,7 @@ protected Parameter customiseParameter(Parameter parameter, ParameterInfo parame
256256
return parameter;
257257
}
258258

259-
protected boolean isParamToIgnore(MethodParameter parameter) {
259+
public boolean isParamToIgnore(MethodParameter parameter) {
260260
if (parameterBuilder.isAnnotationToIgnore(parameter))
261261
return true;
262262
if ((parameter.getParameterAnnotation(PathVariable.class) != null && parameter.getParameterAnnotation(PathVariable.class).required())
@@ -273,11 +273,11 @@ private void setParams(Operation operation, List<Parameter> operationParameters,
273273
operation.setRequestBody(requestBodyInfo.getRequestBody());
274274
}
275275

276-
private boolean isValidParameter(Parameter parameter) {
276+
public boolean isValidParameter(Parameter parameter) {
277277
return parameter != null && (parameter.getName() != null || parameter.get$ref() != null);
278278
}
279279

280-
private Parameter buildParams(ParameterInfo parameterInfo, Components components,
280+
public Parameter buildParams(ParameterInfo parameterInfo, Components components,
281281
RequestMethod requestMethod, JsonView jsonView) {
282282
MethodParameter methodParameter = parameterInfo.getMethodParameter();
283283
RequestHeader requestHeader = parameterInfo.getRequestHeader();
@@ -363,7 +363,7 @@ private Parameter buildParam(String in, Components components, ParameterInfo par
363363
return parameter;
364364
}
365365

366-
private void applyBeanValidatorAnnotations(final Parameter parameter, final List<Annotation> annotations) {
366+
public void applyBeanValidatorAnnotations(final Parameter parameter, final List<Annotation> annotations) {
367367
Map<String, Annotation> annos = new HashMap<>();
368368
if (annotations != null)
369369
annotations.forEach(annotation -> annos.put(annotation.annotationType().getName(), annotation));
@@ -374,7 +374,7 @@ private void applyBeanValidatorAnnotations(final Parameter parameter, final List
374374
applyValidationsToSchema(annos, schema);
375375
}
376376

377-
private void applyBeanValidatorAnnotations(final RequestBody requestBody, final List<Annotation> annotations, boolean isOptional) {
377+
public void applyBeanValidatorAnnotations(final RequestBody requestBody, final List<Annotation> annotations, boolean isOptional) {
378378
Map<String, Annotation> annos = new HashMap<>();
379379
boolean requestBodyRequired = false;
380380
if (!CollectionUtils.isEmpty(annotations)) {
@@ -389,7 +389,7 @@ private void applyBeanValidatorAnnotations(final RequestBody requestBody, final
389389
requestBody.setRequired(true);
390390
Content content = requestBody.getContent();
391391
for (MediaType mediaType : content.values()) {
392-
Schema schema = mediaType.getSchema();
392+
Schema<?> schema = mediaType.getSchema();
393393
applyValidationsToSchema(annos, schema);
394394
}
395395
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
/**
4444
* @author zarebski.m
4545
*/
46-
class DelegatingMethodParameter extends MethodParameter {
46+
public class DelegatingMethodParameter extends MethodParameter {
4747

4848
private MethodParameter delegate;
4949

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ private static void mergeParameter(Parameter paramCalcul, Parameter paramDoc) {
149149
paramDoc.setExplode(paramCalcul.getExplode());
150150
}
151151

152-
Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter parameterDoc,
152+
public Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter parameterDoc,
153153
Components components, JsonView jsonView) {
154154
Parameter parameter = new Parameter();
155155
if (StringUtils.isNotBlank(parameterDoc.description()))

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package org.springdoc.core;
2222

23+
import java.lang.annotation.Annotation;
2324
import java.lang.reflect.Method;
2425
import java.lang.reflect.ParameterizedType;
2526
import java.lang.reflect.Type;
@@ -234,13 +235,17 @@ private Set<io.swagger.v3.oas.annotations.responses.ApiResponse> getApiResponses
234235
}
235236

236237
private Content buildContent(Components components, MethodParameter methodParameter, String[] methodProduces, JsonView jsonView) {
237-
Content content = new Content();
238238
Type returnType = getReturnType(methodParameter);
239+
return buildContent(components, methodParameter.getParameterAnnotations(), methodProduces, jsonView, returnType);
240+
}
241+
242+
public Content buildContent(Components components, Annotation[] annotations, String[] methodProduces, JsonView jsonView, Type returnType) {
243+
Content content = new Content();
239244
// if void, no content
240245
if (isVoid(returnType))
241246
return null;
242247
if (ArrayUtils.isNotEmpty(methodProduces)) {
243-
Schema<?> schemaN = calculateSchema(components, returnType, jsonView);
248+
Schema<?> schemaN = calculateSchema(components, returnType, jsonView, annotations);
244249
if (schemaN != null) {
245250
io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType();
246251
mediaType.setSchema(schemaN);
@@ -264,8 +269,8 @@ private Type getReturnType(MethodParameter methodParameter) {
264269
return returnType;
265270
}
266271

267-
public Schema calculateSchema(Components components, Type returnType, JsonView jsonView) {
268-
return !isVoid(returnType) ? extractSchema(components, returnType, jsonView) : null;
272+
private Schema calculateSchema(Components components, Type returnType, JsonView jsonView, Annotation[] annotations) {
273+
return !isVoid(returnType) ? extractSchema(components, returnType, jsonView,annotations) : null;
269274
}
270275

271276
private void setContent(String[] methodProduces, Content content,
@@ -293,7 +298,7 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
293298
// Merge with existing schema
294299
Content existingContent = apiResponse.getContent();
295300
Schema<?> schemaN = calculateSchema(components, methodParameter.getGenericParameterType(),
296-
methodAttributes.getJsonViewAnnotation());
301+
methodAttributes.getJsonViewAnnotation(), methodParameter.getParameterAnnotations());
297302
if (schemaN != null && ArrayUtils.isNotEmpty(methodAttributes.getMethodProduces()))
298303
Arrays.stream(methodAttributes.getMethodProduces()).forEach(mediaTypeStr -> mergeSchema(existingContent, schemaN, mediaTypeStr));
299304
}
@@ -310,7 +315,7 @@ public static void setDescription(String httpCode, ApiResponse apiResponse) {
310315
}
311316
}
312317

313-
private String evaluateResponseStatus(Method method, Class<?> beanType, boolean isGeneric) {
318+
public String evaluateResponseStatus(Method method, Class<?> beanType, boolean isGeneric) {
314319
String responseStatus = null;
315320
ResponseStatus annotation = AnnotatedElementUtils.findMergedAnnotation(method, ResponseStatus.class);
316321
if (annotation == null && beanType != null)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ private OpenAPIDefinition getApiDefClass(ClassPathScanningCandidateComponentProv
403403
return null;
404404
}
405405

406-
private boolean isAutoTagClasses(Operation operation) {
406+
public boolean isAutoTagClasses(Operation operation) {
407407
return CollectionUtils.isEmpty(operation.getTags()) && springDocConfigProperties.isAutoTagClasses();
408408
}
409409

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.LinkedHashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Map.Entry;
3031
import java.util.Optional;
3132
import java.util.Set;
3233
import java.util.stream.Collectors;
@@ -40,6 +41,7 @@
4041
import io.swagger.v3.oas.models.callbacks.Callback;
4142
import io.swagger.v3.oas.models.headers.Header;
4243
import io.swagger.v3.oas.models.links.Link;
44+
import io.swagger.v3.oas.models.media.Content;
4345
import io.swagger.v3.oas.models.media.Schema;
4446
import io.swagger.v3.oas.models.parameters.Parameter;
4547
import io.swagger.v3.oas.models.responses.ApiResponse;
@@ -220,7 +222,7 @@ private void buildTags(io.swagger.v3.oas.annotations.Operation apiOperation, Ope
220222
}
221223
}
222224

223-
private String getOperationId(String operationId, OpenAPI openAPI) {
225+
public String getOperationId(String operationId, OpenAPI openAPI) {
224226
boolean operationIdUsed = existOperationId(operationId, openAPI);
225227
String operationIdToFind = null;
226228
int counter = 0;
@@ -417,4 +419,22 @@ public String getOperationId(String operationId, String oldOperationId, OpenAPI
417419
return this.getOperationId(operationId, openAPI);
418420
}
419421

422+
public Operation mergeOperation(Operation operation, Operation operationModel) {
423+
if (operationModel.getOperationId().length() < operation.getOperationId().length()) {
424+
operation.setOperationId(operationModel.getOperationId());
425+
}
426+
427+
ApiResponses apiResponses = operation.getResponses();
428+
for (Entry<String, ApiResponse> apiResponseEntry : operationModel.getResponses().entrySet()) {
429+
if (apiResponses.containsKey(apiResponseEntry.getKey())) {
430+
Content existingContent = apiResponses.get(apiResponseEntry.getKey()).getContent();
431+
Content newContent = apiResponseEntry.getValue().getContent();
432+
if (newContent != null)
433+
newContent.forEach((mediaTypeStr, mediaType) -> SpringDocAnnotationsUtils.mergeSchema(existingContent, mediaType.getSchema(), mediaTypeStr));
434+
}
435+
else
436+
apiResponses.addApiResponse(apiResponseEntry.getKey(), apiResponseEntry.getValue());
437+
}
438+
return operation;
439+
}
420440
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.springframework.web.bind.annotation.RequestHeader;
3131
import org.springframework.web.bind.annotation.RequestParam;
3232

33-
class ParameterInfo {
33+
public class ParameterInfo {
3434

3535
private final MethodParameter methodParameter;
3636

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.springdoc.core;
2+
3+
import java.util.List;
4+
5+
import io.swagger.v3.oas.models.OpenAPI;
6+
import org.springdoc.core.fn.RouterOperation;
7+
8+
public interface RepositoryRestResourceProvider {
9+
10+
List<RouterOperation> getRouterOperations(OpenAPI openAPI);
11+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import io.swagger.v3.oas.models.parameters.RequestBody;
2626

2727
@SuppressWarnings("rawtypes")
28-
class RequestBodyInfo {
28+
public class RequestBodyInfo {
2929

3030
private RequestBody requestBody;
3131

springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ResponseSupportConverter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
4646
if (innerType == null)
4747
return new StringSchema();
4848
else if (innerType.getBindings() != null && isResponseTypeWrapper(innerType.getRawClass())) {
49-
type = new AnnotatedType(innerType).jsonViewAnnotation(type.getJsonViewAnnotation()).resolveAsRef(true);
49+
type = new AnnotatedType(innerType).jsonViewAnnotation(type.getJsonViewAnnotation()).ctxAnnotations(type.getCtxAnnotations()).resolveAsRef(true);
5050
return this.resolve(type, context, chain);
5151
}
5252
else
53-
type = new AnnotatedType(innerType).jsonViewAnnotation(type.getJsonViewAnnotation()).resolveAsRef(true);
53+
type = new AnnotatedType(innerType).jsonViewAnnotation(type.getJsonViewAnnotation()).ctxAnnotations((type.getCtxAnnotations())).resolveAsRef(true);
5454
}
5555
else if (isResponseTypeToIgnore(cls))
5656
return null;

0 commit comments

Comments
 (0)