DATAREST-317 - Support for excerpt projections.
This commit adds a mechanism to define excerpt projections to be rendered for exposed repositories. The main user facing mechanism is the addition of the excerptProjection attribute to @RepositoryRestResource. This attribute takes a type which has to be a projection interface (see DATAREST-221 for more information about the general mechanism).
If such a excerpt projection is in place, it will be used by default when rendering instances of the domain type in _embedded clauses or if it is related to.
class Person {
String name;
int age;
Person father;
Person mother;
Set<Person> siblings:
}
interface PersonExcerpt {
String getName();
}
If PersonExcerpt is now configured as excerpt projection for Person the collection resource for people will return:
{ _embedded : {
people : [{
name : "Some name",
_embedded : {
mother : { name : "…" },
father : { name : "…" },
siblings : [ … ]
},
_links : { self : { href : "…" }}
}, … ]
}
}
Here you can see how the age property is omitted when rendering a person in a collection. Also each person contains the excerpt projections of related resources and the links to them omitted. If you now follow the link to the item resource, you'll something like this:
{ name : "Some name",
age : 34,
_embedded : {
mother : { name : "…" },
father : { name : "…" },
siblings : [ … ]
},
_links : {
self : { href : "…" },
mother : { href : "…" },
father : { href : "…" },
siblings : { href : "…" }
}
}
Note, that age appears, as the representation is now rendered entirely. We also see the excerpts of related resource but also the links pointing to them in case you want to manage them.
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<spring.hateoas>0.12.0.RELEASE</spring.hateoas>
|
||||
<spring.hateoas>0.13.0.BUILD-SNAPSHOT</spring.hateoas>
|
||||
<springplugin>1.1.0.RELEASE</springplugin>
|
||||
<evoinflector>1.1</evoinflector>
|
||||
</properties>
|
||||
|
||||
@@ -72,4 +72,15 @@ public @interface RepositoryRestResource {
|
||||
* @return
|
||||
*/
|
||||
Description itemResourceDescription() default @Description(value = "");
|
||||
|
||||
/**
|
||||
* Configures the projection type to be used when embedding item resources into collections and related resources.
|
||||
* Defaults to {@link None}, which indicates full rendering of the items in a collection resource and no inlining of
|
||||
* related resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> excerptProjection() default None.class;
|
||||
|
||||
static class None {}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,12 @@ public interface CollectionResourceMapping extends ResourceMapping {
|
||||
* @return
|
||||
*/
|
||||
ResourceDescription getItemResourceDescription();
|
||||
|
||||
/**
|
||||
* Returns the projection type to be used when embedding item resources into collections and related resources. If
|
||||
* {@literal null} is returned this will mean full rendering for collections and no rendering for related resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> getExcerptProjection();
|
||||
}
|
||||
|
||||
@@ -169,6 +169,15 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
|
||||
return mapping.getItemResourceDescription();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getExcerptProjection()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getExcerptProjection() {
|
||||
return mapping.getExcerptProjection();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSearchResourceMappings()
|
||||
|
||||
@@ -204,4 +204,20 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping {
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getExcerptProjection()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getExcerptProjection() {
|
||||
|
||||
if (repositoryAnnotation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> excerptProjection = repositoryAnnotation.excerptProjection();
|
||||
|
||||
return excerptProjection.equals(RepositoryRestResource.None.class) ? null : excerptProjection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,15 @@ class TypeBasedCollectionResourceMapping implements CollectionResourceMapping {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getExcerptProjection()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getExcerptProjection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default path to be used if the path is not configured manually.
|
||||
*
|
||||
|
||||
@@ -200,24 +200,25 @@ class AbstractRepositoryRestController implements MessageSourceAware {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected Resources resultToResources(Object result, PersistentEntityResourceAssembler assembler) {
|
||||
protected Resources resultToResources(Object result, PersistentEntityResourceAssembler assembler, Link baseLink) {
|
||||
|
||||
if (result instanceof Page) {
|
||||
Page<Object> page = (Page<Object>) result;
|
||||
return entitiesToResources(page, assembler);
|
||||
return entitiesToResources(page, assembler, baseLink);
|
||||
} else if (result instanceof Iterable) {
|
||||
return entitiesToResources((Iterable<Object>) result, assembler);
|
||||
} else if (null == result) {
|
||||
return new Resources(EMPTY_RESOURCE_LIST);
|
||||
} else {
|
||||
Resource<Object> resource = assembler.toResource(result);
|
||||
Resource<Object> resource = assembler.toFullResource(result);
|
||||
return new Resources(Collections.singletonList(resource));
|
||||
}
|
||||
}
|
||||
|
||||
protected Resources<? extends Resource<Object>> entitiesToResources(Page<Object> page,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
return pagedResourcesAssembler.toResource(page, assembler);
|
||||
PersistentEntityResourceAssembler assembler, Link baseLink) {
|
||||
return baseLink == null ? pagedResourcesAssembler.toResource(page, assembler) : pagedResourcesAssembler.toResource(
|
||||
page, assembler, baseLink);
|
||||
}
|
||||
|
||||
protected Resources<Resource<Object>> entitiesToResources(Iterable<Object> entities,
|
||||
|
||||
@@ -15,14 +15,19 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.EmbeddedWrapper;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
/**
|
||||
* A Spring HATEOAS {@link Resource} subclass that holds a reference to the entity's {@link PersistentEntity} metadata.
|
||||
@@ -30,25 +35,163 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistentEntityResource<T> extends Resource<T> {
|
||||
public class PersistentEntityResource extends Resource<Object> {
|
||||
|
||||
private static final Resources<EmbeddedWrapper> NO_EMBEDDEDS = new Resources<EmbeddedWrapper>(
|
||||
Collections.<EmbeddedWrapper> emptyList());
|
||||
|
||||
private final PersistentEntity<?, ?> entity;
|
||||
private final Resources<EmbeddedWrapper> embeddeds;
|
||||
private final boolean enforceAssociationLinks;
|
||||
|
||||
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> entity, T obj, Link selfLink) {
|
||||
return new PersistentEntityResource<T>(entity, obj, selfLink);
|
||||
}
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResource} for the given {@link PersistentEntity}, content, embedded
|
||||
* {@link Resources}, links and flag whether to render all associations.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param content must not be {@literal null}.
|
||||
* @param links must not be {@literal null}.
|
||||
* @param renderAllAssociations
|
||||
* @param embeddeds can be {@literal null}.
|
||||
*/
|
||||
private PersistentEntityResource(PersistentEntity<?, ?> entity, Object content, Iterable<Link> links,
|
||||
boolean renderAllAssociations, Resources<EmbeddedWrapper> embeddeds) {
|
||||
|
||||
public PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Link... links) {
|
||||
this(entity, content, Arrays.asList(links));
|
||||
}
|
||||
|
||||
private PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Iterable<Link> links) {
|
||||
super(content, links);
|
||||
|
||||
Assert.notNull(entity, "PersistentEntity must not be null!");
|
||||
|
||||
this.entity = entity;
|
||||
this.embeddeds = embeddeds == null ? NO_EMBEDDEDS : embeddeds;
|
||||
this.enforceAssociationLinks = renderAllAssociations;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
/**
|
||||
* Returns the {@link PersistentEntity} for the underlying instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PersistentEntity<?, ? extends PersistentProperty<?>> getPersistentEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources that are supposed to be rendered in the {@code _embedded} clause.
|
||||
*
|
||||
* @return the embeddeds
|
||||
*/
|
||||
@JsonUnwrapped
|
||||
public Resources<EmbeddedWrapper> getEmbeddeds() {
|
||||
return embeddeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Link} shall be rendered for the resource.
|
||||
*
|
||||
* @param link must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean shouldRenderLink(Link link) {
|
||||
|
||||
Assert.notNull(link, "Link must not be null!");
|
||||
|
||||
if (enforceAssociationLinks) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (EmbeddedWrapper wrapper : embeddeds) {
|
||||
if (wrapper.hasRel(link.getRel())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} to create {@link PersistentEntityResource}s eventually.
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Builder build(Object content, PersistentEntity<?, ?> entity) {
|
||||
return new Builder(content, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to create {@link PersistentEntityResource} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final Object content;
|
||||
private final PersistentEntity<?, ?> entity;
|
||||
private final List<Link> links = new ArrayList<Link>();
|
||||
|
||||
private Resources<EmbeddedWrapper> embeddeds;
|
||||
private boolean renderAllAssociationLinks = false;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} instance for the given content and {@link PersistentEntity}.
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
*/
|
||||
private Builder(Object content, PersistentEntity<?, ?> entity) {
|
||||
|
||||
Assert.notNull(content, "Content must not be null!");
|
||||
Assert.notNull(entity, "PersistentEntity must not be null!");
|
||||
|
||||
this.content = content;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the builder to embedd the given E
|
||||
*
|
||||
* @param resources can be {@literal null}.
|
||||
* @return the builder
|
||||
*/
|
||||
public Builder withEmbedded(Iterable<EmbeddedWrapper> resources) {
|
||||
|
||||
this.embeddeds = resources == null ? null : new Resources<EmbeddedWrapper>(resources);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the builder to render all association links independently of the embedded resources added.
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
public Builder renderAllAssociationLinks() {
|
||||
|
||||
this.renderAllAssociationLinks = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Link} to the {@link PersistentEntityResource}.
|
||||
*
|
||||
* @param link must not be {@literal null}.
|
||||
* @return the builder
|
||||
*/
|
||||
public Builder withLink(Link link) {
|
||||
|
||||
Assert.notNull(link, "Link must not be null!");
|
||||
|
||||
this.links.add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally creates the {@link PersistentEntityResource} instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PersistentEntityResource build() {
|
||||
return new PersistentEntityResource(entity, content, links, renderAllAssociationLinks, embeddeds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,26 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResource.Builder;
|
||||
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
import org.springframework.data.rest.webmvc.support.Projector;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.ResourceAssembler;
|
||||
import org.springframework.hateoas.core.EmbeddedWrapper;
|
||||
import org.springframework.hateoas.core.EmbeddedWrappers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -29,28 +42,34 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistentEntityResourceAssembler implements ResourceAssembler<Object, PersistentEntityResource<Object>> {
|
||||
public class PersistentEntityResourceAssembler implements ResourceAssembler<Object, PersistentEntityResource> {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final EntityLinks entityLinks;
|
||||
private final Projector projector;
|
||||
private final ResourceMappings mappings;
|
||||
private final EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResourceAssembler}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param entityLinks must not be {@literal null}.
|
||||
* @param projections must not be {@literal null}.
|
||||
* @param projector must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector) {
|
||||
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector,
|
||||
ResourceMappings mappings) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(entityLinks, "EntityLinks must not be null!");
|
||||
Assert.notNull(projector, "PersistentEntityProjector must not be be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.entityLinks = entityLinks;
|
||||
this.projector = projector;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -58,10 +77,106 @@ public class PersistentEntityResourceAssembler implements ResourceAssembler<Obje
|
||||
* @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public PersistentEntityResource<Object> toResource(Object instance) {
|
||||
public PersistentEntityResource toResource(Object instance) {
|
||||
|
||||
Assert.notNull(instance, "Entity instance must not be null!");
|
||||
|
||||
return wrap(projector.projectExcerpt(instance), instance).build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full object as {@link PersistentEntityResource} using the underlying {@link Projector}.
|
||||
*
|
||||
* @param instance must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public PersistentEntityResource toFullResource(Object instance) {
|
||||
|
||||
Assert.notNull(instance, "Entity instance must not be null!");
|
||||
return wrap(projector.project(instance), instance).//
|
||||
renderAllAssociationLinks().build();
|
||||
}
|
||||
|
||||
private Builder wrap(Object instance, Object source) {
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(source.getClass());
|
||||
|
||||
return PersistentEntityResource.build(instance, entity).//
|
||||
withEmbedded(getEmbeddedResources(source)).//
|
||||
withLink(getSelfLinkFor(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the embedded resources to render. This will add an {@link RelatedResource} for linkable associations if
|
||||
* they have an excerpt projection registered.
|
||||
*
|
||||
* @param instance must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Iterable<EmbeddedWrapper> getEmbeddedResources(Object instance) {
|
||||
|
||||
Assert.notNull(instance, "Entity instance must not be null!");
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instance.getClass());
|
||||
return PersistentEntityResource.wrap(entity, projector.project(instance), getSelfLinkFor(instance));
|
||||
|
||||
final List<EmbeddedWrapper> associationProjections = new ArrayList<EmbeddedWrapper>();
|
||||
final BeanWrapper<Object> wrapper = BeanWrapper.create(instance, null);
|
||||
final AssociationLinks associationLinks = new AssociationLinks(mappings);
|
||||
final ResourceMetadata metadata = mappings.getMappingFor(instance.getClass());
|
||||
|
||||
entity.doWithAssociations(new SimpleAssociationHandler() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
|
||||
*/
|
||||
@Override
|
||||
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
|
||||
|
||||
PersistentProperty<?> property = association.getInverse();
|
||||
|
||||
if (!associationLinks.isLinkableAssociation(property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projector.hasExcerptProjection(property.getActualType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object value = wrapper.getProperty(association.getInverse());
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String rel = metadata.getMappingFor(property).getRel();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
|
||||
if (collection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Object> nestedCollection = new ArrayList<Object>();
|
||||
|
||||
for (Object element : collection) {
|
||||
if (element != null) {
|
||||
nestedCollection.add(projector.projectExcerpt(element));
|
||||
}
|
||||
}
|
||||
|
||||
associationProjections.add(wrappers.wrap(nestedCollection, rel));
|
||||
|
||||
} else {
|
||||
associationProjections.add(wrappers.wrap(projector.projectExcerpt(value), rel));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return associationProjections;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
@@ -45,8 +44,9 @@ import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.core.support.DomainObjectMerger;
|
||||
import org.springframework.data.rest.core.support.DomainObjectMerger.NullHandlingPolicy;
|
||||
import org.springframework.data.rest.webmvc.support.BackendId;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.web.PagedResourcesAssembler;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.PagedResources;
|
||||
import org.springframework.hateoas.Resource;
|
||||
@@ -72,7 +72,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
private static final String BASE_MAPPING = "/{repository}";
|
||||
|
||||
private final EntityLinks entityLinks;
|
||||
private final RepositoryEntityLinks entityLinks;
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final ConversionService conversionService;
|
||||
private final DomainObjectMerger domainObjectMerger;
|
||||
@@ -81,7 +81,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
@Autowired
|
||||
public RepositoryEntityController(Repositories repositories, RepositoryRestConfiguration config,
|
||||
EntityLinks entityLinks, PagedResourcesAssembler<Object> assembler,
|
||||
RepositoryEntityLinks entityLinks, PagedResourcesAssembler<Object> assembler,
|
||||
@Qualifier("defaultConversionService") ConversionService conversionService, DomainObjectMerger domainObjectMerger) {
|
||||
|
||||
super(assembler);
|
||||
@@ -103,9 +103,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public Resources<?> getCollectionResource(final RootResourceInformation resourceInformation, Pageable pageable,
|
||||
Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
public Resources<?> getCollectionResource(final RootResourceInformation resourceInformation,
|
||||
DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION);
|
||||
|
||||
@@ -118,7 +118,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
Iterable<?> results;
|
||||
|
||||
if (pageable != null) {
|
||||
results = invoker.invokeFindAll(pageable);
|
||||
results = invoker.invokeFindAll(pageable.getPageable());
|
||||
} else {
|
||||
results = invoker.invokeFindAll(sort);
|
||||
}
|
||||
@@ -132,7 +132,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
.withRel(searchMappings.getRel()));
|
||||
}
|
||||
|
||||
Resources<?> resources = resultToResources(results, assembler);
|
||||
Link baseLink = entityLinks.linkToPagedResource(resourceInformation.getDomainType(), pageable.isDefault() ? null
|
||||
: pageable.getPageable());
|
||||
|
||||
Resources<?> resources = resultToResources(results, assembler, baseLink);
|
||||
resources.add(links);
|
||||
return resources;
|
||||
}
|
||||
@@ -141,15 +144,15 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
|
||||
"application/x-spring-data-compact+json", "text/uri-list" })
|
||||
public Resources<?> getCollectionResourceCompact(RootResourceInformation repoRequest, Pageable pageable, Sort sort,
|
||||
PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
|
||||
public Resources<?> getCollectionResourceCompact(RootResourceInformation repoRequest, DefaultedPageable pageable,
|
||||
Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
Resources<?> resources = getCollectionResource(repoRequest, pageable, sort, assembler);
|
||||
List<Link> links = new ArrayList<Link>(resources.getLinks());
|
||||
|
||||
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
|
||||
PersistentEntityResource<?> persistentEntityResource = (PersistentEntityResource<?>) resource;
|
||||
PersistentEntityResource persistentEntityResource = (PersistentEntityResource) resource;
|
||||
links.add(resourceLink(repoRequest, persistentEntityResource));
|
||||
}
|
||||
if (resources instanceof PagedResources) {
|
||||
@@ -170,7 +173,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST)
|
||||
public ResponseEntity<ResourceSupport> postCollectionResource(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<?> payload, PersistentEntityResourceAssembler assembler)
|
||||
PersistentEntityResource payload, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
|
||||
@@ -205,7 +208,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
return new ResponseEntity<Resource<?>>(assembler.toResource(domainObj), HttpStatus.OK);
|
||||
return new ResponseEntity<Resource<?>>(assembler.toFullResource(domainObj), HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,7 +222,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT)
|
||||
public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<Object> payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler)
|
||||
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
|
||||
@@ -250,7 +253,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PATCH)
|
||||
public ResponseEntity<ResourceSupport> patchItemResource(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<Object> payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler)
|
||||
PersistentEntityResource payload, @BackendId Serializable id, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM);
|
||||
@@ -323,7 +326,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
}
|
||||
|
||||
if (config.isReturnBodyOnUpdate()) {
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, assembler.toResource(obj));
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, assembler.toFullResource(obj));
|
||||
} else {
|
||||
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT, headers);
|
||||
}
|
||||
@@ -346,8 +349,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
addLocationHeader(headers, assembler, savedObject);
|
||||
|
||||
PersistentEntityResource<Object> resource = config.isReturnBodyOnCreate() ? assembler.toResource(savedObject)
|
||||
: null;
|
||||
PersistentEntityResource resource = config.isReturnBodyOnCreate() ? assembler.toFullResource(savedObject) : null;
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, headers, resource);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
} else {
|
||||
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(prop.propertyValue);
|
||||
PersistentEntityResource resource = assembler.toResource(prop.propertyValue);
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
if (propertyId.equals(sId)) {
|
||||
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(obj);
|
||||
PersistentEntityResource resource = assembler.toResource(obj);
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
@@ -225,7 +225,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
if (propertyId.equals(sId)) {
|
||||
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(entry.getValue());
|
||||
PersistentEntityResource resource = assembler.toResource(entry.getValue());
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
return result;
|
||||
}
|
||||
|
||||
return resultToResources(result, assembler);
|
||||
return resultToResources(result, assembler, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
|
||||
@@ -39,6 +40,7 @@ public class PersistentEntityResourceAssemblerArgumentResolver implements Handle
|
||||
private final EntityLinks entityLinks;
|
||||
private final ProjectionDefinitions projectionDefinitions;
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private final ResourceMappings mappings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResourceAssemblerArgumentResolver} for the given {@link Repositories},
|
||||
@@ -50,7 +52,7 @@ public class PersistentEntityResourceAssemblerArgumentResolver implements Handle
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntityResourceAssemblerArgumentResolver(Repositories repositories, EntityLinks entityLinks,
|
||||
ProjectionDefinitions projectionDefinitions, ProjectionFactory projectionFactory) {
|
||||
ProjectionDefinitions projectionDefinitions, ProjectionFactory projectionFactory, ResourceMappings mappings) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(entityLinks, "EntityLinks must not be null!");
|
||||
@@ -61,6 +63,7 @@ public class PersistentEntityResourceAssemblerArgumentResolver implements Handle
|
||||
this.entityLinks = entityLinks;
|
||||
this.projectionDefinitions = projectionDefinitions;
|
||||
this.projectionFactory = projectionFactory;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -82,8 +85,8 @@ public class PersistentEntityResourceAssemblerArgumentResolver implements Handle
|
||||
|
||||
String projectionParameter = webRequest.getParameter(projectionDefinitions.getParameterName());
|
||||
PersistentEntityProjector projector = new PersistentEntityProjector(projectionDefinitions, projectionFactory,
|
||||
projectionParameter);
|
||||
projectionParameter, mappings);
|
||||
|
||||
return new PersistentEntityResourceAssembler(repositories, entityLinks, projector);
|
||||
return new PersistentEntityResourceAssembler(repositories, entityLinks, projector, mappings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
|
||||
throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType, converter));
|
||||
}
|
||||
|
||||
return new PersistentEntityResource<Object>(resourceInformation.getPersistentEntity(), obj);
|
||||
return PersistentEntityResource.build(obj, resourceInformation.getPersistentEntity()).build();
|
||||
}
|
||||
|
||||
throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType));
|
||||
|
||||
@@ -75,6 +75,7 @@ import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaCon
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
|
||||
import org.springframework.data.rest.webmvc.support.BackendIdHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.JpaHelper;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
@@ -85,7 +86,6 @@ import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration;
|
||||
import org.springframework.data.web.config.SpringDataJacksonConfiguration;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.ResourceProcessor;
|
||||
@@ -309,7 +309,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
public EntityLinks entityLinks() {
|
||||
public RepositoryEntityLinks entityLinks() {
|
||||
return new RepositoryEntityLinks(repositories(), resourceMappings(), config(), pageableResolver(),
|
||||
backendIdConverterRegistry());
|
||||
}
|
||||
@@ -573,10 +573,15 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
|
||||
|
||||
PersistentEntityResourceAssemblerArgumentResolver peraResolver = new PersistentEntityResourceAssemblerArgumentResolver(
|
||||
repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory));
|
||||
repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory),
|
||||
resourceMappings());
|
||||
|
||||
return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(),
|
||||
repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
HateoasPageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
|
||||
HandlerMethodArgumentResolver defaultedPageableResolver = new DefaultedPageableHandlerMethodArgumentResolver(
|
||||
pageableResolver);
|
||||
|
||||
return Arrays.asList(defaultedPageableResolver, pageableResolver, sortResolver(),
|
||||
serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE,
|
||||
peraResolver, backendIdHandlerMethodArgumentResolver());
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
import org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
@@ -111,7 +113,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class PersistentEntityResourceSerializer extends StdSerializer<PersistentEntityResource<?>> {
|
||||
private static class PersistentEntityResourceSerializer extends StdSerializer<PersistentEntityResource> {
|
||||
|
||||
private final PersistentEntities entities;
|
||||
private final AssociationLinks associationLinks;
|
||||
@@ -140,7 +142,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
|
||||
*/
|
||||
@Override
|
||||
public void serialize(final PersistentEntityResource<?> resource, final JsonGenerator jgen,
|
||||
public void serialize(final PersistentEntityResource resource, final JsonGenerator jgen,
|
||||
final SerializerProvider provider) throws IOException, JsonGenerationException {
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@@ -153,16 +155,28 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
throw new JsonGenerationException(String.format("No self link found resource %s!", resource));
|
||||
}
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
links.addAll(resource.getLinks());
|
||||
|
||||
Path basePath = new Path(id.expand().getHref());
|
||||
LinkCollectingAssociationHandler associationHandler = new LinkCollectingAssociationHandler(entities, basePath,
|
||||
associationLinks);
|
||||
resource.getPersistentEntity().doWithAssociations(associationHandler);
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
links.addAll(resource.getLinks());
|
||||
links.addAll(associationHandler.getLinks());
|
||||
for (Link link : associationHandler.getLinks()) {
|
||||
if (resource.shouldRenderLink(link)) {
|
||||
links.add(link);
|
||||
}
|
||||
}
|
||||
|
||||
Resource<Object> resourceToRender = new Resource<Object>(resource.getContent(), links) {
|
||||
|
||||
@JsonUnwrapped
|
||||
public Resources<?> getEmbedded() {
|
||||
return resource.getEmbeddeds();
|
||||
}
|
||||
};
|
||||
|
||||
Resource<Object> resourceToRender = new Resource<Object>(resource.getContent(), links);
|
||||
provider.defaultSerializeValue(resourceToRender, jgen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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.support;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
/**
|
||||
* Value object to capture a {@link Pageable} as well it is the default one configured.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultedPageable {
|
||||
|
||||
private final Pageable pageable;
|
||||
private final boolean isDefault;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultedPageable} with the given {@link Pageable} and default flag.
|
||||
*
|
||||
* @param pageable can be {@literal null}.
|
||||
* @param isDefault
|
||||
*/
|
||||
DefaultedPageable(Pageable pageable, boolean isDefault) {
|
||||
|
||||
this.pageable = pageable;
|
||||
this.isDefault = isDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delegate {@link Pageable}.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
public Pageable getPageable() {
|
||||
return pageable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the contained {@link Pageable} is the default one configured.
|
||||
*
|
||||
* @return the isDefault
|
||||
*/
|
||||
public boolean isDefault() {
|
||||
return isDefault;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to resolve {@link DefaultedPageable} if requested. Allows to find out whether
|
||||
* the resolved {@link Pageable} is logically identical to the fallback on configured on the delegate
|
||||
* {@link PageableHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultedPageableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final PageableHandlerMethodArgumentResolver resolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultedPageableHandlerMethodArgumentResolver} delegating to the given
|
||||
* {@link PageableHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param resolver must not be {@literal null}.
|
||||
*/
|
||||
public DefaultedPageableHandlerMethodArgumentResolver(PageableHandlerMethodArgumentResolver resolver) {
|
||||
|
||||
Assert.notNull(resolver, "PageableHandlerMethodArgumentResolver must not be null!");
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Pageable pageable = resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
|
||||
return new DefaultedPageable(pageable, resolver.isFallbackPageable(pageable));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return DefaultedPageable.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -30,6 +32,7 @@ public class PersistentEntityProjector implements Projector {
|
||||
private final ProjectionDefinitions projectionDefinitions;
|
||||
private final ProjectionFactory factory;
|
||||
private final String projection;
|
||||
private final ResourceMappings mappings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityProjector} using the given {@link ProjectionDefinitions},
|
||||
@@ -40,7 +43,7 @@ public class PersistentEntityProjector implements Projector {
|
||||
* @param projection can be empty.
|
||||
*/
|
||||
public PersistentEntityProjector(ProjectionDefinitions projectionDefinitions, ProjectionFactory factory,
|
||||
String projection) {
|
||||
String projection, ResourceMappings mappings) {
|
||||
|
||||
Assert.notNull(projectionDefinitions, "ProjectionDefinitions must not be null!");
|
||||
Assert.notNull(factory, "ProjectionFactory must not be null!");
|
||||
@@ -48,6 +51,7 @@ public class PersistentEntityProjector implements Projector {
|
||||
this.projectionDefinitions = projectionDefinitions;
|
||||
this.factory = factory;
|
||||
this.projection = projection;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -56,6 +60,8 @@ public class PersistentEntityProjector implements Projector {
|
||||
*/
|
||||
public Object project(Object source) {
|
||||
|
||||
Assert.notNull(source, "Projection source must not be null!");
|
||||
|
||||
if (!StringUtils.hasText(projection)) {
|
||||
return source;
|
||||
}
|
||||
@@ -63,4 +69,34 @@ public class PersistentEntityProjector implements Projector {
|
||||
Class<?> projectionType = projectionDefinitions.getProjectionType(source.getClass(), projection);
|
||||
return projectionType == null ? source : factory.createProjection(source, projectionType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.Projector#projectExcerpt(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object projectExcerpt(Object source) {
|
||||
|
||||
Assert.notNull(source, "Projection source must not be null!");
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(source.getClass());
|
||||
Class<?> projection = metadata == null ? null : metadata.getExcerptProjection();
|
||||
|
||||
if (projection == null) {
|
||||
return project(source);
|
||||
}
|
||||
|
||||
return projection == null ? source : factory.createProjection(source, projection);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.Projector#hasExcerptProjection(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasExcerptProjection(Class<?> type) {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(type);
|
||||
return metadata == null ? false : metadata.getExcerptProjection() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.springframework.hateoas.ResourceProcessor;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PersistentEntityResourceProcessor implements ResourceProcessor<PersistentEntityResource<?>> {
|
||||
public class PersistentEntityResourceProcessor implements ResourceProcessor<PersistentEntityResource> {
|
||||
|
||||
private final List<DomainTypeResourceProcessor> resourceProcessors = new ArrayList<DomainTypeResourceProcessor>();
|
||||
|
||||
@@ -35,8 +35,10 @@ public class PersistentEntityResourceProcessor implements ResourceProcessor<Pers
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentEntityResource<?> process(PersistentEntityResource<?> resource) {
|
||||
public PersistentEntityResource process(PersistentEntityResource resource) {
|
||||
|
||||
Object content = resource.getContent();
|
||||
|
||||
if (null == content) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -29,5 +29,23 @@ public interface Projector {
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object project(Object source);
|
||||
Object project(Object source);
|
||||
|
||||
/**
|
||||
* Creates a excerpt projection for the given source. If no excerpt projection is available, the call will fall back
|
||||
* to the behavior of {@link #project(Object)}. If you completely wish to skip handling the object, check for the
|
||||
* presence of an excerpt projection using {@link #hasExcerptProjection(Class)}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Object projectExcerpt(Object source);
|
||||
|
||||
/**
|
||||
* Returns whether an excerpt projection has been registered for the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean hasExcerptProjection(Class<?> type);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
@@ -37,7 +38,6 @@ import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.core.AbstractEntityLinks;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -111,20 +111,23 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
return linkFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkToCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Link linkToCollectionResource(Class<?> type) {
|
||||
public Link linkToPagedResource(Class<?> type, Pageable pageable) {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(type);
|
||||
TemplateVariables variables = new TemplateVariables();
|
||||
String href = linkFor(type).withSelfRel().getHref();
|
||||
|
||||
if (metadata.isPagingResource()) {
|
||||
UriComponents components = UriComponentsBuilder.fromUriString(href).build();
|
||||
variables = variables.concat(resolver.getPaginationTemplateVariables(null, components));
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(href);
|
||||
|
||||
if (pageable != null) {
|
||||
resolver.enhance(builder, null, pageable);
|
||||
}
|
||||
|
||||
href = builder.build().toString();
|
||||
|
||||
variables = variables.concat(resolver.getPaginationTemplateVariables(null, builder.build()));
|
||||
}
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
@@ -137,6 +140,15 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
variables), metadata.getRel());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkToCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Link linkToCollectionResource(Class<?> type) {
|
||||
return linkToPagedResource(type, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkToSingleResource(java.lang.Class, java.lang.Object)
|
||||
|
||||
@@ -53,12 +53,8 @@ public abstract class AbstractControllerIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public PersistentEntityResourceAssembler persistentEntityResourceAssembler() {
|
||||
return new PersistentEntityResourceAssembler(repositories(), entityLinks(), new Projector() {
|
||||
@Override
|
||||
public Object project(Object source) {
|
||||
return source;
|
||||
}
|
||||
});
|
||||
return new PersistentEntityResourceAssembler(repositories(), entityLinks(), StubProjector.INSTANCE,
|
||||
resourceMappings());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,4 +97,24 @@ public abstract class AbstractControllerIntegrationTests {
|
||||
protected ResourceMetadata getMetadata(Class<?> domainType) {
|
||||
return mappings.getMappingFor(domainType);
|
||||
}
|
||||
|
||||
private static enum StubProjector implements Projector {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Object project(Object source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object projectExcerpt(Object source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasExcerptProjection(Class<?> type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +260,11 @@ public abstract class AbstractWebIntegrationTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T assertHasJsonPathValue(String path, MockHttpServletResponse response) throws Exception {
|
||||
|
||||
Object jsonPathResult = JsonPath.read(response.getContentAsString(), path);
|
||||
assertThat(jsonPathResult, is(notNullValue()));
|
||||
String content = response.getContentAsString();
|
||||
Object jsonPathResult = JsonPath.read(content, path);
|
||||
|
||||
assertThat(String.format("JSONPath lookup for %s did return null in %s.", path, content), jsonPathResult,
|
||||
is(notNullValue()));
|
||||
|
||||
if (jsonPathResult instanceof JSONArray) {
|
||||
JSONArray array = (JSONArray) jsonPathResult;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.EmbeddedWrapper;
|
||||
import org.springframework.hateoas.core.EmbeddedWrappers;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PersistentEntityResource}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PersistentEntityResourceUnitTests {
|
||||
|
||||
@Mock Object payload;
|
||||
@Mock PersistentEntity<?, ?> entity;
|
||||
|
||||
Resources<EmbeddedWrapper> resources;
|
||||
Link link = new Link("http://localhost", "foo");
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
|
||||
EmbeddedWrapper wrapper = wrappers.wrap("Embedded", "foo");
|
||||
this.resources = new Resources<EmbeddedWrapper>(Collections.singleton(wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullPayload() {
|
||||
PersistentEntityResource.build(null, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullPersistentEntity() {
|
||||
PersistentEntityResource.build(payload, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void defaultsEmbeddedsToEmptyResources() {
|
||||
|
||||
PersistentEntityResource resource = PersistentEntityResource.build(payload, entity).build();
|
||||
|
||||
assertThat(resource.getEmbeddeds(), is(notNullValue()));
|
||||
assertThat(resource.getEmbeddeds(), is(emptyIterable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void doesNotRenderAssociationLinksIfEmbeddedWithRelPresent() {
|
||||
|
||||
PersistentEntityResource resource = PersistentEntityResource.build(payload, entity).//
|
||||
withEmbedded(resources).build();
|
||||
|
||||
assertThat(resource.shouldRenderLink(link), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void rendersAssociationLinksIfEvenIfEmbeddedWithRelPresentButLinksEnforced() {
|
||||
|
||||
PersistentEntityResource resource = PersistentEntityResource.build(payload, entity).//
|
||||
withEmbedded(resources).renderAllAssociationLinks().build();
|
||||
|
||||
assertThat(resource.shouldRenderLink(link), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void rendersAssociationIfNoEmbeddedPresent() {
|
||||
|
||||
PersistentEntityResource resource = PersistentEntityResource.build(payload, entity).build();
|
||||
assertThat(resource.shouldRenderLink(link), is(true));
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,9 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
public void setsExpandedSelfUriInLocationHeader() throws Exception {
|
||||
|
||||
RootResourceInformation information = getResourceInformation(Order.class);
|
||||
PersistentEntityResource<Object> persistentEntityResource = new PersistentEntityResource<Object>(
|
||||
entities.getPersistentEntity(Order.class), new Order(new Person()));
|
||||
|
||||
PersistentEntityResource persistentEntityResource = PersistentEntityResource.build(new Order(new Person()),
|
||||
entities.getPersistentEntity(Order.class)).build();
|
||||
|
||||
ResponseEntity<?> entity = controller.putItemResource(information, persistentEntityResource, 1L, assembler);
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@ import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
@@ -66,7 +66,7 @@ public class RepositoryRestHandlerMappingUnitTests {
|
||||
mockRequest = new MockHttpServletRequest();
|
||||
|
||||
listEntitiesMethod = RepositoryEntityController.class.getMethod("getCollectionResource",
|
||||
RootResourceInformation.class, Pageable.class, Sort.class, PersistentEntityResourceAssembler.class);
|
||||
RootResourceInformation.class, DefaultedPageable.class, Sort.class, PersistentEntityResourceAssembler.class);
|
||||
rootHandlerMethod = RepositoryController.class.getMethod("listRepositories");
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Author {
|
||||
|
||||
@Id @GeneratedValue//
|
||||
Long id;
|
||||
String name;
|
||||
public String name;
|
||||
|
||||
@ManyToMany(mappedBy = "authors")//
|
||||
Set<Book> books = new HashSet<Book>();
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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.rest.core.config.Projection;
|
||||
|
||||
/**
|
||||
* Interface for an excerpt projection for {@link Book}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Projection(name = "excerpt", types = Book.class)
|
||||
public interface BookExcerpt {
|
||||
|
||||
String getTitle();
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
package org.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RepositoryRestResource(excerptProjection = BookExcerpt.class)
|
||||
public interface BookRepository extends CrudRepository<Book, Long> {
|
||||
|
||||
}
|
||||
|
||||
@@ -111,8 +111,9 @@ public class DataRest262Tests {
|
||||
|
||||
JpaPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(AircraftMovement.class);
|
||||
|
||||
Resource<AircraftMovement> resource = PersistentEntityResource.wrap(persistentEntity, movement, new Link(
|
||||
"/api/airports/" + movement.id));
|
||||
Resource<Object> resource = PersistentEntityResource.build(movement, persistentEntity).//
|
||||
withLink(new Link("/api/airports/" + movement.id)).//
|
||||
build();
|
||||
|
||||
String result = mapper.writeValueAsString(resource);
|
||||
|
||||
|
||||
@@ -143,13 +143,12 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
* @see DATAREST-169
|
||||
*/
|
||||
@Test
|
||||
public void exposesCreatorOfAnOrder() throws Exception {
|
||||
public void exposesLinkForRelatedResource() throws Exception {
|
||||
|
||||
MockHttpServletResponse response = request("/");
|
||||
Link ordersLink = assertHasLinkWithRel("orders", response);
|
||||
|
||||
MockHttpServletResponse orders = request(ordersLink);
|
||||
|
||||
Link creatorLink = assertHasContentLinkWithRel("creator", orders);
|
||||
|
||||
assertThat(request(creatorLink), is(notNullValue()));
|
||||
@@ -252,20 +251,12 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void listsSiblingsWithContentCorrectly() throws Exception {
|
||||
|
||||
MockHttpServletResponse response = mvc.perform(get("/people")).andReturn().getResponse();
|
||||
String href = assertHasJsonPathValue(String.format(LINK_TO_SIBLINGS_OF, "John"), response);
|
||||
|
||||
mvc.perform(get(href)).andExpect(status().isOk());
|
||||
assertPersonWithNameAndSiblingLink("John");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listsEmptySiblingsCorrectly() throws Exception {
|
||||
|
||||
MockHttpServletResponse response = mvc.perform(get("/people")).andReturn().getResponse();
|
||||
String href = assertHasJsonPathValue(String.format(LINK_TO_SIBLINGS_OF, "Billy Bob"), response);
|
||||
|
||||
mvc.perform(get(href)).andExpect(status().isOk());
|
||||
assertPersonWithNameAndSiblingLink("Billy Bob");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,9 +310,9 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
Link frodosSiblingsLink = links.get(0);
|
||||
|
||||
putAndGet(frodosSiblingsLink, links.get(1).getHref(), TEXT_URI_LIST);
|
||||
putAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
|
||||
putAndGet(frodosSiblingsLink, links.get(3).getHref(), TEXT_URI_LIST);
|
||||
putAndGet(frodosSiblingsLink, links.get(1).expand().getHref(), TEXT_URI_LIST);
|
||||
putAndGet(frodosSiblingsLink, links.get(2).expand().getHref(), TEXT_URI_LIST);
|
||||
putAndGet(frodosSiblingsLink, links.get(3).expand().getHref(), TEXT_URI_LIST);
|
||||
assertSiblingNames(frodosSiblingsLink, "Pippin");
|
||||
|
||||
patchAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
|
||||
@@ -524,6 +515,31 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
assertThat(JsonPath.<JSONArray> read(responseBody, "$.content"), hasSize(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void rendersExcerptProjectionsCorrectly() throws Exception {
|
||||
|
||||
Link authorsLink = discoverUnique("authors");
|
||||
|
||||
MockHttpServletResponse response = request(authorsLink);
|
||||
String firstAuthorPath = "$._embedded.authors[0]";
|
||||
|
||||
// Has main content
|
||||
assertHasJsonPathValue(firstAuthorPath.concat(".name"), response);
|
||||
|
||||
// Embeddes content of related entity but no link to it
|
||||
assertHasJsonPathValue(firstAuthorPath.concat("._embedded.books[0].title"), response);
|
||||
assertJsonPathDoesntExist(firstAuthorPath.concat("._links.books"), response);
|
||||
|
||||
// Access item resource and expect link to related resource present
|
||||
String content = response.getContentAsString();
|
||||
String href = JsonPath.read(content, firstAuthorPath.concat("._links.self.href"));
|
||||
|
||||
follow(new Link(href)).andExpect(hasLinkWithRel("books"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the {@link Person} resource the given link points to contains siblings with the given names.
|
||||
*
|
||||
@@ -540,6 +556,24 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
assertThat(persons, hasItems(siblingNames));
|
||||
}
|
||||
|
||||
private void assertPersonWithNameAndSiblingLink(String name) throws Exception {
|
||||
|
||||
MockHttpServletResponse response = request(discoverUnique("people"));
|
||||
|
||||
String jsonPath = String.format("$._embedded.people[?(@.firstName == '%s')][0]", name);
|
||||
|
||||
// Assert content inlined
|
||||
Object john = JsonPath.read(response.getContentAsString(), jsonPath);
|
||||
assertThat(john, is(notNullValue()));
|
||||
assertThat(JsonPath.read(john, "$.firstName"), is(notNullValue()));
|
||||
|
||||
// Assert sibling link exposed in resource pointed to
|
||||
Link selfLink = new Link(JsonPath.<String> read(john, "$._links.self.href"));
|
||||
follow(selfLink).//
|
||||
andExpect(status().isOk()).//
|
||||
andExpect(jsonPath("$._links.siblings", is(notNullValue())));
|
||||
}
|
||||
|
||||
private static String toUriList(Link... links) {
|
||||
|
||||
List<String> uris = new ArrayList<String>(links.length);
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
package org.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RepositoryRestResource
|
||||
public interface OrderRepository extends CrudRepository<Order, Long> {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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.rest.core.config.Projection;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Projection(name = "excerpt", types = Person.class)
|
||||
public interface PersonSummary {
|
||||
|
||||
String getFirstName();
|
||||
|
||||
String getLastName();
|
||||
}
|
||||
@@ -114,8 +114,8 @@ public class PersistentEntitySerializationTests {
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(Person.class);
|
||||
Person person = people.save(new Person("John", "Doe"));
|
||||
|
||||
PersistentEntityResource<Person> resource = PersistentEntityResource.wrap(persistentEntity, person, new Link(
|
||||
"/person/" + person.getId()));
|
||||
PersistentEntityResource resource = PersistentEntityResource.build(person, persistentEntity).//
|
||||
withLink(new Link("/person/" + person.getId())).build();
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
mapper.writeValue(writer, resource);
|
||||
@@ -175,17 +175,18 @@ public class PersistentEntitySerializationTests {
|
||||
* @see DATAREST-250
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void serializesEmbeddedReferencesCorrectly() throws Exception {
|
||||
|
||||
User user = new User();
|
||||
user.address = new Address();
|
||||
user.address.street = "Street";
|
||||
|
||||
PersistentEntityResource<User> userResource = new PersistentEntityResource<User>(
|
||||
repositories.getPersistentEntity(User.class), user, new Link("/users/1"));
|
||||
PersistentEntityResource userResource = PersistentEntityResource.//
|
||||
build(user, repositories.getPersistentEntity(User.class)).//
|
||||
withLink(new Link("/users/1")).//
|
||||
build();
|
||||
|
||||
PagedResources<PersistentEntityResource<User>> persistentEntityResource = new PagedResources<PersistentEntityResource<User>>(
|
||||
PagedResources<PersistentEntityResource> persistentEntityResource = new PagedResources<PersistentEntityResource>(
|
||||
Arrays.asList(userResource), new PageMetadata(1, 0, 10));
|
||||
|
||||
String result = mapper.writeValueAsString(persistentEntityResource);
|
||||
@@ -205,12 +206,12 @@ public class PersistentEntitySerializationTests {
|
||||
order.add(new LineItem("first"));
|
||||
order.add(new LineItem("second"));
|
||||
|
||||
PersistentEntityResource<Order> orderResource = new PersistentEntityResource<Order>(
|
||||
repositories.getPersistentEntity(Order.class), order);
|
||||
orderResource.add(new Link("/orders/1"));
|
||||
PersistentEntityResource orderResource = PersistentEntityResource.//
|
||||
build(order, repositories.getPersistentEntity(Order.class)).//
|
||||
withLink(new Link("/orders/1")).//
|
||||
build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
PagedResources<PersistentEntityResource<Order>> persistentEntityResource = new PagedResources<PersistentEntityResource<Order>>(
|
||||
PagedResources<PersistentEntityResource> persistentEntityResource = new PagedResources<PersistentEntityResource>(
|
||||
Arrays.asList(orderResource), new PageMetadata(1, 0, 10));
|
||||
|
||||
String result = mapper.writeValueAsString(persistentEntityResource);
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
public class PersistentEntityProjectorUnitTests {
|
||||
|
||||
@Mock ProjectionFactory factory;
|
||||
@Mock ResourceMappings mappings;
|
||||
ProjectionDefinitionConfiguration configuration;
|
||||
|
||||
@Before
|
||||
@@ -49,7 +51,7 @@ public class PersistentEntityProjectorUnitTests {
|
||||
@Test
|
||||
public void returnsObjectAsIfNoProjectionTypeFound() {
|
||||
|
||||
Projector projector = new PersistentEntityProjector(configuration, factory, "sample");
|
||||
Projector projector = new PersistentEntityProjector(configuration, factory, "sample", mappings);
|
||||
|
||||
Object object = new Object();
|
||||
assertThat(projector.project(object), is(object));
|
||||
@@ -63,7 +65,7 @@ public class PersistentEntityProjectorUnitTests {
|
||||
|
||||
configuration.addProjection(Sample.class, Object.class);
|
||||
|
||||
Projector projector = new PersistentEntityProjector(configuration, factory, "sample");
|
||||
Projector projector = new PersistentEntityProjector(configuration, factory, "sample", mappings);
|
||||
Object source = new Object();
|
||||
projector.project(source);
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.jpa.Book;
|
||||
@@ -45,7 +46,7 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt
|
||||
|
||||
Link link = entityLinks.linkToSingleResource(Person.class, 1);
|
||||
|
||||
assertThat(link.getHref(), endsWith("/people/1"));
|
||||
assertThat(link.getHref(), endsWith("/people/1{?projection}"));
|
||||
assertThat(link.getRel(), is("person"));
|
||||
}
|
||||
|
||||
@@ -78,6 +79,19 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt
|
||||
public void usesCustomGeneratedBackendId() {
|
||||
|
||||
Link link = entityLinks.linkToSingleResource(Book.class, 7L);
|
||||
assertThat(link.getHref(), endsWith("/7-7-7-7-7-7-7"));
|
||||
assertThat(link.expand().getHref(), endsWith("/7-7-7-7-7-7-7"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-317
|
||||
*/
|
||||
@Test
|
||||
public void adaptsToExistingPageable() {
|
||||
|
||||
Link link = entityLinks.linkToPagedResource(Person.class, new PageRequest(0, 10));
|
||||
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasSize(2));
|
||||
assertThat(link.getVariableNames(), hasItems("sort", "projection"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user