Skip to content

Commit a7492bc

Browse files
committed
DATAREST-1205 - Use Affordances API
Leverage the Affordances API while making other media types work.
1 parent 6986a35 commit a7492bc

File tree

20 files changed

+596
-56
lines changed

20 files changed

+596
-56
lines changed

spring-data-rest-core/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
</parent>
1717

1818
<properties>
19-
<springplugin>1.2.0.RELEASE</springplugin>
19+
<springplugin>2.0.0.BUILD-SNAPSHOT</springplugin>
2020
<evoinflector>1.2.2</evoinflector>
21+
<spring-hateoas>1.0.0.SDR-SNAPSHOT</spring-hateoas>
2122
<java-module-name>spring.data.rest.core</java-module-name>
2223
<project.root>${basedir}/..</project.root>
2324
</properties>

spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/Java8PluginRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static <T extends Plugin<S>, S> Java8PluginRegistry<T, S> empty() {
4646
}
4747

4848
public Optional<T> getPluginFor(S delimiter) {
49-
return Optional.ofNullable(registry.getPluginFor(delimiter));
49+
return registry.getPluginFor(delimiter);
5050
}
5151

5252
public T getPluginOrDefaultFor(S delimiter, T fallback) {

spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/ResourceTester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Link assertHasLinkEndingWith(String rel, String hrefEnd) {
8383

8484
private final Link assertHasLinkMatching(String rel, Matcher<String> hrefMatcher) {
8585

86-
Link link = resource.getLink(rel);
86+
Link link = resource.getRequiredLink(rel);
8787
assertThat("Expected link with rel '" + rel + "' but didn't find it in " + resource.getLinks(), link,
8888
is(notNullValue()));
8989

spring-data-rest-tests/spring-data-rest-tests-jpa/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@
4747
<scope>test</scope>
4848
</dependency>
4949

50+
<dependency>
51+
<groupId>org.springframework</groupId>
52+
<artifactId>spring-web</artifactId>
53+
<version>${spring}</version>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.springframework.hateoas</groupId>
58+
<artifactId>spring-hateoas</artifactId>
59+
<version>1.0.0.SDR-SNAPSHOT</version>
60+
<scope>test</scope>
61+
</dependency>
62+
5063
<!-- Jackson Hibernate -->
5164

5265
<dependency>

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/AffordanceIntegrationTests.java

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/support/RepositoryEntityLinksIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void returnsLinksToSearchResources() {
9999

100100
assertThat(links.hasLink("firstname")).isTrue();
101101

102-
Link firstnameLink = links.getLink("firstname");
102+
Link firstnameLink = links.getLink("firstname").orElse(null);
103103
assertThat(firstnameLink.isTemplated()).isTrue();
104104
assertThat(firstnameLink.getVariableNames()).contains("page", "size");
105105
}

spring-data-rest-tests/spring-data-rest-tests-mongodb/src/test/java/org/springframework/data/rest/webmvc/PersistentEntityResourceAssemblerIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void addsSelfAndSingleResourceLinkToResourceByDefault() throws Exception
7272
Links links = new Links(resource.getLinks());
7373

7474
assertThat(links).hasSize(2);
75-
assertThat(links.getLink("self").getVariables()).isEmpty();
76-
assertThat(links.getLink("user").getVariableNames()).contains("projection");
75+
assertThat(links.getLink("self").orElseThrow(() -> new RuntimeException("Unable to find 'self' link")).getVariables()).isEmpty();
76+
assertThat(links.getLink("user").orElseThrow(() -> new RuntimeException("Unable to find 'user' link")).getVariableNames()).contains("projection");
7777
}
7878
}

spring-data-rest-webmvc/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@
124124
<scope>test</scope>
125125
</dependency>
126126

127+
<dependency>
128+
<groupId>org.springframework.data</groupId>
129+
<artifactId>spring-data-jpa</artifactId>
130+
<version>${springdata.jpa}</version>
131+
<scope>test</scope>
132+
</dependency>
133+
127134
</dependencies>
128135

129136
</project>

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ protected Link resourceLink(RootResourceInformation resourceLink, Resource resou
6363

6464
ResourceMetadata repoMapping = resourceLink.getResourceMetadata();
6565

66-
Link selfLink = resource.getLink("self");
66+
Link selfLink = resource.getRequiredLink(Link.REL_SELF);
6767
String rel = repoMapping.getItemResourceRel();
6868

6969
return new Link(selfLink.getHref(), rel);

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceAssembler.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import lombok.NonNull;
1919
import lombok.RequiredArgsConstructor;
2020

21+
import java.util.Collections;
22+
23+
import org.springframework.core.ResolvableType;
2124
import org.springframework.data.mapping.PersistentEntity;
2225
import org.springframework.data.mapping.context.PersistentEntities;
2326
import org.springframework.data.rest.core.support.SelfLinkProvider;
@@ -28,6 +31,7 @@
2831
import org.springframework.hateoas.ResourceAssembler;
2932
import org.springframework.hateoas.core.EmbeddedWrapper;
3033
import org.springframework.hateoas.core.EmbeddedWrappers;
34+
import org.springframework.http.HttpMethod;
3135
import org.springframework.util.Assert;
3236

3337
/**
@@ -72,9 +76,11 @@ private Builder wrap(Object instance, Object source) {
7276
PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(source.getClass());
7377

7478
return PersistentEntityResource.build(instance, entity).//
75-
withEmbedded(getEmbeddedResources(source)).//
76-
withLink(getSelfLinkFor(source)).//
77-
withLink(linkProvider.createSelfLinkFor(source));
79+
withEmbedded(getEmbeddedResources(source)).//
80+
withLink(getExpandedSelfLink(source)
81+
.andAffordance(HttpMethod.PUT, source.getClass(), Collections.emptyList(), null)
82+
.andAffordance(HttpMethod.PATCH, source.getClass(), Collections.emptyList(), null)).//
83+
withLink(linkProvider.createSelfLinkFor(source));
7884
}
7985

8086
/**
@@ -94,7 +100,7 @@ private Iterable<EmbeddedWrapper> getEmbeddedResources(Object instance) {
94100
* @param instance must be a managed entity, not {@literal null}.
95101
* @return
96102
*/
97-
public Link getSelfLinkFor(Object instance) {
103+
public Link getExpandedSelfLink(Object instance) {
98104

99105
Link link = linkProvider.createSelfLinkFor(instance);
100106
return new Link(link.expand().getHref(), Link.REL_SELF);

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.data.rest.core.mapping.ResourceMetadata;
2424
import org.springframework.data.web.PagedResourcesAssembler;
2525
import org.springframework.hateoas.EntityLinks;
26+
import org.springframework.hateoas.Link;
2627
import org.springframework.http.HttpEntity;
2728
import org.springframework.http.HttpHeaders;
2829
import org.springframework.http.HttpMethod;
@@ -105,6 +106,8 @@ public HttpEntity<RepositoryLinksResource> listRepositories() {
105106

106107
RepositoryLinksResource resource = new RepositoryLinksResource();
107108

109+
resource.add(new Link("/"));
110+
108111
for (Class<?> domainType : repositories) {
109112

110113
ResourceMetadata metadata = mappings.getMetadataFor(domainType);
@@ -113,6 +116,6 @@ public HttpEntity<RepositoryLinksResource> listRepositories() {
113116
}
114117
}
115118

116-
return new ResponseEntity<RepositoryLinksResource>(resource, HttpStatus.OK);
119+
return new ResponseEntity<>(resource, HttpStatus.OK);
117120
}
118121
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Optional;
26+
import java.util.stream.Collectors;
2627

2728
import org.springframework.beans.factory.annotation.Autowired;
2829
import org.springframework.context.ApplicationEventPublisher;
2930
import org.springframework.context.ApplicationEventPublisherAware;
31+
import org.springframework.core.ResolvableType;
3032
import org.springframework.core.convert.ConversionService;
3133
import org.springframework.data.auditing.AuditableBeanWrapperFactory;
3234
import org.springframework.data.domain.Sort;
@@ -208,7 +210,29 @@ public Resources<?> getCollectionResource(@QuerydslPredicate RootResourceInforma
208210

209211
Resources<?> result = toResources(results, assembler, metadata.getDomainType(), baseLink);
210212
result.add(getCollectionResourceLinks(resourceInformation, pageable));
211-
return result;
213+
return addAffordances(metadata.getDomainType(), result);
214+
}
215+
216+
private Resources<?> addAffordances(Class<?> domainType, Resources<?> resources) {
217+
218+
if (resources instanceof PagedResources) {
219+
return new PagedResources<>(resources.getContent(), ((PagedResources<?>) resources).getMetadata(), addAffordancesToLinks(domainType, resources.getLinks()));
220+
}
221+
222+
return new Resources<>(resources.getContent(), addAffordancesToLinks(domainType, resources.getLinks()));
223+
}
224+
225+
private List<Link> addAffordancesToLinks(Class<?> domainType, List<Link> links) {
226+
227+
return links.stream()
228+
.map(link -> {
229+
if (link.hasRel(Link.REL_SELF)) {
230+
return link.andAffordance(HttpMethod.POST, domainType, Collections.emptyList(), null);
231+
} else {
232+
return link;
233+
}
234+
})
235+
.collect(Collectors.toList());
212236
}
213237

214238
private List<Link> getCollectionResourceLinks(RootResourceInformation resourceInformation,
@@ -495,7 +519,7 @@ private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, Rep
495519
*/
496520
private void addLocationHeader(HttpHeaders headers, PersistentEntityResourceAssembler assembler, Object source) {
497521

498-
String selfLink = assembler.getSelfLinkFor(source).getHref();
522+
String selfLink = assembler.getExpandedSelfLink(source).getHref();
499523
headers.setLocation(new UriTemplate(selfLink).expand());
500524
}
501525

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ public ResponseEntity<ResourceSupport> followPropertyReference(final RootResourc
134134
} else {
135135

136136
PersistentEntityResource resource = assembler.toResource(it);
137-
headers.set("Content-Location", resource.getId().getHref());
137+
headers.set("Content-Location", resource.getRequiredLink(Link.REL_SELF).getHref());
138138
return resource;
139139
}
140140

141-
}).orElseThrow(() -> new ResourceNotFoundException());
141+
}).orElseThrow(ResourceNotFoundException::new);
142142

143143
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, //
144144
doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.GET));
@@ -187,7 +187,7 @@ public ResponseEntity<ResourceSupport> followPropertyReference(RootResourceInfor
187187
if (propertyId.equals(accessor1.getIdentifier().toString())) {
188188

189189
PersistentEntityResource resource1 = assembler.toResource(obj);
190-
headers.set("Content-Location", resource1.getId().getHref());
190+
headers.set("Content-Location", resource1.getRequiredLink(Link.REL_SELF).getHref());
191191
return resource1;
192192
}
193193
}
@@ -200,18 +200,18 @@ public ResponseEntity<ResourceSupport> followPropertyReference(RootResourceInfor
200200
if (propertyId.equals(accessor2.getIdentifier().toString())) {
201201

202202
PersistentEntityResource resource2 = assembler.toResource(entry.getValue());
203-
headers.set("Content-Location", resource2.getId().getHref());
203+
headers.set("Content-Location", resource2.getRequiredLink(Link.REL_SELF).getHref());
204204
return resource2;
205205
}
206206
}
207207

208208
} else {
209-
return new Resource<Object>(prop.propertyValue);
209+
return new Resource<>(prop.propertyValue);
210210
}
211211

212212
throw new ResourceNotFoundException();
213213

214-
}).orElseThrow(() -> new ResourceNotFoundException());
214+
}).orElseThrow(ResourceNotFoundException::new);
215215

216216
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, //
217217
doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.GET));
@@ -254,7 +254,7 @@ public ResponseEntity<ResourceSupport> followPropertyReferenceCompact(RootResour
254254
Map<Object, Resource<?>> map = (Map<Object, Resource<?>>) content;
255255

256256
for (Entry<Object, Resource<?>> entry : map.entrySet()) {
257-
Link l = new Link(entry.getValue().getLink("self").getHref(), entry.getKey().toString());
257+
Link l = new Link(entry.getValue().getRequiredLink(Link.REL_SELF).getHref(), entry.getKey().toString());
258258
links.add(l);
259259
}
260260
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.data.rest.core.mapping.ResourceMappings;
3636
import org.springframework.data.rest.core.mapping.ResourceMetadata;
3737
import org.springframework.data.rest.webmvc.support.JpaHelper;
38+
import org.springframework.hateoas.MediaTypes;
3839
import org.springframework.http.HttpMethod;
3940
import org.springframework.http.MediaType;
4041
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
@@ -203,6 +204,8 @@ protected ProducesRequestCondition customize(ProducesRequestCondition condition)
203204
HashSet<String> mediaTypes = new LinkedHashSet<String>();
204205
mediaTypes.add(configuration.getDefaultMediaType().toString());
205206
mediaTypes.add(MediaType.APPLICATION_JSON_VALUE);
207+
mediaTypes.add(MediaTypes.HAL_FORMS_JSON_VALUE);
208+
mediaTypes.add(MediaTypes.COLLECTION_JSON_VALUE);
206209

207210
return new ProducesRequestCondition(mediaTypes.toArray(new String[mediaTypes.size()]));
208211
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,23 +344,20 @@ private Optional<Object> executeQueryMethod(final RepositoryInvoker invoker,
344344
List<TypeInformation<?>> parameterTypeInformations = ClassTypeInformation.from(method.getDeclaringClass())
345345
.getParameterTypes(method);
346346

347-
for (Entry<String, List<Object>> entry : parameters.entrySet()) {
347+
parameters.entrySet().forEach(entry ->
348348

349-
MethodParameter parameter = methodParameters.getParameter(entry.getKey());
349+
methodParameters.getParameter(entry.getKey()).ifPresent(parameter -> {
350350

351-
if (parameter == null) {
352-
continue;
353-
}
354-
355-
int parameterIndex = parameterList.indexOf(parameter);
356-
TypeInformation<?> domainType = parameterTypeInformations.get(parameterIndex).getActualType();
351+
int parameterIndex = parameterList.indexOf(parameter);
352+
TypeInformation<?> domainType = parameterTypeInformations.get(parameterIndex).getActualType();
357353

358-
ResourceMetadata metadata = mappings.getMetadataFor(domainType.getType());
354+
ResourceMetadata metadata = mappings.getMetadataFor(domainType.getType());
359355

360-
if (metadata != null && metadata.isExported()) {
361-
result.put(parameter.getParameterName(), prepareUris(entry.getValue()));
356+
if (metadata != null && metadata.isExported()) {
357+
result.put(parameter.getParameterName(), prepareUris(entry.getValue()));
358+
}
362359
}
363-
}
360+
));
364361

365362
return invoker.invokeQueryMethod(method, result, pageable.getPageable(), sort);
366363
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public Alps convert(RootResourceInformation resourceInformation) {
122122

123123
descriptors.addAll(buildSearchResourceDescriptors(resourceInformation.getPersistentEntity()));
124124

125-
return Alps.alps().descriptors(descriptors).build();
125+
return Alps.alps().descriptor(descriptors).build();
126126
}
127127

128128
private Descriptor buildRepresentationDescriptor(Class<?> type) {
@@ -135,7 +135,7 @@ private Descriptor buildRepresentationDescriptor(Class<?> type) {
135135
id(getRepresentationDescriptorId(metadata)).//
136136
href(href).//
137137
doc(getDocFor(metadata.getItemResourceDescription())).//
138-
descriptors(buildPropertyDescriptors(type, metadata.getItemResourceRel())).//
138+
descriptor(buildPropertyDescriptors(type, metadata.getItemResourceRel())).//
139139
build();
140140
}
141141

@@ -155,7 +155,7 @@ private Descriptor buildCollectionResourceDescriptor(Class<?> type, RootResource
155155
type(descriptorType).//
156156
doc(getDocFor(metadata.getDescription())).//
157157
rt("#" + representationDescriptor.getId()).//
158-
descriptors(nestedDescriptors).build();
158+
descriptor(nestedDescriptors).build();
159159
}
160160

161161
/**
@@ -184,15 +184,15 @@ private Descriptor buildProjectionDescriptor(ResourceMetadata metadata) {
184184
type(Type.SEMANTIC).//
185185
name(projection.getKey()).//
186186
doc(getDocFor(projectionDescription)).//
187-
descriptors(createJacksonDescriptor(projection.getKey(), type)).//
187+
descriptor(createJacksonDescriptor(projection.getKey(), type)).//
188188
build());
189189
}
190190

191191
return descriptor().//
192192
type(Type.SEMANTIC).//
193193
name(projectionParameterName).//
194194
doc(getDocFor(SimpleResourceDescription.defaultFor(projectionParameterName))).//
195-
descriptors(projectionDescriptors).build();
195+
descriptor(projectionDescriptors).build();
196196
}
197197

198198
private List<Descriptor> createJacksonDescriptor(String name, Class<?> type) {
@@ -231,7 +231,7 @@ private Descriptor buildItemResourceDescriptor(RootResourceInformation resourceI
231231
type(getType(method)).//
232232
doc(getDocFor(metadata.getItemResourceDescription())).//
233233
rt("#".concat(representationDescriptor.getId())). //
234-
descriptors(getProjectionDescriptor(entity.getType(), method)).//
234+
descriptor(getProjectionDescriptor(entity.getType(), method)).//
235235
build();
236236
}
237237

@@ -375,7 +375,7 @@ private Collection<Descriptor> buildSearchResourceDescriptors(PersistentEntity<?
375375
descriptors.add(descriptor().//
376376
type(Type.SAFE).//
377377
name(methodMapping.getRel()).//
378-
descriptors(parameterDescriptors).//
378+
descriptor(parameterDescriptors).//
379379
build());
380380
}
381381

0 commit comments

Comments
 (0)