Skip to content

Commit 13e3bfe

Browse files
author
bnasslahsen
committed
issues with spring data rest and @manytoone relationships. Fixes #792.
1 parent 336fa8f commit 13e3bfe

16 files changed

+1456
-1284
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.util.Optional;
2727

28+
import com.fasterxml.jackson.databind.ObjectMapper;
2829
import com.querydsl.core.types.Predicate;
2930
import org.springdoc.core.AbstractRequestBuilder;
3031
import org.springdoc.core.GenericParameterBuilder;
@@ -53,6 +54,7 @@
5354
import org.springframework.context.annotation.Primary;
5455
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
5556
import org.springframework.data.domain.Sort;
57+
import org.springframework.data.mapping.context.PersistentEntities;
5658
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
5759
import org.springframework.data.repository.support.Repositories;
5860
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
@@ -157,15 +159,18 @@ static class SpringRepositoryRestResourceProviderConfiguration {
157159
* @param associations the associations
158160
* @param delegatingHandlerMapping the delegating handler mapping
159161
* @param dataRestRouterOperationBuilder the data rest router operation builder
162+
* @param persistentEntities the persistent entities
163+
* @param mapper the mapper
160164
* @return the spring repository rest resource provider
161165
*/
162166
@Bean
163167
@ConditionalOnMissingBean
164168
SpringRepositoryRestResourceProvider springRepositoryRestResourceProvider(ResourceMappings mappings,
165169
Repositories repositories, Associations associations, DelegatingHandlerMapping delegatingHandlerMapping,
166-
DataRestRouterOperationBuilder dataRestRouterOperationBuilder) {
167-
return new SpringRepositoryRestResourceProvider(mappings, repositories, associations,
168-
delegatingHandlerMapping, dataRestRouterOperationBuilder);
170+
DataRestRouterOperationBuilder dataRestRouterOperationBuilder, PersistentEntities persistentEntities,
171+
ObjectMapper mapper) {
172+
return new SpringRepositoryRestResourceProvider(mappings,repositories, associations, delegatingHandlerMapping,
173+
dataRestRouterOperationBuilder, persistentEntities, mapper);
169174
}
170175

171176
/**

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@
2929
import java.util.stream.Collectors;
3030
import java.util.stream.Stream;
3131

32+
import com.fasterxml.jackson.databind.ObjectMapper;
3233
import io.swagger.v3.oas.models.OpenAPI;
3334
import org.springdoc.api.AbstractOpenApiResource;
3435
import org.springdoc.core.RepositoryRestResourceProvider;
3536
import org.springdoc.core.fn.RouterOperation;
37+
import org.springdoc.data.rest.core.ControllerType;
3638
import org.springdoc.data.rest.core.DataRestRepository;
3739
import org.springdoc.data.rest.core.DataRestRouterOperationBuilder;
3840

41+
import org.springframework.data.mapping.PersistentEntity;
42+
import org.springframework.data.mapping.PersistentProperty;
43+
import org.springframework.data.mapping.SimpleAssociationHandler;
44+
import org.springframework.data.mapping.context.PersistentEntities;
3945
import org.springframework.data.repository.support.Repositories;
4046
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
4147
import org.springframework.data.rest.core.mapping.ResourceMappings;
@@ -45,6 +51,7 @@
4551
import org.springframework.data.rest.webmvc.ProfileController;
4652
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
4753
import org.springframework.data.rest.webmvc.alps.AlpsController;
54+
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
4855
import org.springframework.data.rest.webmvc.mapping.Associations;
4956
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
5057
import org.springframework.web.method.HandlerMethod;
@@ -102,6 +109,16 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou
102109
*/
103110
private DataRestRouterOperationBuilder dataRestRouterOperationBuilder;
104111

112+
/**
113+
* The Persistent entities.
114+
*/
115+
private PersistentEntities persistentEntities;
116+
117+
/**
118+
* The Mapper.
119+
*/
120+
private ObjectMapper mapper;
121+
105122
/**
106123
* Instantiates a new Spring repository rest resource provider.
107124
*
@@ -110,14 +127,17 @@ public class SpringRepositoryRestResourceProvider implements RepositoryRestResou
110127
* @param associations the associations
111128
* @param delegatingHandlerMapping the delegating handler mapping
112129
* @param dataRestRouterOperationBuilder the data rest router operation builder
130+
* @param persistentEntities the persistent entities
131+
* @param mapper the mapper
113132
*/
114-
public SpringRepositoryRestResourceProvider(ResourceMappings mappings, Repositories repositories, Associations associations,
115-
DelegatingHandlerMapping delegatingHandlerMapping, DataRestRouterOperationBuilder dataRestRouterOperationBuilder) {
133+
public SpringRepositoryRestResourceProvider(ResourceMappings mappings, Repositories repositories, Associations associations, DelegatingHandlerMapping delegatingHandlerMapping, DataRestRouterOperationBuilder dataRestRouterOperationBuilder, PersistentEntities persistentEntities, ObjectMapper mapper) {
116134
this.mappings = mappings;
117135
this.repositories = repositories;
118136
this.associations = associations;
119137
this.delegatingHandlerMapping = delegatingHandlerMapping;
120138
this.dataRestRouterOperationBuilder = dataRestRouterOperationBuilder;
139+
this.persistentEntities = persistentEntities;
140+
this.mapper = mapper;
121141
}
122142

123143
public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
@@ -127,18 +147,38 @@ public List<RouterOperation> getRouterOperations(OpenAPI openAPI) {
127147
Class<?> repository = repositories.getRequiredRepositoryInformation(domainType).getRepositoryInterface();
128148
DataRestRepository dataRestRepository = new DataRestRepository(domainType, repository);
129149
ResourceMetadata resourceMetadata = mappings.getMetadataFor(domainType);
150+
final PersistentEntity<?, ?> entity = persistentEntities.getRequiredPersistentEntity(domainType);
151+
final JacksonMetadata jackson = new JacksonMetadata(mapper, domainType);
152+
130153
if (resourceMetadata.isExported()) {
131154
for (HandlerMapping handlerMapping : handlerMappingList) {
132155
if (handlerMapping instanceof RepositoryRestHandlerMapping) {
133156
RepositoryRestHandlerMapping repositoryRestHandlerMapping = (RepositoryRestHandlerMapping) handlerMapping;
134157
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = repositoryRestHandlerMapping.getHandlerMethods();
158+
// Entity controllers lookup first
135159
Map<RequestMappingInfo, HandlerMethod> handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
136160
.filter(requestMappingInfoHandlerMethodEntry -> REPOSITORY_ENTITY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
137-
.getValue().getBeanType().getName()) || REPOSITORY_PROPERTY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
138161
.getValue().getBeanType().getName()))
139162
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
140163
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
164+
dataRestRepository.setControllerType(ControllerType.ENTITY);
141165
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);
166+
167+
Map<RequestMappingInfo, HandlerMethod> handlerMethodMapFilteredMethodMap = handlerMethodMap.entrySet().stream()
168+
.filter(requestMappingInfoHandlerMethodEntry -> REPOSITORY_PROPERTY_CONTROLLER.equals(requestMappingInfoHandlerMethodEntry
169+
.getValue().getBeanType().getName()))
170+
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
171+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
172+
173+
entity.doWithAssociations((SimpleAssociationHandler) association -> {
174+
PersistentProperty<?> property = association.getInverse();
175+
if (jackson.isExported(property) && associations.isLinkableAssociation(property)) {
176+
ResourceMetadata targetTypeMetadata = associations.getMetadataFor(property.getActualType());
177+
dataRestRepository.setRelationName(targetTypeMetadata.getItemResourceRel().toString());
178+
dataRestRepository.setControllerType(ControllerType.PROPERTY);
179+
findControllers(routerOperationList, handlerMethodMapFilteredMethodMap, resourceMetadata, dataRestRepository, openAPI);
180+
}
181+
});
142182
}
143183
else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
144184
BasePathAwareHandlerMapping beanBasePathAwareHandlerMapping = (BasePathAwareHandlerMapping) handlerMapping;
@@ -148,7 +188,7 @@ else if (handlerMapping instanceof BasePathAwareHandlerMapping) {
148188
.getValue().getBeanType().getName()))
149189
.filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getBeanType()))
150190
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
151-
191+
dataRestRepository.setControllerType(ControllerType.SCHEMA);
152192
findControllers(routerOperationList, handlerMethodMapFiltered, resourceMetadata, dataRestRepository, openAPI);
153193
handlerMethodMapFiltered = handlerMethodMap.entrySet().stream()
154194
.filter(requestMappingInfoHandlerMethodEntry -> ProfileController.class.equals(requestMappingInfoHandlerMethodEntry

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/ControllerType.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,21 @@
2727
* The enum Controller type.
2828
* @author bnasslahsen
2929
*/
30-
enum ControllerType {
30+
public enum ControllerType {
3131
/**
3232
*Entity controller type.
3333
*/
3434
ENTITY,
3535
/**
3636
*Search controller type.
3737
*/
38-
SEARCH
38+
SEARCH,
39+
/**
40+
*Schema controller type.
41+
*/
42+
SCHEMA,
43+
/**
44+
*PROPERTY controller type.
45+
*/
46+
PROPERTY
3947
}

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestOperationBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ public Operation buildOperation(HandlerMethod handlerMethod, DataRestRepository
111111
OpenAPI openAPI, RequestMethod requestMethod, String operationPath, MethodAttributes methodAttributes,
112112
ResourceMetadata resourceMetadata, MethodResourceMapping methodResourceMapping, ControllerType controllerType) {
113113
Operation operation = null;
114-
if (ControllerType.ENTITY.equals(controllerType)) {
114+
if (ControllerType.ENTITY.equals(controllerType)
115+
|| ControllerType.PROPERTY.equals(controllerType)
116+
|| ControllerType.SCHEMA.equals(controllerType)) {
115117
operation = buildEntityOperation(handlerMethod, dataRestRepository,
116118
openAPI, requestMethod, operationPath, methodAttributes, resourceMetadata);
117119
}

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRepository.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ public class DataRestRepository {
3939
*/
4040
private Class<?> repositoryType;
4141

42+
/**
43+
* The Relation name.
44+
*/
45+
private String relationName;
46+
47+
/**
48+
* The Controller type.
49+
*/
50+
private ControllerType controllerType;
51+
4252
/**
4353
* Instantiates a new Data rest repository.
4454
*
@@ -85,4 +95,40 @@ public Class<?> getRepositoryType() {
8595
public void setRepositoryType(Class<?> repositoryType) {
8696
this.repositoryType = repositoryType;
8797
}
98+
99+
/**
100+
* Gets relation name.
101+
*
102+
* @return the relation name
103+
*/
104+
public String getRelationName() {
105+
return relationName;
106+
}
107+
108+
/**
109+
* Sets relation name.
110+
*
111+
* @param relationName the relation name
112+
*/
113+
public void setRelationName(String relationName) {
114+
this.relationName = relationName;
115+
}
116+
117+
/**
118+
* Gets controller type.
119+
*
120+
* @return the controller type
121+
*/
122+
public ControllerType getControllerType() {
123+
return controllerType;
124+
}
125+
126+
/**
127+
* Sets controller type.
128+
*
129+
* @param controllerType the controller type
130+
*/
131+
public void setControllerType(ControllerType controllerType) {
132+
this.controllerType = controllerType;
133+
}
88134
}

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ else if (methodParameter.getParameterAnnotation(BackendId.class) != null) {
172172
* @return the boolean
173173
*/
174174
private boolean isParamToIgnore(MethodParameter methodParameter) {
175-
return !requestBuilder.isParamToIgnore(methodParameter) && !isHeaderToIgnore(methodParameter);
175+
return !requestBuilder.isParamToIgnore(methodParameter)
176+
&& !isHeaderToIgnore(methodParameter)
177+
&& !"property".equals(methodParameter.getParameterName());
176178
}
177179

178180
/**

springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRouterOperationBuilder.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ public void buildEntityRouterOperationList(List<RouterOperation> routerOperation
113113
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap, ResourceMetadata resourceMetadata,
114114
DataRestRepository dataRestRepository, OpenAPI openAPI) {
115115
String path = resourceMetadata.getPath().toString();
116+
ControllerType controllerType = (dataRestRepository == null) ? ControllerType.SCHEMA : dataRestRepository.getControllerType();
116117
for (Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
117-
buildRouterOperationList(routerOperationList, resourceMetadata, dataRestRepository, openAPI, path, entry, null, ControllerType.ENTITY, null);
118+
buildRouterOperationList(routerOperationList, resourceMetadata, dataRestRepository, openAPI, path, entry, null, controllerType, null);
118119
}
119120
}
120121

@@ -150,23 +151,25 @@ public void buildSearchRouterOperationList(List<RouterOperation> routerOperation
150151
* @param path the path
151152
* @param entry the entry
152153
* @param subPath the sub path
153-
* @param entity the entity
154+
* @param controllerType the controllerType
154155
* @param methodResourceMapping the method resource mapping
155156
*/
156157
private void buildRouterOperationList(List<RouterOperation> routerOperationList, ResourceMetadata resourceMetadata,
157158
DataRestRepository dataRestRepository, OpenAPI openAPI, String path, Entry<RequestMappingInfo, HandlerMethod> entry,
158-
String subPath, ControllerType entity, MethodResourceMapping methodResourceMapping) {
159+
String subPath, ControllerType controllerType, MethodResourceMapping methodResourceMapping) {
159160
RequestMappingInfo requestMappingInfo = entry.getKey();
160161
HandlerMethod handlerMethod = entry.getValue();
161162
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
162163
if (resourceMetadata != null) {
163164
HttpMethods httpMethodsItem = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.ITEM);
164165
Set<RequestMethod> requestMethodsItem = requestMethods.stream().filter(requestMethod -> httpMethodsItem.contains(HttpMethod.valueOf(requestMethod.toString())))
165166
.collect(Collectors.toSet());
166-
HttpMethods httpMethodsCollection = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.COLLECTION);
167-
Set<RequestMethod> requestMethodsCollection = requestMethods.stream().filter(requestMethod -> httpMethodsCollection.contains(HttpMethod.valueOf(requestMethod.toString())))
168-
.collect(Collectors.toSet());
169-
requestMethodsItem.addAll(requestMethodsCollection);
167+
if (!ControllerType.PROPERTY.equals(controllerType)) {
168+
HttpMethods httpMethodsCollection = resourceMetadata.getSupportedHttpMethods().getMethodsFor(ResourceType.COLLECTION);
169+
Set<RequestMethod> requestMethodsCollection = requestMethods.stream().filter(requestMethod -> httpMethodsCollection.contains(HttpMethod.valueOf(requestMethod.toString())))
170+
.collect(Collectors.toSet());
171+
requestMethodsItem.addAll(requestMethodsCollection);
172+
}
170173
requestMethods = requestMethodsItem;
171174
}
172175

@@ -176,9 +179,11 @@ private void buildRouterOperationList(List<RouterOperation> routerOperationList,
176179
PatternsRequestCondition patternsRequestCondition = requestMappingInfo.getPatternsCondition();
177180
Set<String> patterns = patternsRequestCondition.getPatterns();
178181
Map<String, String> regexMap = new LinkedHashMap<>();
179-
String operationPath = calculateOperationPath(path, subPath, patterns, regexMap, entity);
182+
String operationPath = calculateOperationPath(path, subPath, patterns, regexMap, controllerType);
183+
if (ControllerType.PROPERTY.equals(controllerType))
184+
operationPath = operationPath.replace("{property}", dataRestRepository.getRelationName());
180185
buildRouterOperation(routerOperationList, dataRestRepository, openAPI, methodResourceMapping,
181-
handlerMethod, requestMethod, resourceMetadata, operationPath, entity);
186+
handlerMethod, requestMethod, resourceMetadata, operationPath, controllerType);
182187
}
183188
}
184189
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package test.org.springdoc.api.app17;
2+
3+
import javax.persistence.Entity;
4+
import javax.persistence.GeneratedValue;
5+
import javax.persistence.GenerationType;
6+
import javax.persistence.Id;
7+
8+
import lombok.Data;
9+
10+
@Entity
11+
public @Data
12+
class ChildProperty {
13+
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.AUTO)
16+
private long id;
17+
18+
private String name;
19+
20+
public long getId() {
21+
return id;
22+
}
23+
24+
public void setId(long id) {
25+
this.id = id;
26+
}
27+
28+
public String getName() {
29+
return name;
30+
}
31+
32+
public void setName(String name) {
33+
this.name = name;
34+
}
35+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package test.org.springdoc.api.app17;
2+
3+
import org.springframework.data.repository.PagingAndSortingRepository;
4+
5+
public interface ChildPropertyRepository extends PagingAndSortingRepository<ChildProperty, Long> {
6+
}

0 commit comments

Comments
 (0)