diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Category.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Category.java new file mode 100644 index 000000000..95ecdb36a --- /dev/null +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Category.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +/** + * @author Dario Seidl + */ +@Entity +public class Category { + + public @Id @GeneratedValue Long id; + public @Version Long version = 0L; + public String name; + + public Category() { + } + + public Category(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryProjection.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryProjection.java new file mode 100644 index 000000000..a6ccf5de9 --- /dev/null +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryProjection.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.rest.core.config.Projection; + +/** + * @author Dario Seidl + */ +@Projection(name = "open", types = Category.class) +public interface CategoryProjection { + + String getName(); + + @Value("calculated-#{target.name}") + String getCalculatedName(); +} diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryRepository.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryRepository.java new file mode 100644 index 000000000..58398fcb6 --- /dev/null +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/CategoryRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +/** + * @author Dario Seidl + */ +@RepositoryRestResource(collectionResourceRel = "categories", path = "categories") +public interface CategoryRepository extends CrudRepository { + +} diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java index eaf391630..b23b7b82c 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java @@ -57,7 +57,7 @@ public class RepositoryControllerIntegrationTests extends AbstractControllerInte RepositoryLinksResource resource = controller.listRepositories().getBody(); - assertThat(resource.getLinks()).hasSize(8); + assertThat(resource.getLinks()).hasSize(9); assertThat(resource.hasLink("people")).isTrue(); assertThat(resource.hasLink("orders")).isTrue(); @@ -66,5 +66,6 @@ public class RepositoryControllerIntegrationTests extends AbstractControllerInte assertThat(resource.hasLink("authors")).isTrue(); assertThat(resource.hasLink("receipts")).isTrue(); assertThat(resource.hasLink("items")).isTrue(); + assertThat(resource.hasLink("categories")).isTrue(); } } diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index f8bd24e6b..48a06254b 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -61,6 +61,7 @@ import com.jayway.jsonpath.JsonPath; * @author Greg Turnquist * @author Mark Paluch * @author Ľubomír Varga + * @author Dario Seidl */ @Transactional @ContextConfiguration(classes = JpaRepositoryConfig.class) @@ -240,6 +241,48 @@ public class JpaWebTests extends CommonWebTests { assertNull(JsonPath.read(frodo.getContentAsString(), "$.lastName")); } + @Test // DATAREST-1213 + public void createThenPatchWithProjection() throws Exception { + + Link categoriesLink = client.discoverUnique(LinkRelation.of("categories")); + + MockHttpServletResponse test = postAndGet(categoriesLink, "{ \"name\" : \"test\" }", + MediaType.APPLICATION_JSON); + + Link testLink = client.assertHasLinkWithRel(IanaLinkRelations.SELF, test); + + assertThat((String) JsonPath.read(test.getContentAsString(), "$.name")).isEqualTo("test"); + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(testLink.getHref()); + String uri = builder.queryParam("projection", "open").build().toUriString(); + + MockHttpServletResponse patched = patchAndGet(new Link(uri), "{ \"name\" : \"patched\" }", MediaType.APPLICATION_JSON); + + assertThat((String) JsonPath.read(patched.getContentAsString(), "$.name")).isEqualTo("patched"); + assertThat((String) JsonPath.read(patched.getContentAsString(), "$.calculatedName")).isEqualTo("calculated-patched"); + } + + @Test // DATAREST-1213 + public void createThenPutWithProjection() throws Exception { + + Link categoriesLink = client.discoverUnique(LinkRelation.of("categories")); + + MockHttpServletResponse test = postAndGet(categoriesLink, "{ \"name\" : \"test\" }", + MediaType.APPLICATION_JSON); + + Link testLink = client.assertHasLinkWithRel(IanaLinkRelations.SELF, test); + + assertThat((String) JsonPath.read(test.getContentAsString(), "$.name")).isEqualTo("test"); + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(testLink.getHref()); + String uri = builder.queryParam("projection", "open").build().toUriString(); + + MockHttpServletResponse patched = putAndGet(new Link(uri), "{ \"name\" : \"put\" }", MediaType.APPLICATION_JSON); + + assertThat((String) JsonPath.read(patched.getContentAsString(), "$.name")).isEqualTo("put"); + assertThat((String) JsonPath.read(patched.getContentAsString(), "$.calculatedName")).isEqualTo("calculated-put"); + } + @Test public void listsSiblingsWithContentCorrectly() throws Exception { assertPersonWithNameAndSiblingLink("John"); diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/ProfileIntegrationTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/ProfileIntegrationTests.java index 77675d4f3..cc136194b 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/ProfileIntegrationTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/ProfileIntegrationTests.java @@ -95,6 +95,7 @@ public class ProfileIntegrationTests extends AbstractControllerIntegrationTests assertThat(client.discoverUnique(profileLink, "orders", MediaType.ALL)).isNotNull(); assertThat(client.discoverUnique(profileLink, "receipts", MediaType.ALL)).isNotNull(); assertThat(client.discoverUnique(profileLink, "addresses", MediaType.ALL)).isNotNull(); + assertThat(client.discoverUnique(profileLink, "categories", MediaType.ALL)).isNotNull(); } @Test // DATAREST-638 diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java index c07e5b7ec..c2c33c955 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java @@ -37,6 +37,7 @@ import org.springframework.util.Assert; * instances. * * @author Oliver Gierke + * @author Dario Seidl * @soundtrack Ron Spielman Trio - Matchstick */ @RequiredArgsConstructor @@ -59,7 +60,7 @@ public class HttpHeadersPreparer { public HttpHeaders prepareHeaders(Optional resource) { return resource// - .map(it -> prepareHeaders(it.getPersistentEntity(), it.getContent()))// + .map(it -> prepareHeaders(it.getPersistentEntity(), it.getTargetEntity()))// .orElseGet(() -> new HttpHeaders()); } @@ -75,6 +76,8 @@ public class HttpHeadersPreparer { Assert.notNull(entity, "PersistentEntity must not be null!"); Assert.notNull(value, "Entity value must not be null!"); + Assert.isInstanceOf(entity.getType(), value, () -> + String.format("Target bean of type %s is not of type of the persistent entity (%s)!", value.getClass().getName(), entity.getType().getName())); // Add ETag HttpHeaders headers = ETag.from(entity, value).addTo(new HttpHeaders()); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java index bcf6e144c..10128a949 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java @@ -24,6 +24,7 @@ import java.util.List; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.projection.TargetAware; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; @@ -38,6 +39,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * * @author Jon Brisbin * @author Oliver Gierke + * @author Dario Seidl */ public class PersistentEntityResource extends EntityModel { @@ -88,13 +90,27 @@ public class PersistentEntityResource extends EntityModel { return entity; } + /** + * Returns the underlying instance. If the instance is a dynamic JDK proxy, the proxy target is returned. + * + * @return + */ + public Object getTargetEntity() { + Object content = getContent(); + if (content instanceof TargetAware) { + return ((TargetAware) content).getTarget(); + } else { + return content; + } + } + /** * Returns the {@link PersistentPropertyAccessor} for the underlying content bean. * * @return */ public PersistentPropertyAccessor getPropertyAccessor() { - return entity.getPropertyAccessor(getContent()); + return entity.getPropertyAccessor(getTargetEntity()); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java index 53571b258..7712bc321 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/ETag.java @@ -32,6 +32,7 @@ import org.springframework.util.Assert; * A value object to represent ETags. * * @author Oliver Gierke + * @author Dario Seidl */ @EqualsAndHashCode public final class ETag { @@ -74,7 +75,7 @@ public final class ETag { Assert.notNull(resource, "PersistentEntityResource must not be null!"); - return from(resource.getPersistentEntity(), resource.getContent()); + return from(resource.getPersistentEntity(), resource.getTargetEntity()); } /**