DATAREST-1341 - Further API adaption for Spring HATEOAS 1.0.

This commit is contained in:
Oliver Drotbohm
2019-02-15 11:05:05 +01:00
parent ce321da807
commit 554d6cb27b
44 changed files with 423 additions and 401 deletions

View File

@@ -26,7 +26,9 @@ import org.springframework.data.auditing.AuditableBeanWrapperFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
@@ -63,8 +65,8 @@ class AbstractRepositoryRestController {
ResourceMetadata repoMapping = resourceLink.getResourceMetadata();
Link selfLink = resource.getRequiredLink(Link.REL_SELF);
String rel = repoMapping.getItemResourceRel();
Link selfLink = resource.getRequiredLink(IanaLinkRelations.SELF);
LinkRelation rel = repoMapping.getItemResourceRel();
return new Link(selfLink.getHref(), rel);
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.hateoas.core.EmbeddedWrappers;
import org.springframework.util.Assert;
@@ -59,7 +60,7 @@ public class EmbeddedResourcesAssembler {
PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(instance.getClass());
final List<EmbeddedWrapper> associationProjections = new ArrayList<EmbeddedWrapper>();
final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
final PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(instance);
final ResourceMetadata metadata = associations.getMetadataFor(entity.getType());
entity.doWithAssociations((SimpleAssociationHandler) association -> {
@@ -80,7 +81,7 @@ public class EmbeddedResourcesAssembler {
return;
}
String rel = metadata.getMappingFor(property).getRel();
LinkRelation rel = metadata.getMappingFor(property).getRel();
if (value instanceof Collection) {

View File

@@ -25,6 +25,7 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrapper;
@@ -91,7 +92,7 @@ public class PersistentEntityResource extends Resource<Object> {
*
* @return
*/
public PersistentPropertyAccessor getPropertyAccessor() {
public PersistentPropertyAccessor<?> getPropertyAccessor() {
return entity.getPropertyAccessor(getContent());
}
@@ -214,7 +215,7 @@ public class PersistentEntityResource extends Resource<Object> {
*/
@Override
@JsonIgnore
public List<Link> getLinks() {
public Links getLinks() {
return super.getLinks();
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.data.rest.webmvc;
import static org.springframework.http.HttpMethod.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -165,11 +164,11 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
throw new ResourceNotFoundException();
}
List<Link> links = getCollectionResourceLinks(resourceInformation, pageable);
links.add(0, getDefaultSelfLink());
Links links = Links.of(getDefaultSelfLink()) //
.and(getCollectionResourceLinks(resourceInformation, pageable));
HttpHeaders headers = new HttpHeaders();
headers.add(LINK_HEADER, new Links(links).toString());
headers.add(LINK_HEADER, links.toString());
return new ResponseEntity<Object>(headers, HttpStatus.NO_CONTENT);
}
@@ -211,21 +210,18 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
return result;
}
private List<Link> getCollectionResourceLinks(RootResourceInformation resourceInformation,
DefaultedPageable pageable) {
private Links getCollectionResourceLinks(RootResourceInformation resourceInformation, DefaultedPageable pageable) {
ResourceMetadata metadata = resourceInformation.getResourceMetadata();
SearchResourceMappings searchMappings = metadata.getSearchResourceMappings();
List<Link> links = new ArrayList<Link>();
links.add(new Link(ProfileController.getPath(this.config, metadata), ProfileResourceProcessor.PROFILE_REL));
Links links = Links
.of(new Link(ProfileController.getPath(this.config, metadata), ProfileResourceProcessor.PROFILE_REL));
if (searchMappings.isExported()) {
links.add(entityLinks.linkFor(metadata.getDomainType()).slash(searchMappings.getPath())
.withRel(searchMappings.getRel()));
}
return links;
return searchMappings.isExported() //
? links.and(entityLinks.linkFor(metadata.getDomainType()).slash(searchMappings.getPath())
.withRel(searchMappings.getRel()))
: links;
}
@ResponseBody
@@ -237,12 +233,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
Resources<?> resources = getCollectionResource(resourceinformation, pageable, sort, assembler);
List<Link> links = new ArrayList<Link>(resources.getLinks());
Links links = resources.getLinks();
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
PersistentEntityResource persistentEntityResource = (PersistentEntityResource) resource;
links.add(resourceLink(resourceinformation, persistentEntityResource));
links = links.and(resourceLink(resourceinformation, persistentEntityResource));
}
if (resources instanceof PagedResources) {
return new PagedResources<Object>(Collections.emptyList(), ((PagedResources<?>) resources).getMetadata(), links);
} else {
@@ -307,7 +304,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
return getItemResource(resourceInformation, id).map(it -> {
Links links = new Links(assembler.toResource(it).getLinks());
Links links = assembler.toResource(it).getLinks();
HttpHeaders headers = headersPreparer.prepareHeaders(resourceInformation.getPersistentEntity(), it);
headers.add(LINK_HEADER, links.toString());

View File

@@ -57,6 +57,7 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.support.BackendId;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
@@ -294,9 +295,9 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
} else if (prop.property.isMap()) {
Map<String, Object> map = AUGMENTING_METHODS.contains(requestMethod) //
? (Map<String, Object>) prop.propertyValue //
: CollectionFactory.<String, Object> createMap(propertyType, 0);
Map<LinkRelation, Object> map = AUGMENTING_METHODS.contains(requestMethod) //
? (Map<LinkRelation, Object>) prop.propertyValue //
: CollectionFactory.<LinkRelation, Object> createMap(propertyType, 0);
// Add to the existing collection
for (Link l2 : source.getLinks()) {
@@ -314,12 +315,13 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
"Cannot PATCH a reference to this singular property since the property type is not a List or a Map.");
}
if (source.getLinks().size() != 1) {
if (source.getLinks().hasSingleLink()) {
throw new IllegalArgumentException(
"Must send only 1 link to update a property reference that isn't a List or a Map.");
}
prop.accessor.setProperty(prop.property, loadPropertyValue(prop.propertyType, source.getLinks().get(0)));
prop.accessor.setProperty(prop.property,
loadPropertyValue(prop.propertyType, source.getLinks().toList().get(0)));
}
publisher.publishEvent(new BeforeLinkSaveEvent(prop.accessor.getBean(), prop.propertyValue));

View File

@@ -57,6 +57,7 @@ import org.springframework.data.rest.webmvc.json.JacksonMetadata;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.alps.Alps;
import org.springframework.hateoas.alps.Descriptor;
@@ -150,8 +151,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
Type descriptorType = getType(method);
return descriptor().//
id(prefix(method).concat(metadata.getRel())).//
name(metadata.getRel()).//
id(prefix(method).concat(metadata.getRel().value())).//
name(metadata.getRel().value()).//
type(descriptorType).//
doc(getDocFor(metadata.getDescription())).//
rt("#" + representationDescriptor.getId()).//
@@ -176,7 +177,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
Class<?> type = projection.getValue();
String key = String.format("%s.%s.%s", metadata.getRel(), projectionParameterName, projection.getKey());
ResourceDescription fallback = SimpleResourceDescription.defaultFor(key);
ResourceDescription fallback = SimpleResourceDescription.defaultFor(LinkRelation.of(key));
AnnotationBasedResourceDescription projectionDescription = new AnnotationBasedResourceDescription(type, fallback);
projectionDescriptors.add(//
@@ -191,7 +192,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
return descriptor().//
type(Type.SEMANTIC).//
name(projectionParameterName).//
doc(getDocFor(SimpleResourceDescription.defaultFor(projectionParameterName))).//
doc(getDocFor(SimpleResourceDescription.defaultFor(LinkRelation.of(projectionParameterName)))).//
descriptor(projectionDescriptors).build();
}
@@ -204,7 +205,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
AnnotatedMethod getter = definition.getGetter();
Description description = getter.getAnnotation(Description.class);
ResourceDescription fallback = SimpleResourceDescription
.defaultFor(String.format("%s.%s", name, definition.getName()));
.defaultFor(LinkRelation.of(String.format("%s.%s", name, definition.getName())));
ResourceDescription resourceDescription = description == null ? null
: new AnnotationBasedResourceDescription(description, fallback);
@@ -226,8 +227,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
ResourceMetadata metadata = associations.getMetadataFor(entity.getType());
return descriptor().//
id(prefix(method).concat(metadata.getItemResourceRel())).//
name(metadata.getItemResourceRel()).//
id(prefix(method).concat(metadata.getItemResourceRel().value())).//
name(metadata.getItemResourceRel().value()).//
type(getType(method)).//
doc(getDocFor(metadata.getItemResourceDescription())).//
rt("#".concat(representationDescriptor.getId())). //
@@ -275,7 +276,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
continue;
}
ResourceDescription description = SimpleResourceDescription.defaultFor(variable.getDescription());
ResourceDescription description = SimpleResourceDescription
.defaultFor(LinkRelation.of(variable.getDescription()));
descriptors.add(//
descriptor().//
@@ -288,7 +290,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
return descriptors;
}
private List<Descriptor> buildPropertyDescriptors(final Class<?> type, String baseRel) {
private List<Descriptor> buildPropertyDescriptors(final Class<?> type, LinkRelation baseRel) {
final PersistentEntity<?, ?> entity = persistentEntities.getRequiredPersistentEntity(type);
final List<Descriptor> propertyDescriptors = new ArrayList<Descriptor>();
@@ -333,7 +335,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
ResourceMapping mapping = metadata.getMappingFor(property);
DescriptorBuilder builder = descriptor().//
name(mapping.getRel()).doc(getDocFor(mapping.getDescription()));
name(mapping.getRel().value()).doc(getDocFor(mapping.getDescription()));
ResourceMetadata targetTypeMetadata = associations.getMetadataFor(property.getActualType());
@@ -374,7 +376,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
descriptors.add(descriptor().//
type(Type.SAFE).//
name(methodMapping.getRel()).//
name(methodMapping.getRel().value()).//
descriptor(parameterDescriptors).//
build());
}
@@ -421,7 +423,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
}
private static String getRepresentationDescriptorId(ResourceMetadata metadata) {
return metadata.getItemResourceRel().concat("-representation");
return metadata.getItemResourceRel().value().concat("-representation");
}
private static String prefix(HttpMethod method) {

View File

@@ -613,7 +613,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
Object target = value.getTarget();
ResourceMetadata metadata = associations.getMetadataFor(value.getTargetClass());
Links links = metadata.isExported() ? collector.getLinksFor(target) : new Links();
Links links = metadata.isExported() ? collector.getLinksFor(target) : Links.NONE;
Resource<TargetAware> resource = invoker.invokeProcessorsFor(new Resource<TargetAware>(value, links));

View File

@@ -81,8 +81,8 @@ public class LinkCollectingAssociationHandler implements SimpleAssociationHandle
*
* @return the links
*/
public List<Link> getLinks() {
return links;
public Links getLinks() {
return Links.of(links);
}
/*
@@ -96,7 +96,7 @@ public class LinkCollectingAssociationHandler implements SimpleAssociationHandle
if (associations.isLinkableAssociation(property)) {
Links existingLinks = new Links(links);
Links existingLinks = Links.of(links);
for (Link link : associations.getLinksFor(association, basePath)) {
if (existingLinks.hasLink(link.getRel())) {

View File

@@ -35,6 +35,7 @@ import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.util.Assert;
@@ -75,7 +76,7 @@ public class LinkCollector {
* @return
*/
public Links getLinksFor(Object object) {
return getLinksFor(object, Collections.<Link> emptyList());
return getLinksFor(object, Links.NONE);
}
/**
@@ -85,16 +86,15 @@ public class LinkCollector {
* @param existingLinks must not be {@literal null}.
* @return
*/
public Links getLinksFor(Object object, List<Link> existingLinks) {
public Links getLinksFor(Object object, Links existingLinks) {
Assert.notNull(object, "Object must not be null!");
Assert.notNull(existingLinks, "Existing links must not be null!");
Links links = new Links(existingLinks);
Link selfLink = createSelfLink(object, links);
Link selfLink = createSelfLink(object, existingLinks);
if (selfLink == null) {
return links;
return existingLinks;
}
Path path = new Path(selfLink.expand().getHref());
@@ -102,13 +102,10 @@ public class LinkCollector {
LinkCollectingAssociationHandler handler = new LinkCollectingAssociationHandler(entities, path, associationLinks);
entities.getRequiredPersistentEntity(object.getClass()).doWithAssociations(handler);
List<Link> result = new ArrayList<Link>(existingLinks);
result.addAll(handler.getLinks());
return addSelfLinkIfNecessary(object, result);
return addSelfLinkIfNecessary(object, existingLinks.and(handler.getLinks()));
}
public Links getLinksForNested(Object object, List<Link> existing) {
public Links getLinksForNested(Object object, Links existing) {
PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(object.getClass());
@@ -116,35 +113,21 @@ public class LinkCollector {
entity.getPropertyAccessor(object), associationLinks);
entity.doWithAssociations(handler);
List<Link> links = new ArrayList<Link>();
links.addAll(existing);
links.addAll(handler.getLinks());
return new Links(links);
return existing.and(handler.getLinks());
}
private Links addSelfLinkIfNecessary(Object object, List<Link> existing) {
private Links addSelfLinkIfNecessary(Object object, Links existing) {
Links result = new Links(existing);
if (result.hasLink(Link.REL_SELF)) {
return result;
}
List<Link> list = new ArrayList<Link>();
list.add(createSelfLink(object, result));
list.addAll(existing);
return new Links(list);
return existing.hasLink(IanaLinkRelations.SELF) //
? existing //
: Links.of(createSelfLink(object, existing)) //
.and(existing);
}
private Link createSelfLink(Object object, Links existing) {
if (existing.hasLink(Link.REL_SELF)) {
return existing.getLink(Link.REL_SELF).get();
}
return links.createSelfLinkFor(object).withSelfRel();
return existing.getLink(IanaLinkRelations.SELF) //
.orElseGet(() -> links.createSelfLinkFor(object).withSelfRel());
}
/**
@@ -168,8 +151,8 @@ public class LinkCollector {
*
* @return the links
*/
public List<Link> getLinks() {
return links;
public Links getLinks() {
return Links.of(links);
}
/*
@@ -182,7 +165,7 @@ public class LinkCollector {
if (associationLinks.isLinkableAssociation(association)) {
PersistentProperty<?> property = association.getInverse();
Links existingLinks = new Links(links);
Links existingLinks = Links.of(links);
for (Link link : associationLinks.getLinksFor(association, basePath)) {
if (existingLinks.hasLink(link.getRel())) {
@@ -199,7 +182,7 @@ public class LinkCollector {
private static class NestedLinkCollectingAssociationHandler implements SimpleAssociationHandler {
private final SelfLinkProvider selfLinks;
private final PersistentPropertyAccessor accessor;
private final PersistentPropertyAccessor<?> accessor;
private final Associations associations;
private final @Getter List<Link> links = new ArrayList<Link>();

View File

@@ -41,6 +41,7 @@ import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConv
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariable.VariableType;
@@ -181,43 +182,43 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
}
/**
* Creates the link to the search resource with the given rel for a given type.
* Creates the link to the search resource with the given {@link LinkRelation} for a given type.
*
* @param domainType must not be {@literal null}.
* @param rel must not be {@literal null} or empty.
* @param relation must not be {@literal null}.
* @return
* @since 2.3
*/
public Link linkToSearchResource(Class<?> domainType, String rel) {
return getSearchResourceLinkFor(domainType, rel, null, null);
public Link linkToSearchResource(Class<?> domainType, LinkRelation relation) {
return getSearchResourceLinkFor(domainType, relation, null, null);
}
/**
* Creates the link to the search resource with the given rel for a given type. Uses the given {@link Pageable} to
* pre-expand potentially available template variables.
* Creates the link to the search resource with the given {@link LinkRelation} for a given type. Uses the given
* {@link Pageable} to pre-expand potentially available template variables.
*
* @param domainType must not be {@literal null}.
* @param rel must not be {@literal null} or empty.
* @param relation must not be {@literal null}.
* @param pageable can be {@literal null}.
* @return
* @since 2.3
*/
public Link linkToSearchResource(Class<?> domainType, String rel, Pageable pageable) {
return getSearchResourceLinkFor(domainType, rel, pageable, null);
public Link linkToSearchResource(Class<?> domainType, LinkRelation relation, Pageable pageable) {
return getSearchResourceLinkFor(domainType, relation, pageable, null);
}
/**
* Creates the link to the search resource with the given rel for a given type. Uses the given {@link Sort} to
* pre-expand potentially available template variables.
* Creates the link to the search resource with the given {@link LinkRelation} for a given type. Uses the given
* {@link Sort} to pre-expand potentially available template variables.
*
* @param domainType must not be {@literal null}.
* @param rel must not be {@literal null} or empty.
* @param relation must not be {@literal null}.
* @param sort can be {@literal null}.
* @return
* @since 2.3
*/
public Link linkToSearchResource(Class<?> domainType, String rel, Sort sort) {
return getSearchResourceLinkFor(domainType, rel, null, sort);
public Link linkToSearchResource(Class<?> domainType, LinkRelation relation, Sort sort) {
return getSearchResourceLinkFor(domainType, relation, null, sort);
}
/**
@@ -231,31 +232,26 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
*/
private Links linksToSearchResources(Class<?> type, Pageable pageable, Sort sort) {
List<Link> links = new ArrayList<Link>();
SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(type);
for (MethodResourceMapping mapping : searchMappings.getExportedMappings()) {
links.add(getSearchResourceLinkFor(type, mapping.getRel(), pageable, sort));
}
return new Links(links);
return mappings.getSearchResourceMappings(type).getExportedMappings() //
.map(MethodResourceMapping::getRel) //
.map(it -> getSearchResourceLinkFor(type, it, pageable, sort)) //
.collect(Links.collector());
}
/**
* Returns the link pointing to the search resource with the given rel of the given type and pre-expands the
* calculated URi tempalte with the given {@link Pageable} and {@link Sort}.
* Returns the link pointing to the search resource with the given {@link LinkRelation} of the given type and
* pre-expands the calculated URi template with the given {@link Pageable} and {@link Sort}.
*
* @param type must not be {@literal null}.
* @param rel must not be {@literal null} or empty.
* @param rel must not be {@literal null}.
* @param pageable can be {@literal null}.
* @param sort can be {@literal null}.
* @return
*/
private Link getSearchResourceLinkFor(Class<?> type, String rel, Pageable pageable, Sort sort) {
private Link getSearchResourceLinkFor(Class<?> type, LinkRelation rel, Pageable pageable, Sort sort) {
Assert.notNull(type, "Domain type must not be null!");
Assert.hasText(rel, "Relation name must not be null or empty!");
Assert.notNull(rel, "Relation name must not be null!");
SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(type);
MethodResourceMapping mapping = searchMappings.getExportedMethodMappingForRel(rel);

View File

@@ -26,6 +26,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.hateoas.core.EmbeddedWrappers;
@@ -48,7 +49,7 @@ public class PersistentEntityResourceUnitTests {
public void setUp() {
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.wrap("Embedded", "foo");
EmbeddedWrapper wrapper = wrappers.wrap("Embedded", LinkRelation.of("foo"));
this.resources = new Resources<EmbeddedWrapper>(Collections.singleton(wrapper));
}