DATAREST-1213 - ETag creation now uses proxy target for projections.
Original pull request: #355.
This commit is contained in:
committed by
Oliver Drotbohm
parent
3cb683c137
commit
e3a88ff9ca
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<Category, Long> {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<PersistentEntityResource> 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());
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
@@ -88,13 +90,27 @@ public class PersistentEntityResource extends EntityModel<Object> {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user