Skip to content

Commit

Permalink
issues with spring data rest and @manytoone relationships. Fixes #792.
Browse files Browse the repository at this point in the history
  • Loading branch information
bnasslahsen committed Aug 26, 2020
1 parent 336fa8f commit 13e3bfe
Show file tree
Hide file tree
Showing 16 changed files with 1,456 additions and 1,284 deletions.
Expand Up @@ -25,6 +25,7 @@

import java.util.Optional;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.querydsl.core.types.Predicate;
import org.springdoc.core.AbstractRequestBuilder;
import org.springdoc.core.GenericParameterBuilder;
Expand Down Expand Up @@ -53,6 +54,7 @@
import org.springframework.context.annotation.Primary;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
Expand Down Expand Up @@ -157,15 +159,18 @@ static class SpringRepositoryRestResourceProviderConfiguration {
* @param associations the associations
* @param delegatingHandlerMapping the delegating handler mapping
* @param dataRestRouterOperationBuilder the data rest router operation builder
* @param persistentEntities the persistent entities
* @param mapper the mapper
* @return the spring repository rest resource provider
*/
@Bean
@ConditionalOnMissingBean
SpringRepositoryRestResourceProvider springRepositoryRestResourceProvider(ResourceMappings mappings,
Repositories repositories, Associations associations, DelegatingHandlerMapping delegatingHandlerMapping,
DataRestRouterOperationBuilder dataRestRouterOperationBuilder) {
return new SpringRepositoryRestResourceProvider(mappings, repositories, associations,
delegatingHandlerMapping, dataRestRouterOperationBuilder);
DataRestRouterOperationBuilder dataRestRouterOperationBuilder, PersistentEntities persistentEntities,
ObjectMapper mapper) {
return new SpringRepositoryRestResourceProvider(mappings,repositories, associations, delegatingHandlerMapping,
dataRestRouterOperationBuilder, persistentEntities, mapper);
}

/**
Expand Down
Expand Up @@ -29,13 +29,19 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.models.OpenAPI;
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.core.RepositoryRestResourceProvider;
import org.springdoc.core.fn.RouterOperation;
import org.springdoc.data.rest.core.ControllerType;
import org.springdoc.data.rest.core.DataRestRepository;
import org.springdoc.data.rest.core.DataRestRouterOperationBuilder;

import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
Expand All @@ -45,6 +51,7 @@
import org.springframework.data.rest.webmvc.ProfileController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.alps.AlpsController;
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
import org.springframework.web.method.HandlerMethod;
Expand Down Expand Up @@ -102,6 +109,16 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou
*/
private DataRestRouterOperationBuilder dataRestRouterOperationBuilder;

/**
* The Persistent entities.
*/
private PersistentEntities persistentEntities;

/**
* The Mapper.
*/
private ObjectMapper mapper;

/**
* Instantiates a new Spring repository rest resource provider.
*
Expand All @@ -110,14 +127,17 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou
* @param associations the associations
* @param delegatingHandlerMapping the delegating handler mapping
* @param dataRestRouterOperationBuilder the data rest router operation builder
* @param persistentEntities the persistent entities
* @param mapper the mapper
*/
public SpringRepositoryRestResourceProvider(ResourceMappings mappings, Repositories repositories, Associations associations,
DelegatingHandlerMapping delegatingHandlerMapping, DataRestRouterOperationBuilder dataRestRouterOperationBuilder) {
public SpringRepositoryRestResourceProvider(ResourceMappings mappings, Repositories repositories, Associations associations, DelegatingHandlerMapping delegatingHandlerMapping, DataRestRouterOperationBuilder dataRestRouterOperationBuilder, PersistentEntities persistentEntities, ObjectMapper mapper) {
this.mappings = mappings;
this.repositories = repositories;
this.associations = associations;
this.delegatingHandlerMapping = delegatingHandlerMapping;
this.dataRestRouterOperationBuilder = dataRestRouterOperationBuilder;
this.persistentEntities = persistentEntities;
this.mapper = mapper;
}

public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
Expand All @@ -127,18 +147,38 @@ public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
Class<?> repository = repositories.getRequiredRepositoryInformation(domainType).getRepositoryInterface();
DataRestRepository dataRestRepository = new DataRestRepository(domainType, repository);
ResourceMetadata resourceMetadata = mappings.getMetadataFor(domainType);
final PersistentEntity<?, ?> entity = persistentEntities.getRequiredPersistentEntity(domainType);
final JacksonMetadata jackson = new JacksonMetadata(mapper, domainType);

if (resourceMetadata.isExported()) {
for (HandlerMapping handlerMapping : handlerMappingList) {
if (handlerMapping instanceof RepositoryRestHandlerMapping) {
RepositoryRestHandlerMapping repositoryRestHandlerMapping = (RepositoryRestHandlerMapping) handlerMapping;
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = repositoryRestHandlerMapping.getHandlerMethods();
// Entity controllers lookup first
Map<RequestMappingInfo, HandlerMethod> handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
.filter(requestMappingInfoHandlerMethodEntry -> REPOSITORY_ENTITY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
.getValue().getBeanType().getName()) || REPOSITORY_PROPERTY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
.getValue().getBeanType().getName()))
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
dataRestRepository.setControllerType(ControllerType.ENTITY);
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);

Map<RequestMappingInfo, HandlerMethod> handlerMethodMapFilteredMethodMap = handlerMethodMap.entrySet().stream()
.filter(requestMappingInfoHandlerMethodEntry -> REPOSITORY_PROPERTY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
.getValue().getBeanType().getName()))
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));

entity.doWithAssociations((SimpleAssociationHandler) association -> {
PersistentProperty<?> property = association.getInverse();
if (jackson.isExported(property) && associations.isLinkableAssociation(property)) {
ResourceMetadata targetTypeMetadata = associations.getMetadataFor(property.getActualType());
dataRestRepository.setRelationName(targetTypeMetadata.getItemResourceRel().toString());
dataRestRepository.setControllerType(ControllerType.PROPERTY);
findControllers(routerOperationList, handlerMethodMapFilteredMethodMap, resourceMetadata, dataRestRepository, openAPI);
}
});
}
else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
BasePathAwareHandlerMapping beanBasePathAwareHandlerMapping = (BasePathAwareHandlerMapping) handlerMapping;
Expand All @@ -148,7 +188,7 @@ else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
.getValue().getBeanType().getName()))
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));

dataRestRepository.setControllerType(ControllerType.SCHEMA);
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);
handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
.filter(requestMappingInfoHandlerMethodEntry -> ProfileController.class.equals(requestMappingInfoHandlerMethodEntry
Expand Down
Expand Up @@ -27,13 +27,21 @@
* The enum Controller type.
* @author bnasslahsen
*/
enum ControllerType {
public enum ControllerType {
/**
*Entity controller type.
*/
ENTITY,
/**
*Search controller type.
*/
SEARCH
SEARCH,
/**
*Schema controller type.
*/
SCHEMA,
/**
*PROPERTY controller type.
*/
PROPERTY
}
Expand Up @@ -111,7 +111,9 @@ public Operation buildOperation(HandlerMethod handlerMethod, DataRestRepository
OpenAPI openAPI, RequestMethod requestMethod, String operationPath, MethodAttributes methodAttributes,
ResourceMetadata resourceMetadata, MethodResourceMapping methodResourceMapping, ControllerType controllerType) {
Operation operation = null;
if (ControllerType.ENTITY.equals(controllerType)) {
if (ControllerType.ENTITY.equals(controllerType)
|| ControllerType.PROPERTY.equals(controllerType)
|| ControllerType.SCHEMA.equals(controllerType)) {
operation = buildEntityOperation(handlerMethod, dataRestRepository,
openAPI, requestMethod, operationPath, methodAttributes, resourceMetadata);
}
Expand Down
Expand Up @@ -39,6 +39,16 @@ public class DataRestRepository {
*/
private Class<?> repositoryType;

/**
* The Relation name.
*/
private String relationName;

/**
* The Controller type.
*/
private ControllerType controllerType;

/**
* Instantiates a new Data rest repository.
*
Expand Down Expand Up @@ -85,4 +95,40 @@ public Class<?> getRepositoryType() {
public void setRepositoryType(Class<?> repositoryType) {
this.repositoryType = repositoryType;
}

/**
* Gets relation name.
*
* @return the relation name
*/
public String getRelationName() {
return relationName;
}

/**
* Sets relation name.
*
* @param relationName the relation name
*/
public void setRelationName(String relationName) {
this.relationName = relationName;
}

/**
* Gets controller type.
*
* @return the controller type
*/
public ControllerType getControllerType() {
return controllerType;
}

/**
* Sets controller type.
*
* @param controllerType the controller type
*/
public void setControllerType(ControllerType controllerType) {
this.controllerType = controllerType;
}
}
Expand Up @@ -172,7 +172,9 @@ else if (methodParameter.getParameterAnnotation(BackendId.class) != null) {
* @return the boolean
*/
private boolean isParamToIgnore(MethodParameter methodParameter) {
return !requestBuilder.isParamToIgnore(methodParameter) && !isHeaderToIgnore(methodParameter);
return !requestBuilder.isParamToIgnore(methodParameter)
&& !isHeaderToIgnore(methodParameter)
&& !"property".equals(methodParameter.getParameterName());
}

/**
Expand Down
Expand Up @@ -113,8 +113,9 @@ public void buildEntityRouterOperationList(List<RouterOperation> routerOperation
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
DataRestRepository dataRestRepository, OpenAPI openAPI) {
String path = resourceMetadata.getPath().toString();
ControllerType controllerType = (dataRestRepository == null) ? ControllerType.SCHEMA : dataRestRepository.getControllerType();
for (Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
buildRouterOperationList(routerOperationList, resourceMetadata, dataRestRepository, openAPI, path, entry, null, ControllerType.ENTITY, null);
buildRouterOperationList(routerOperationList, resourceMetadata, dataRestRepository, openAPI, path, entry, null, controllerType, null);
}
}

Expand Down Expand Up @@ -150,23 +151,25 @@ public void buildSearchRouterOperationList(List<RouterOperation> routerOperation
* @param path the path
* @param entry the entry
* @param subPath the sub path
* @param entity the entity
* @param controllerType the controllerType
* @param methodResourceMapping the method resource mapping
*/
private void buildRouterOperationList(List<RouterOperation> routerOperationList, ResourceMetadata resourceMetadata,
DataRestRepository dataRestRepository, OpenAPI openAPI, String path, Entry<RequestMappingInfo, HandlerMethod> entry,
String subPath, ControllerType entity, MethodResourceMapping methodResourceMapping) {
String subPath, ControllerType controllerType, MethodResourceMapping methodResourceMapping) {
RequestMappingInfo requestMappingInfo = entry.getKey();
HandlerMethod handlerMethod = entry.getValue();
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
if (resourceMetadata != null) {
HttpMethods httpMethodsItem = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.ITEM);
Set<RequestMethod> requestMethodsItem = requestMethods.stream().filter(requestMethod -> httpMethodsItem.contains(HttpMethod.valueOf(requestMethod.toString())))
.collect(Collectors.toSet());
HttpMethods httpMethodsCollection = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.COLLECTION);
Set<RequestMethod> requestMethodsCollection = requestMethods.stream().filter(requestMethod -> httpMethodsCollection.contains(HttpMethod.valueOf(requestMethod.toString())))
.collect(Collectors.toSet());
requestMethodsItem.addAll(requestMethodsCollection);
if (!ControllerType.PROPERTY.equals(controllerType)) {
HttpMethods httpMethodsCollection = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.COLLECTION);
Set<RequestMethod> requestMethodsCollection = requestMethods.stream().filter(requestMethod -> httpMethodsCollection.contains(HttpMethod.valueOf(requestMethod.toString())))
.collect(Collectors.toSet());
requestMethodsItem.addAll(requestMethodsCollection);
}
requestMethods = requestMethodsItem;
}

Expand All @@ -176,9 +179,11 @@ private void buildRouterOperationList(List<RouterOperation> routerOperationList,
PatternsRequestCondition patternsRequestCondition = requestMappingInfo.getPatternsCondition();
Set<String> patterns = patternsRequestCondition.getPatterns();
Map<String, String> regexMap = new LinkedHashMap<>();
String operationPath = calculateOperationPath(path, subPath, patterns, regexMap, entity);
String operationPath = calculateOperationPath(path, subPath, patterns, regexMap, controllerType);
if (ControllerType.PROPERTY.equals(controllerType))
operationPath = operationPath.replace("{property}", dataRestRepository.getRelationName());
buildRouterOperation(routerOperationList, dataRestRepository, openAPI, methodResourceMapping,
handlerMethod, requestMethod, resourceMetadata, operationPath, entity);
handlerMethod, requestMethod, resourceMetadata, operationPath, controllerType);
}
}
}
Expand Down
@@ -0,0 +1,35 @@
package test.org.springdoc.api.app17;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Data;

@Entity
public @Data
class ChildProperty {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

private String name;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,6 @@
package test.org.springdoc.api.app17;

import org.springframework.data.repository.PagingAndSortingRepository;

public interface ChildPropertyRepository extends PagingAndSortingRepository<ChildProperty, Long> {
}

0 comments on commit 13e3bfe

Please sign in to comment.