Skip to content

Commit

Permalink
Add support of swagger annotations @securityrequirement and @tag on @…
Browse files Browse the repository at this point in the history
…repository interface. Fixes #837.
  • Loading branch information
bnasslahsen committed Aug 26, 2020
1 parent 9ecb59e commit a382dd3
Show file tree
Hide file tree
Showing 14 changed files with 2,116 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package org.springdoc.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -253,33 +254,11 @@ public void setServersPresent(boolean serversPresent) {
*/
public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI) {

// class tags
Set<Tags> tagsSet = AnnotatedElementUtils
.findAllMergedAnnotations(handlerMethod.getBeanType(), Tags.class);
Set<Tag> classTags = tagsSet.stream()
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
classTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(handlerMethod.getBeanType(), Tag.class));

// method tags
tagsSet = AnnotatedElementUtils
.findAllMergedAnnotations(handlerMethod.getMethod(), Tags.class);
Set<Tag> methodTags = tagsSet.stream()
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(handlerMethod.getMethod(), Tag.class));


List<Tag> allTags = new ArrayList<>();
Set<io.swagger.v3.oas.models.tags.Tag> tags = new HashSet<>();
Set<String> tagsStr = new HashSet<>();

if (!CollectionUtils.isEmpty(methodTags)) {
tagsStr.addAll(methodTags.stream().map(Tag::name).collect(Collectors.toSet()));
allTags.addAll(methodTags);
}

if (!CollectionUtils.isEmpty(classTags)) {
tagsStr.addAll(classTags.stream().map(Tag::name).collect(Collectors.toSet()));
allTags.addAll(classTags);
}
buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr);
buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr);

if (springdocTags.containsKey(handlerMethod)) {
io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
Expand All @@ -289,16 +268,18 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope
}
}

Optional<Set<io.swagger.v3.oas.models.tags.Tag>> tags = AnnotationsUtils
.getTags(allTags.toArray(new Tag[0]), true);
if (!CollectionUtils.isEmpty(tagsStr))
operation.setTags(new ArrayList<>(tagsStr));

if (isAutoTagClasses(operation))
operation.addTagsItem(splitCamelCase(handlerMethod.getBeanType().getSimpleName()));

if (tags.isPresent()) {
Set<io.swagger.v3.oas.models.tags.Tag> tagSet = tags.get();
if (!CollectionUtils.isEmpty(tags)) {
// Existing tags
List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
if (!CollectionUtils.isEmpty(openApiTags))
tagSet.addAll(openApiTags);
openAPI.setTags(new ArrayList<>(tagSet));
tags.addAll(openApiTags);
openAPI.setTags(new ArrayList<>(tags));
}

// Handle SecurityRequirement at operation level
Expand All @@ -310,14 +291,39 @@ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, Ope
else
securityParser.buildSecurityRequirement(securityRequirements, operation);
}
if (!CollectionUtils.isEmpty(tagsStr))
operation.setTags(new ArrayList<>(tagsStr));

return operation;
}

if (isAutoTagClasses(operation))
operation.addTagsItem(splitCamelCase(handlerMethod.getBeanType().getSimpleName()));
private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr) {
// method tags
Set<Tags> tagsSet = AnnotatedElementUtils
.findAllMergedAnnotations(method, Tags.class);
Set<Tag> methodTags = tagsSet.stream()
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, Tag.class));
if (!CollectionUtils.isEmpty(methodTags)) {
tagsStr.addAll(methodTags.stream().map(Tag::name).collect(Collectors.toSet()));
List<Tag> allTags = new ArrayList<>(methodTags);
AnnotationsUtils
.getTags(allTags.toArray(new Tag[0]), true).ifPresent(tags::addAll);
}
}

return operation;
public void buildTagsFromClass(Class<?> beanType, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr) {
List<Tag> allTags = new ArrayList<>();
// class tags
Set<Tags> tagsSet = AnnotatedElementUtils
.findAllMergedAnnotations(beanType, Tags.class);
Set<Tag> classTags = tagsSet.stream()
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
classTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(beanType, Tag.class));
if (!CollectionUtils.isEmpty(classTags)) {
tagsStr.addAll(classTags.stream().map(Tag::name).collect(Collectors.toSet()));
allTags.addAll(classTags);
AnnotationsUtils
.getTags(allTags.toArray(new Tag[0]), true).ifPresent(tags::addAll);
}
}

/**
Expand Down Expand Up @@ -654,4 +660,8 @@ public void resetCalculatedOpenAPI() {
public ApplicationContext getContext() {
return context;
}

public SecurityParser getSecurityParser() {
return securityParser;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package org.springdoc.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
Expand Down Expand Up @@ -47,7 +48,7 @@
* The type Security parser.
* @author bnasslahsen
*/
class SecurityParser {
public class SecurityParser {

/**
* The Property resolver utils.
Expand Down Expand Up @@ -111,38 +112,50 @@ private static boolean isEmpty(OAuthScope[] scopes) {
/**
* Get security requirements io . swagger . v 3 . oas . annotations . security . security requirement [ ].
*
* @param method the method
* @param handlerMethod the handlerMethod
* @return the io . swagger . v 3 . oas . annotations . security . security requirement [ ]
*/
public io.swagger.v3.oas.annotations.security.SecurityRequirement[] getSecurityRequirements(
HandlerMethod method) {
HandlerMethod handlerMethod) {
// class SecurityRequirements
io.swagger.v3.oas.annotations.security.SecurityRequirements classSecurity = AnnotatedElementUtils.findMergedAnnotation(method.getBeanType(), io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
// method SecurityRequirements
io.swagger.v3.oas.annotations.security.SecurityRequirements methodSecurity = AnnotatedElementUtils.findMergedAnnotation(method.getMethod(), io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
Class<?> beanType = handlerMethod.getBeanType();
Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags = getSecurityRequirementsForClass(beanType);

Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags = null;
// handlerMethod SecurityRequirements
Method method = handlerMethod.getMethod();
allSecurityTags = getSecurityRequirementsForMethod(method,allSecurityTags);

if (classSecurity != null)
allSecurityTags = new HashSet<>(Arrays.asList(classSecurity.value()));
return (allSecurityTags != null) ? allSecurityTags.toArray(new io.swagger.v3.oas.annotations.security.SecurityRequirement[0]) : null;
}

private Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> getSecurityRequirementsForMethod(Method method,Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags) {
io.swagger.v3.oas.annotations.security.SecurityRequirements methodSecurity = AnnotatedElementUtils.findMergedAnnotation(method, io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
if (methodSecurity != null)
allSecurityTags = addSecurityRequirements(allSecurityTags, new HashSet<>(Arrays.asList(methodSecurity.value())));
if (CollectionUtils.isEmpty(allSecurityTags)) {
// handlerMethod SecurityRequirement
Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsMethodList = AnnotatedElementUtils.findMergedRepeatableAnnotations(method,
io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
if (!CollectionUtils.isEmpty(securityRequirementsMethodList))
allSecurityTags = addSecurityRequirements(allSecurityTags, securityRequirementsMethodList);
}
return allSecurityTags;
}

public Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> getSecurityRequirementsForClass(Class<?> beanType) {
Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags =null;
io.swagger.v3.oas.annotations.security.SecurityRequirements classSecurity = AnnotatedElementUtils.findMergedAnnotation(beanType, io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
if (classSecurity != null)
allSecurityTags = new HashSet<>(Arrays.asList(classSecurity.value()));
if (CollectionUtils.isEmpty(allSecurityTags)) {
// class SecurityRequirement
Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsClassList = AnnotatedElementUtils.findMergedRepeatableAnnotations(
method.getBeanType(),
io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
// method SecurityRequirement
Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsMethodList = AnnotatedElementUtils.findMergedRepeatableAnnotations(method.getMethod(),
beanType,
io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
if (!CollectionUtils.isEmpty(securityRequirementsClassList))
allSecurityTags = addSecurityRequirements(allSecurityTags, securityRequirementsClassList);
if (!CollectionUtils.isEmpty(securityRequirementsMethodList))
allSecurityTags = addSecurityRequirements(allSecurityTags, securityRequirementsMethodList);
}

return (allSecurityTags != null) ? allSecurityTags.toArray(new io.swagger.v3.oas.annotations.security.SecurityRequirement[0]) : null;
return allSecurityTags;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.core.RepositoryRestResourceProvider;
import org.springdoc.core.fn.RouterOperation;
import org.springdoc.data.rest.core.DataRestRepository;
import org.springdoc.data.rest.core.DataRestRouterOperationBuilder;

import org.springframework.data.repository.support.Repositories;
Expand Down Expand Up @@ -123,6 +124,8 @@ public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
List<RouterOperation> routerOperationList = new ArrayList<>();
List<HandlerMapping> handlerMappingList = delegatingHandlerMapping.getDelegates();
for (Class<?> domainType : repositories) {
Class<?> repository = repositories.getRequiredRepositoryInformation(domainType).getRepositoryInterface();
DataRestRepository dataRestRepository = new DataRestRepository(domainType, repository);
ResourceMetadata resourceMetadata = mappings.getMetadataFor(domainType);
if (resourceMetadata.isExported()) {
for (HandlerMapping handlerMapping : handlerMappingList) {
Expand All @@ -135,9 +138,9 @@ public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
.getValue().getBeanType().getName()))
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, domainType, openAPI);
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);
}
else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
BasePathAwareHandlerMapping beanBasePathAwareHandlerMapping = (BasePathAwareHandlerMapping) handlerMapping;
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = beanBasePathAwareHandlerMapping.getHandlerMethods();
Map<RequestMappingInfo, HandlerMethod> handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
Expand All @@ -146,7 +149,7 @@ else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));

findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, domainType, openAPI);
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);
handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
.filter(requestMappingInfoHandlerMethodEntry -> ProfileController.class.equals(requestMappingInfoHandlerMethodEntry
.getValue().getBeanType()) || AlpsController.class.equals(requestMappingInfoHandlerMethodEntry
Expand All @@ -157,7 +160,7 @@ else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
}
}
// search
findSearchResourceMappings(openAPI, routerOperationList, handlerMappingList, domainType, resourceMetadata);
findSearchResourceMappings(openAPI, routerOperationList, handlerMappingList, dataRestRepository, resourceMetadata);
}
return routerOperationList;
}
Expand All @@ -168,10 +171,10 @@ else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
* @param openAPI the open api
* @param routerOperationList the router operation list
* @param handlerMappingList the handler mapping list
* @param domainType the domain type
* @param dataRestRepository the repository data rest
* @param resourceMetadata the resource metadata
*/
private void findSearchResourceMappings(OpenAPI openAPI, List<RouterOperation> routerOperationList, List<HandlerMapping> handlerMappingList, Class<?> domainType, ResourceMetadata resourceMetadata) {
private void findSearchResourceMappings(OpenAPI openAPI, List<RouterOperation> routerOperationList, List<HandlerMapping> handlerMappingList, DataRestRepository dataRestRepository, ResourceMetadata resourceMetadata) {
for (HandlerMapping handlerMapping : handlerMappingList) {
if (handlerMapping instanceof RepositoryRestHandlerMapping) {
RepositoryRestHandlerMapping repositoryRestHandlerMapping = (RepositoryRestHandlerMapping) handlerMapping;
Expand All @@ -181,10 +184,10 @@ private void findSearchResourceMappings(OpenAPI openAPI, List<RouterOperation> r
.getValue().getBeanType().getName()))
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
ResourceMetadata metadata = associations.getMetadataFor(domainType);
ResourceMetadata metadata = associations.getMetadataFor(dataRestRepository.getDomainType());
SearchResourceMappings searchResourceMappings = metadata.getSearchResourceMappings();
if (searchResourceMappings.isExported()) {
findSearchControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, domainType, openAPI, searchResourceMappings);
findSearchControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI, searchResourceMappings);
}
}
}
Expand All @@ -196,16 +199,16 @@ private void findSearchResourceMappings(OpenAPI openAPI, List<RouterOperation> r
* @param routerOperationList the router operation list
* @param handlerMethodMap the handler method map
* @param resourceMetadata the resource metadata
* @param domainType the domain type
* @param dataRestRepository the repository data rest
* @param openAPI the open api
* @param searchResourceMappings the search resource mappings
* @return the list
*/
private List<RouterOperation> findSearchControllers(List<RouterOperation> routerOperationList,
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata, Class<?> domainType, OpenAPI openAPI, SearchResourceMappings searchResourceMappings) {
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata, DataRestRepository dataRestRepository, OpenAPI openAPI, SearchResourceMappings searchResourceMappings) {
Stream<MethodResourceMapping> methodResourceMappingStream = searchResourceMappings.getExportedMappings();
methodResourceMappingStream.forEach(methodResourceMapping -> dataRestRouterOperationBuilder.buildSearchRouterOperationList(
routerOperationList, handlerMethodMap, resourceMetadata, domainType, openAPI, methodResourceMapping));
routerOperationList, handlerMethodMap, resourceMetadata, dataRestRepository, openAPI, methodResourceMapping));
return routerOperationList;
}

Expand All @@ -216,16 +219,16 @@ private List<RouterOperation> findSearchControllers(List<RouterOperation> router
* @param routerOperationList the router operation list
* @param handlerMethodMap the handler method map
* @param resourceMetadata the resource metadata
* @param domainType the domain type
* @param dataRestRepository the repository data rest
* @param openAPI the open api
* @return the list
*/
private List<RouterOperation> findControllers
(List<RouterOperation> routerOperationList,
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
Class<?> domainType, OpenAPI openAPI) {
(List<RouterOperation> routerOperationList,
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
DataRestRepository dataRestRepository, OpenAPI openAPI) {
dataRestRouterOperationBuilder.buildEntityRouterOperationList(routerOperationList, handlerMethodMap, resourceMetadata,
domainType, openAPI);
dataRestRepository, openAPI);
return routerOperationList;
}

Expand Down

0 comments on commit a382dd3

Please sign in to comment.