Skip to content

Commit 055c452

Browse files
darioseidlodrotbohm
authored andcommitted
DATAREST-1213 - ETag creation now uses proxy target for projections.
Original pull request: #355.
1 parent 18b4594 commit 055c452

File tree

9 files changed

+190
-4
lines changed

9 files changed

+190
-4
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.webmvc.jpa;
17+
18+
import javax.persistence.Entity;
19+
import javax.persistence.GeneratedValue;
20+
import javax.persistence.Id;
21+
import javax.persistence.Version;
22+
23+
/**
24+
* @author Dario Seidl
25+
*/
26+
@Entity
27+
public class Category {
28+
29+
public @Id @GeneratedValue Long id;
30+
public @Version Long version = 0L;
31+
public String name;
32+
33+
public Category() {
34+
}
35+
36+
public Category(String name) {
37+
this.name = name;
38+
}
39+
40+
public Long getId() {
41+
return id;
42+
}
43+
44+
public void setId(Long id) {
45+
this.id = id;
46+
}
47+
48+
public Long getVersion() {
49+
return version;
50+
}
51+
52+
public void setVersion(Long version) {
53+
this.version = version;
54+
}
55+
56+
public String getName() {
57+
return name;
58+
}
59+
60+
public void setName(String name) {
61+
this.name = name;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.webmvc.jpa;
17+
18+
import org.springframework.beans.factory.annotation.Value;
19+
import org.springframework.data.rest.core.config.Projection;
20+
21+
/**
22+
* @author Dario Seidl
23+
*/
24+
@Projection(name = "open", types = Category.class)
25+
public interface CategoryProjection {
26+
27+
String getName();
28+
29+
@Value("calculated-#{target.name}")
30+
String getCalculatedName();
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.webmvc.jpa;
17+
18+
import org.springframework.data.repository.CrudRepository;
19+
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
20+
21+
/**
22+
* @author Dario Seidl
23+
*/
24+
@RepositoryRestResource(collectionResourceRel = "categories", path = "categories")
25+
public interface CategoryRepository extends CrudRepository<Category, Long> {
26+
27+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void exposesLinksToRepositories() {
5757

5858
RepositoryLinksResource resource = controller.listRepositories().getBody();
5959

60-
assertThat(resource.getLinks()).hasSize(8);
60+
assertThat(resource.getLinks()).hasSize(9);
6161

6262
assertThat(resource.hasLink("people")).isTrue();
6363
assertThat(resource.hasLink("orders")).isTrue();
@@ -66,5 +66,6 @@ public void exposesLinksToRepositories() {
6666
assertThat(resource.hasLink("authors")).isTrue();
6767
assertThat(resource.hasLink("receipts")).isTrue();
6868
assertThat(resource.hasLink("items")).isTrue();
69+
assertThat(resource.hasLink("categories")).isTrue();
6970
}
7071
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
* @author Greg Turnquist
6767
* @author Mark Paluch
6868
* @author Ľubomír Varga
69+
* @author Dario Seidl
6970
*/
7071
@Transactional
7172
@ContextConfiguration(classes = JpaRepositoryConfig.class, initializers = JpaWebTests.JpaContextInitializer.class)
@@ -256,6 +257,48 @@ public void createThenPut() throws Exception {
256257
assertNull(JsonPath.read(frodo.getContentAsString(), "$.lastName"));
257258
}
258259

260+
@Test // DATAREST-1213
261+
public void createThenPatchWithProjection() throws Exception {
262+
263+
Link categoriesLink = client.discoverUnique(LinkRelation.of("categories"));
264+
265+
MockHttpServletResponse test = postAndGet(categoriesLink, "{ \"name\" : \"test\" }",
266+
MediaType.APPLICATION_JSON);
267+
268+
Link testLink = client.assertHasLinkWithRel(IanaLinkRelations.SELF, test);
269+
270+
assertThat((String) JsonPath.read(test.getContentAsString(), "$.name")).isEqualTo("test");
271+
272+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(testLink.getHref());
273+
String uri = builder.queryParam("projection", "open").build().toUriString();
274+
275+
MockHttpServletResponse patched = patchAndGet(new Link(uri), "{ \"name\" : \"patched\" }", MediaType.APPLICATION_JSON);
276+
277+
assertThat((String) JsonPath.read(patched.getContentAsString(), "$.name")).isEqualTo("patched");
278+
assertThat((String) JsonPath.read(patched.getContentAsString(), "$.calculatedName")).isEqualTo("calculated-patched");
279+
}
280+
281+
@Test // DATAREST-1213
282+
public void createThenPutWithProjection() throws Exception {
283+
284+
Link categoriesLink = client.discoverUnique(LinkRelation.of("categories"));
285+
286+
MockHttpServletResponse test = postAndGet(categoriesLink, "{ \"name\" : \"test\" }",
287+
MediaType.APPLICATION_JSON);
288+
289+
Link testLink = client.assertHasLinkWithRel(IanaLinkRelations.SELF, test);
290+
291+
assertThat((String) JsonPath.read(test.getContentAsString(), "$.name")).isEqualTo("test");
292+
293+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(testLink.getHref());
294+
String uri = builder.queryParam("projection", "open").build().toUriString();
295+
296+
MockHttpServletResponse patched = putAndGet(new Link(uri), "{ \"name\" : \"put\" }", MediaType.APPLICATION_JSON);
297+
298+
assertThat((String) JsonPath.read(patched.getContentAsString(), "$.name")).isEqualTo("put");
299+
assertThat((String) JsonPath.read(patched.getContentAsString(), "$.calculatedName")).isEqualTo("calculated-put");
300+
}
301+
259302
@Test
260303
public void listsSiblingsWithContentCorrectly() throws Exception {
261304
assertPersonWithNameAndSiblingLink("John");

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public void profileRootLinkContainsMetadataForEachRepo() throws Exception {
9595
assertThat(client.discoverUnique(profileLink, "orders", MediaType.ALL)).isNotNull();
9696
assertThat(client.discoverUnique(profileLink, "receipts", MediaType.ALL)).isNotNull();
9797
assertThat(client.discoverUnique(profileLink, "addresses", MediaType.ALL)).isNotNull();
98+
assertThat(client.discoverUnique(profileLink, "categories", MediaType.ALL)).isNotNull();
9899
}
99100

100101
@Test // DATAREST-638

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* instances.
3535
*
3636
* @author Oliver Gierke
37+
* @author Dario Seidl
3738
* @soundtrack Ron Spielman Trio - Matchstick
3839
*/
3940
public class HttpHeadersPreparer {
@@ -60,7 +61,7 @@ public HttpHeadersPreparer(AuditableBeanWrapperFactory auditableBeanWrapperFacto
6061
public HttpHeaders prepareHeaders(Optional<PersistentEntityResource> resource) {
6162

6263
return resource//
63-
.map(it -> prepareHeaders(it.getPersistentEntity(), it.getContent()))//
64+
.map(it -> prepareHeaders(it.getPersistentEntity(), it.getTargetEntity()))//
6465
.orElseGet(() -> new HttpHeaders());
6566
}
6667

@@ -76,6 +77,8 @@ public HttpHeaders prepareHeaders(PersistentEntity<?, ?> entity, Object value) {
7677

7778
Assert.notNull(entity, "PersistentEntity must not be null!");
7879
Assert.notNull(value, "Entity value must not be null!");
80+
Assert.isInstanceOf(entity.getType(), value, () ->
81+
String.format("Target bean of type %s is not of type of the persistent entity (%s)!", value.getClass().getName(), entity.getType().getName()));
7982

8083
// Add ETag
8184
HttpHeaders headers = ETag.from(entity, value).addTo(new HttpHeaders());

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.mapping.PersistentEntity;
2323
import org.springframework.data.mapping.PersistentProperty;
2424
import org.springframework.data.mapping.PersistentPropertyAccessor;
25+
import org.springframework.data.projection.TargetAware;
2526
import org.springframework.hateoas.CollectionModel;
2627
import org.springframework.hateoas.EntityModel;
2728
import org.springframework.hateoas.Link;
@@ -36,6 +37,7 @@
3637
*
3738
* @author Jon Brisbin
3839
* @author Oliver Gierke
40+
* @author Dario Seidl
3941
*/
4042
public class PersistentEntityResource extends EntityModel<Object> {
4143

@@ -92,13 +94,27 @@ public boolean isNested() {
9294
return entity;
9395
}
9496

97+
/**
98+
* Returns the underlying instance. If the instance is a dynamic JDK proxy, the proxy target is returned.
99+
*
100+
* @return
101+
*/
102+
public Object getTargetEntity() {
103+
Object content = getContent();
104+
if (content instanceof TargetAware) {
105+
return ((TargetAware) content).getTarget();
106+
} else {
107+
return content;
108+
}
109+
}
110+
95111
/**
96112
* Returns the {@link PersistentPropertyAccessor} for the underlying content bean.
97113
*
98114
* @return
99115
*/
100116
public PersistentPropertyAccessor<?> getPropertyAccessor() {
101-
return entity.getPropertyAccessor(getContent());
117+
return entity.getPropertyAccessor(getTargetEntity());
102118
}
103119

104120
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* A value object to represent ETags.
3232
*
3333
* @author Oliver Gierke
34+
* @author Dario Seidl
3435
*/
3536
public final class ETag {
3637

@@ -72,7 +73,7 @@ public static ETag from(PersistentEntityResource resource) {
7273

7374
Assert.notNull(resource, "PersistentEntityResource must not be null!");
7475

75-
return from(resource.getPersistentEntity(), resource.getContent());
76+
return from(resource.getPersistentEntity(), resource.getTargetEntity());
7677
}
7778

7879
/**

0 commit comments

Comments
 (0)