DATAREST-221 - Added support for projections.
This commit introduces support to access resources via projections, which means naming a dedicated set of properties of the entity to be exposed and being able to refer to that set through a request parameter.
## General usage
Projections are defined as interfaces that mimic the properties of the domain class to be exported:
@Projection(types = Customer.class, name = "summary")
interface Summary {
String getFirstname();
String getLastname();
AddressSummary getAddress();
}
interface AddressSummary() {
String getZipCode();
}
The projection interface can be annotated with @Projection to be auto-discovered. We scan all packages in which we find domain types to be exported for projection types and auto-register them. For manual registration, use RepositoryRestConfiguration.projectionDefinitionConfiguration().addProjection(…) and manually register them.
If a projection is registered for a given type, this will be indicated via a "projection" template variable in the URI pointing to resources with projections. The name of the variable can also be configured on ProjectionDefinitionConfiguration.
## Internals
The projection interfaces are consider bean property delegates by default. This means, that for the above interfaces we will lookup the firstname, lastname and address property of the projection target. In the case of address we re-project the result of the proxy target invocation with a sub-projection onto AddressSummary.
For more advanced use-cases you can annotate a method of the projection interface with @Value and use a SpEL expression to invoke further functionality and return that to be rendered:
interface MyProjection {
@Value("#{@myBean.someMethod(target)}")
SubProjection getValue();
}
This projection would call the someMethod(…) method on a Spring bean named myBean handing the proxy target to the method. The result will be projected in turn onto a type called SubProjection.
As the projection objects are exposed to Jackson as is, they can be annotated with Jackson annotations to further customize the representation.
This commit is contained in:
@@ -25,10 +25,10 @@ import java.util.Locale;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
@@ -57,23 +57,18 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
class AbstractRepositoryRestController implements MessageSourceAware, InitializingBean {
|
||||
class AbstractRepositoryRestController implements MessageSourceAware {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractRepositoryRestController.class);
|
||||
|
||||
private final PersistentEntityResourceAssembler<Object> perAssembler;
|
||||
|
||||
@Autowired(required = false) private ValidationExceptionHandler handler;
|
||||
@Autowired(required = false) private PlatformTransactionManager txMgr;
|
||||
|
||||
private MessageSource messageSource;
|
||||
private PagedResourcesAssembler<Object> assembler;
|
||||
private final PagedResourcesAssembler<Object> pagedResourcesAssembler;
|
||||
private MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
public AbstractRepositoryRestController(PagedResourcesAssembler<Object> assembler,
|
||||
PersistentEntityResourceAssembler<Object> entityResourceAssembler) {
|
||||
|
||||
this.assembler = assembler;
|
||||
this.perAssembler = entityResourceAssembler;
|
||||
public AbstractRepositoryRestController(PagedResourcesAssembler<Object> pagedResourcesAssembler) {
|
||||
this.pagedResourcesAssembler = pagedResourcesAssembler;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -82,18 +77,7 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
*/
|
||||
@Override
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
// FIXME:
|
||||
|
||||
// if (null != txMgr) {
|
||||
// txTmpl = new TransactionTemplate(txMgr);
|
||||
// txTmpl.afterPropertiesSet();
|
||||
// }
|
||||
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ NullPointerException.class })
|
||||
@@ -135,7 +119,7 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
public ResponseEntity handleRepositoryConstraintViolationException(Locale locale,
|
||||
RepositoryConstraintViolationException rcve) {
|
||||
|
||||
return response(null, new RepositoryConstraintViolationExceptionMessage(rcve, messageSource, locale),
|
||||
return response(null, new RepositoryConstraintViolationExceptionMessage(rcve, messageSourceAccessor),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@@ -216,33 +200,33 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected Resources resultToResources(Object result) {
|
||||
protected Resources resultToResources(Object result, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
if (result instanceof Page) {
|
||||
Page<Object> page = (Page<Object>) result;
|
||||
return entitiesToResources(page, assembler);
|
||||
} else if (result instanceof Iterable) {
|
||||
return entitiesToResources((Iterable<Object>) result);
|
||||
return entitiesToResources((Iterable<Object>) result, assembler);
|
||||
} else if (null == result) {
|
||||
return new Resources(EMPTY_RESOURCE_LIST);
|
||||
} else {
|
||||
Resource<Object> resource = perAssembler.toResource(result);
|
||||
Resource<Object> resource = assembler.toResource(result);
|
||||
return new Resources(Collections.singletonList(resource));
|
||||
}
|
||||
}
|
||||
|
||||
protected Resources<? extends Resource<Object>> entitiesToResources(Page<Object> page,
|
||||
PagedResourcesAssembler<Object> assembler) {
|
||||
|
||||
return assembler.toResource(page, perAssembler);
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
return pagedResourcesAssembler.toResource(page, assembler);
|
||||
}
|
||||
|
||||
protected Resources<Resource<Object>> entitiesToResources(Iterable<Object> entities) {
|
||||
protected Resources<Resource<Object>> entitiesToResources(Iterable<Object> entities,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
List<Resource<Object>> resources = new ArrayList<Resource<Object>>();
|
||||
|
||||
for (Object obj : entities) {
|
||||
resources.add(obj == null ? null : perAssembler.toResource(obj));
|
||||
resources.add(obj == null ? null : assembler.toResource(obj));
|
||||
}
|
||||
|
||||
return new Resources<Resource<Object>>(resources);
|
||||
|
||||
@@ -34,8 +34,8 @@ public class PersistentEntityResource<T> extends Resource<T> {
|
||||
|
||||
private final PersistentEntity<?, ?> entity;
|
||||
|
||||
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> entity, T obj) {
|
||||
return new PersistentEntityResource<T>(entity, obj);
|
||||
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> entity, T obj, Link selfLink) {
|
||||
return new PersistentEntityResource<T>(entity, obj, selfLink);
|
||||
}
|
||||
|
||||
public PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Link... links) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -18,32 +18,39 @@ package org.springframework.data.rest.webmvc;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
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.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ResourceAssembler} to create {@link PersistentEntityResource}s for arbitrary domain objects.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistentEntityResourceAssembler<T> implements ResourceAssembler<T, PersistentEntityResource<T>> {
|
||||
public class PersistentEntityResourceAssembler implements ResourceAssembler<Object, PersistentEntityResource<Object>> {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final EntityLinks entityLinks;
|
||||
private final Projector projector;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks) {
|
||||
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector) {
|
||||
|
||||
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!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.entityLinks = entityLinks;
|
||||
this.projector = projector;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -51,22 +58,34 @@ public class PersistentEntityResourceAssembler<T> implements ResourceAssembler<T
|
||||
* @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public PersistentEntityResource<T> toResource(T instance) {
|
||||
public PersistentEntityResource<Object> toResource(Object instance) {
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instance.getClass());
|
||||
|
||||
PersistentEntityResource<T> resource = PersistentEntityResource.wrap(entity, instance);
|
||||
resource.add(getSelfLinkFor(instance));
|
||||
return resource;
|
||||
return PersistentEntityResource.wrap(entity, projector.project(instance), getSelfLinkFor(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the self link for the given domain instance.
|
||||
*
|
||||
* @param instance must be a managed entity, not {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Link getSelfLinkFor(Object instance) {
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instance.getClass());
|
||||
Assert.notNull(instance, "Domain object must not be null!");
|
||||
|
||||
Class<? extends Object> instanceType = instance.getClass();
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(instanceType);
|
||||
|
||||
if (entity == null) {
|
||||
throw new IllegalArgumentException(String.format("Cannot create self link for %s! No persistent entity found!",
|
||||
instanceType));
|
||||
}
|
||||
|
||||
BeanWrapper<?, Object> wrapper = BeanWrapper.create(instance, null);
|
||||
Object id = wrapper.getProperty(entity.getIdProperty());
|
||||
|
||||
return entityLinks.linkForSingleResource(entity.getType(), id).withSelfRel();
|
||||
Link resourceLink = entityLinks.linkToSingleResource(entity.getType(), id);
|
||||
return new Link(resourceLink.getHref(), Link.REL_SELF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -37,11 +37,10 @@ public class RepositoryController extends AbstractRepositoryRestController {
|
||||
private final ResourceMappings mappings;
|
||||
|
||||
@Autowired
|
||||
public RepositoryController(PagedResourcesAssembler<Object> assembler,
|
||||
PersistentEntityResourceAssembler<Object> perAssembler, Repositories repositories, EntityLinks entityLinks,
|
||||
ResourceMappings mappings) {
|
||||
public RepositoryController(PagedResourcesAssembler<Object> assembler, Repositories repositories,
|
||||
EntityLinks entityLinks, ResourceMappings mappings) {
|
||||
|
||||
super(assembler, perAssembler);
|
||||
super(assembler);
|
||||
|
||||
this.repositories = repositories;
|
||||
this.entityLinks = entityLinks;
|
||||
|
||||
@@ -72,7 +72,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
private static final String BASE_MAPPING = "/{repository}";
|
||||
|
||||
private final EntityLinks entityLinks;
|
||||
private final PersistentEntityResourceAssembler<Object> perAssembler;
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final ConversionService conversionService;
|
||||
private final DomainObjectMerger domainObjectMerger;
|
||||
@@ -82,13 +81,11 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
@Autowired
|
||||
public RepositoryEntityController(Repositories repositories, RepositoryRestConfiguration config,
|
||||
EntityLinks entityLinks, PagedResourcesAssembler<Object> assembler,
|
||||
PersistentEntityResourceAssembler<Object> perAssembler,
|
||||
@Qualifier("defaultConversionService") ConversionService conversionService, DomainObjectMerger domainObjectMerger) {
|
||||
|
||||
super(assembler, perAssembler);
|
||||
super(assembler);
|
||||
|
||||
this.entityLinks = entityLinks;
|
||||
this.perAssembler = perAssembler;
|
||||
this.config = config;
|
||||
this.conversionService = conversionService;
|
||||
this.domainObjectMerger = domainObjectMerger;
|
||||
@@ -105,8 +102,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public Resources<?> listEntities(final RootResourceInformation resourceInformation, Pageable pageable, Sort sort)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
public Resources<?> getCollectionResource(final RootResourceInformation resourceInformation, Pageable pageable,
|
||||
Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION);
|
||||
|
||||
@@ -133,7 +131,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
.withRel(searchMappings.getRel()));
|
||||
}
|
||||
|
||||
Resources<?> resources = resultToResources(results);
|
||||
Resources<?> resources = resultToResources(results, assembler);
|
||||
resources.add(links);
|
||||
return resources;
|
||||
}
|
||||
@@ -142,10 +140,11 @@ 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<?> listEntitiesCompact(final RootResourceInformation repoRequest, Pageable pageable, Sort sort)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
public Resources<?> getCollectionResourceCompact(RootResourceInformation repoRequest, Pageable pageable, Sort sort,
|
||||
PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
Resources<?> resources = listEntities(repoRequest, pageable, sort);
|
||||
Resources<?> resources = getCollectionResource(repoRequest, pageable, sort, assembler);
|
||||
List<Link> links = new ArrayList<Link>(resources.getLinks());
|
||||
|
||||
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
|
||||
@@ -170,11 +169,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST)
|
||||
public ResponseEntity<ResourceSupport> postEntity(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<?> payload) throws HttpRequestMethodNotSupportedException {
|
||||
PersistentEntityResource<?> payload, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
|
||||
|
||||
return createAndReturn(payload.getContent(), resourceInformation.getInvoker());
|
||||
return createAndReturn(payload.getContent(), resourceInformation.getInvoker(), assembler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,8 +186,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Resource<?>> getSingleEntity(RootResourceInformation resourceInformation,
|
||||
@PathVariable String id) throws HttpRequestMethodNotSupportedException {
|
||||
public ResponseEntity<Resource<?>> getItemResource(RootResourceInformation resourceInformation,
|
||||
@PathVariable String id, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM);
|
||||
|
||||
@@ -203,7 +204,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
return new ResponseEntity<Resource<?>>(perAssembler.toResource(domainObj), HttpStatus.OK);
|
||||
return new ResponseEntity<Resource<?>>(assembler.toResource(domainObj), HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +218,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT)
|
||||
public ResponseEntity<? extends ResourceSupport> putEntity(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<Object> payload, @PathVariable String id) throws HttpRequestMethodNotSupportedException {
|
||||
PersistentEntityResource<Object> payload, @PathVariable String id, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
|
||||
|
||||
@@ -229,10 +231,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
BeanWrapper<?, Object> incomingWrapper = BeanWrapper.create(payload.getContent(), conversionService);
|
||||
incomingWrapper.setProperty(payload.getPersistentEntity().getIdProperty(), id);
|
||||
|
||||
return createAndReturn(incomingWrapper.getBean(), invoker);
|
||||
return createAndReturn(incomingWrapper.getBean(), invoker, assembler);
|
||||
}
|
||||
|
||||
return mergeAndReturn(payload.getContent(), domainObject, invoker, PUT);
|
||||
return mergeAndReturn(payload.getContent(), domainObject, invoker, PUT, assembler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,8 +249,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PATCH)
|
||||
public ResponseEntity<ResourceSupport> patchEntity(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<Object> payload, @PathVariable String id) throws HttpRequestMethodNotSupportedException,
|
||||
ResourceNotFoundException {
|
||||
PersistentEntityResource<Object> payload, @PathVariable String id, PersistentEntityResourceAssembler assembler)
|
||||
throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PATCH, ResourceType.ITEM);
|
||||
|
||||
@@ -258,7 +260,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
return mergeAndReturn(payload.getContent(), domainObject, resourceInformation.getInvoker(), PATCH);
|
||||
return mergeAndReturn(payload.getContent(), domainObject, resourceInformation.getInvoker(), PATCH, assembler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +306,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @return
|
||||
*/
|
||||
private ResponseEntity<ResourceSupport> mergeAndReturn(Object incoming, Object domainObject,
|
||||
RepositoryInvoker invoker, HttpMethod httpMethod) {
|
||||
RepositoryInvoker invoker, HttpMethod httpMethod, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
NullHandlingPolicy nullPolicy = httpMethod.equals(PATCH) ? IGNORE_NULLS : APPLY_NULLS;
|
||||
domainObjectMerger.merge(incoming, domainObject, nullPolicy);
|
||||
@@ -316,11 +318,11 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
if (PUT.equals(httpMethod)) {
|
||||
headers.setLocation(URI.create(perAssembler.getSelfLinkFor(obj).getHref()));
|
||||
headers.setLocation(URI.create(assembler.getSelfLinkFor(obj).getHref()));
|
||||
}
|
||||
|
||||
if (config.isReturnBodyOnUpdate()) {
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, perAssembler.toResource(obj));
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, assembler.toResource(obj));
|
||||
} else {
|
||||
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT, headers);
|
||||
}
|
||||
@@ -333,16 +335,17 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
* @param invoker
|
||||
* @return
|
||||
*/
|
||||
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, RepositoryInvoker invoker) {
|
||||
private ResponseEntity<ResourceSupport> createAndReturn(Object domainObject, RepositoryInvoker invoker,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
publisher.publishEvent(new BeforeCreateEvent(domainObject));
|
||||
Object savedObject = invoker.invokeSave(domainObject);
|
||||
publisher.publishEvent(new AfterCreateEvent(savedObject));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setLocation(URI.create(perAssembler.getSelfLinkFor(savedObject).getHref()));
|
||||
headers.setLocation(URI.create(assembler.getSelfLinkFor(savedObject).expand().getHref()));
|
||||
|
||||
PersistentEntityResource<Object> resource = config.isReturnBodyOnCreate() ? perAssembler.toResource(savedObject)
|
||||
PersistentEntityResource<Object> resource = config.isReturnBodyOnCreate() ? assembler.toResource(savedObject)
|
||||
: null;
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.CREATED, headers, resource);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
private static final String BASE_MAPPING = "/{repository}/{id}/{property}";
|
||||
|
||||
private final Repositories repositories;
|
||||
private final PersistentEntityResourceAssembler<Object> perAssembler;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
@@ -82,12 +81,11 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
@Autowired
|
||||
public RepositoryPropertyReferenceController(Repositories repositories,
|
||||
@Qualifier("defaultConversionService") ConversionService conversionService,
|
||||
PagedResourcesAssembler<Object> assembler, PersistentEntityResourceAssembler<Object> perAssembler) {
|
||||
PagedResourcesAssembler<Object> assembler) {
|
||||
|
||||
super(assembler, perAssembler);
|
||||
super(assembler);
|
||||
|
||||
this.repositories = repositories;
|
||||
this.perAssembler = perAssembler;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@@ -102,7 +100,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws Exception {
|
||||
@PathVariable String id, @PathVariable String property, final PersistentEntityResourceAssembler assembler)
|
||||
throws Exception {
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@@ -120,7 +119,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
List<Resource<?>> resources = new ArrayList<Resource<?>>();
|
||||
|
||||
for (Object obj : (Iterable<Object>) prop.propertyValue) {
|
||||
resources.add(perAssembler.toResource(obj));
|
||||
resources.add(assembler.toResource(obj));
|
||||
}
|
||||
|
||||
return new Resources<Resource<?>>(resources);
|
||||
@@ -130,14 +129,14 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
Map<Object, Resource<?>> resources = new HashMap<Object, Resource<?>>();
|
||||
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) prop.propertyValue).entrySet()) {
|
||||
resources.put(entry.getKey(), perAssembler.toResource(entry.getValue()));
|
||||
resources.put(entry.getKey(), assembler.toResource(entry.getValue()));
|
||||
}
|
||||
|
||||
return new Resource<Object>(resources);
|
||||
|
||||
} else {
|
||||
|
||||
PersistentEntityResource<Object> resource = perAssembler.toResource(prop.propertyValue);
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(prop.propertyValue);
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
@@ -190,7 +189,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET)
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId) throws Exception {
|
||||
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId,
|
||||
final PersistentEntityResourceAssembler assembler) throws Exception {
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@@ -210,7 +210,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
if (propertyId.equals(sId)) {
|
||||
|
||||
PersistentEntityResource<Object> resource = perAssembler.toResource(obj);
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(obj);
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
@@ -223,7 +223,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
if (propertyId.equals(sId)) {
|
||||
|
||||
PersistentEntityResource<Object> resource = perAssembler.toResource(entry.getValue());
|
||||
PersistentEntityResource<Object> resource = assembler.toResource(entry.getValue());
|
||||
headers.set("Content-Location", resource.getId().getHref());
|
||||
return resource;
|
||||
}
|
||||
@@ -242,9 +242,10 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
|
||||
"application/x-spring-data-compact+json", "text/uri-list" })
|
||||
public ResponseEntity<ResourceSupport> followPropertyReferenceCompact(RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws Exception {
|
||||
@PathVariable String id, @PathVariable String property, PersistentEntityResourceAssembler assembler)
|
||||
throws Exception {
|
||||
|
||||
ResponseEntity<ResourceSupport> response = followPropertyReference(repoRequest, id, property);
|
||||
ResponseEntity<ResourceSupport> response = followPropertyReference(repoRequest, id, property, assembler);
|
||||
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
return response;
|
||||
@@ -259,7 +260,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
ControllerLinkBuilder linkBuilder = linkTo(methodOn(RepositoryPropertyReferenceController.class)
|
||||
.followPropertyReference(repoRequest, id, property));
|
||||
.followPropertyReference(repoRequest, id, property, assembler));
|
||||
|
||||
if (resource instanceof Resource) {
|
||||
|
||||
|
||||
@@ -67,18 +67,17 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositorySearchController} using the given {@link PagedResourcesAssembler},
|
||||
* {@link PersistentEntityResourceAssembler}, {@link EntityLinks} and {@link ResourceMappings}.
|
||||
* {@link EntityLinks} and {@link ResourceMappings}.
|
||||
*
|
||||
* @param assembler must not be {@literal null}.
|
||||
* @param perAssembler must not be {@literal null}.
|
||||
* @param entityLinks must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public RepositorySearchController(PagedResourcesAssembler<Object> assembler,
|
||||
PersistentEntityResourceAssembler<Object> perAssembler, EntityLinks entityLinks, ResourceMappings mappings) {
|
||||
public RepositorySearchController(PagedResourcesAssembler<Object> assembler, EntityLinks entityLinks,
|
||||
ResourceMappings mappings) {
|
||||
|
||||
super(assembler, perAssembler);
|
||||
super(assembler);
|
||||
|
||||
Assert.notNull(entityLinks, "EntityLinks must not be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
@@ -129,10 +128,10 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Resources<?>> executeSearch(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String search, Pageable pageable) {
|
||||
@PathVariable String search, Pageable pageable, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
Resources<?> resources = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable);
|
||||
Resources<?> resources = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable, assembler);
|
||||
|
||||
return new ResponseEntity<Resources<?>>(resources, HttpStatus.OK);
|
||||
}
|
||||
@@ -150,10 +149,12 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, //
|
||||
produces = { "application/x-spring-data-compact+json" })
|
||||
public ResourceSupport executeSearchCompact(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String repository, @PathVariable String search, Pageable pageable) {
|
||||
@PathVariable String repository, @PathVariable String search, Pageable pageable,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
ResourceSupport resource = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable);
|
||||
ResourceSupport resource = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable,
|
||||
assembler);
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
@@ -209,12 +210,12 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
* @return
|
||||
*/
|
||||
private Resources<?> executeQueryMethod(final RepositoryInvoker invoker, WebRequest request, Method method,
|
||||
Pageable pageable) {
|
||||
Pageable pageable, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
Object result = invoker.invokeQueryMethod(method, parameters, pageable, null);
|
||||
|
||||
return resultToResources(result);
|
||||
return resultToResources(result, assembler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RootResourceInformation {
|
||||
public class RootResourceInformation {
|
||||
|
||||
private final ResourceMetadata resourceMetadata;
|
||||
private final RepositoryInvoker invoker;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.projection.ProjectionDefinitions;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
|
||||
import org.springframework.data.rest.webmvc.support.PersistentEntityProjector;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
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 create {@link PersistentEntityResourceAssembler}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistentEntityResourceAssemblerArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final EntityLinks entityLinks;
|
||||
private final ProjectionDefinitions projectionDefinitions;
|
||||
private final ProjectionFactory projectionFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResourceAssemblerArgumentResolver} for the given {@link Repositories},
|
||||
* {@link EntityLinks}, {@link ProjectionDefinitions} and {@link ProjectionFactory}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param entityLinks must not be {@literal null}.
|
||||
* @param projectionDefinitions must not be {@literal null}.
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntityResourceAssemblerArgumentResolver(Repositories repositories, EntityLinks entityLinks,
|
||||
ProjectionDefinitions projectionDefinitions, ProjectionFactory projectionFactory) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(entityLinks, "EntityLinks must not be null!");
|
||||
Assert.notNull(projectionDefinitions, "ProjectionDefinitions must not be null!");
|
||||
Assert.notNull(projectionFactory, "ProjectionFactory must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.entityLinks = entityLinks;
|
||||
this.projectionDefinitions = projectionDefinitions;
|
||||
this.projectionFactory = projectionFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return PersistentEntityResourceAssembler.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (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 {
|
||||
|
||||
String projectionParameter = webRequest.getParameter(projectionDefinitions.getParameterName());
|
||||
PersistentEntityProjector projector = new PersistentEntityProjector(projectionDefinitions, projectionFactory,
|
||||
projectionParameter);
|
||||
|
||||
return new PersistentEntityResourceAssembler(repositories, entityLinks, projector);
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResource;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -18,7 +18,9 @@ package org.springframework.data.rest.webmvc.config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
@@ -34,9 +36,12 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.UriToEntityConverter;
|
||||
import org.springframework.data.rest.core.config.Projection;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor;
|
||||
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
|
||||
@@ -44,15 +49,12 @@ import org.springframework.data.rest.core.invoke.DefaultRepositoryInvokerFactory
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.ResourceDescription;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
|
||||
import org.springframework.data.rest.core.support.DomainObjectMerger;
|
||||
import org.springframework.data.rest.core.util.UUIDConverter;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResourceHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.ResourceMetadataHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformationHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
|
||||
@@ -62,6 +64,9 @@ import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgum
|
||||
import org.springframework.data.rest.webmvc.support.JpaHelper;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;
|
||||
import org.springframework.data.util.AnnotatedTypeScanner;
|
||||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
@@ -123,6 +128,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
RepositoryRestMvcConfiguration.class.getClassLoader());
|
||||
|
||||
@Autowired ListableBeanFactory beanFactory;
|
||||
@Autowired Environment environment;
|
||||
|
||||
@Autowired(required = false) List<ResourceProcessor<?>> resourceProcessors = Collections.emptyList();
|
||||
@Autowired(required = false) RelProvider relProvider;
|
||||
@Autowired(required = false) CurieProvider curieProvider;
|
||||
@@ -187,7 +194,14 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
*/
|
||||
@Bean
|
||||
public RepositoryRestConfiguration config() {
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
|
||||
|
||||
ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration();
|
||||
|
||||
for (Class<?> projection : getProjections()) {
|
||||
configuration.addProjection(projection);
|
||||
}
|
||||
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration(configuration);
|
||||
configureRepositoryRestConfiguration(config);
|
||||
return config;
|
||||
}
|
||||
@@ -275,7 +289,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
@Bean
|
||||
public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() {
|
||||
return new PersistentEntityToJsonSchemaConverter(repositories(), resourceMappings(),
|
||||
resourceDescriptionMessageSourceAccessor());
|
||||
resourceDescriptionMessageSourceAccessor(), entityLinks());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,11 +386,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
return new UriListHttpMessageConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PersistentEntityResourceAssembler<Object> persistentEntityResourceAssembler() {
|
||||
return new PersistentEntityResourceAssembler<Object>(repositories(), entityLinks());
|
||||
}
|
||||
|
||||
/**
|
||||
* Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in the
|
||||
* provided controller classes.
|
||||
@@ -472,10 +481,45 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
return messageConverters;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration#pageableResolver()
|
||||
*/
|
||||
@Bean
|
||||
@Override
|
||||
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
|
||||
|
||||
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
|
||||
resolver.setPageParameterName(config().getPageParamName());
|
||||
resolver.setSizeParameterName(config().getLimitParamName());
|
||||
|
||||
return resolver;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration#sortResolver()
|
||||
*/
|
||||
@Bean
|
||||
@Override
|
||||
public HateoasSortHandlerMethodArgumentResolver sortResolver() {
|
||||
|
||||
HateoasSortHandlerMethodArgumentResolver resolver = super.sortResolver();
|
||||
resolver.setSortParameter(config().getSortParamName());
|
||||
|
||||
return resolver;
|
||||
}
|
||||
|
||||
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
|
||||
return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(),
|
||||
repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE);
|
||||
|
||||
PersistentEntityResourceAssemblerArgumentResolver peraResolver = new PersistentEntityResourceAssemblerArgumentResolver(
|
||||
repositories(), entityLinks(), config().projectionConfiguration(), new ProxyProjectionFactory(beanFactory));
|
||||
|
||||
return Arrays
|
||||
.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(),
|
||||
repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE,
|
||||
peraResolver);
|
||||
}
|
||||
|
||||
private ObjectMapper basicObjectMapper() {
|
||||
@@ -496,6 +540,18 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
return this.relProvider != null ? relProvider : new EvoInflectorRelProvider();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Set<Class<?>> getProjections() {
|
||||
|
||||
Set<String> packagesToScan = new HashSet<String>();
|
||||
|
||||
for (Class<?> domainType : repositories()) {
|
||||
packagesToScan.add(domainType.getPackage().getName());
|
||||
}
|
||||
|
||||
return new AnnotatedTypeScanner(Projection.class).findTypes(packagesToScan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to add additional configuration.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import static org.springframework.util.ClassUtils.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
@@ -33,6 +33,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to create {@link ResourceMetadata} instances.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -42,6 +44,9 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
|
||||
private final ResourceMappings mappings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceMetadataHandlerMethodArgumentResolver} for the given {@link Repositories} and
|
||||
* {@link ResourceMappings}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
*/
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -21,6 +21,7 @@ import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
@@ -29,15 +29,14 @@ 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.Path;
|
||||
import org.springframework.data.rest.core.UriToEntityConverter;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResource;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -99,12 +98,12 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
|
||||
Assert.notNull(converter, "UriToEntityConverter must not be null!");
|
||||
|
||||
addSerializer(new PersistentEntityResourceSerializer(mappings, config));
|
||||
addSerializer(new PersistentEntityResourceSerializer(mappings));
|
||||
setSerializerModifier(new AssociationOmittingSerializerModifier(repositories, mappings, config));
|
||||
setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(repositories, converter, mappings));
|
||||
}
|
||||
|
||||
public static boolean maybeAddAssociationLink(RepositoryLinkBuilder builder, ResourceMappings mappings,
|
||||
public static boolean maybeAddAssociationLink(Path path, ResourceMappings mappings,
|
||||
PersistentProperty<?> persistentProperty, List<Link> links) {
|
||||
|
||||
Assert.isTrue(persistentProperty.isAssociation(), "PersistentProperty must be an association!");
|
||||
@@ -117,7 +116,8 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
ResourceMapping propertyMapping = ownerMetadata.getMappingFor(persistentProperty);
|
||||
|
||||
if (propertyMapping.isExported()) {
|
||||
links.add(builder.slash(propertyMapping.getPath()).withRel(propertyMapping.getRel()));
|
||||
|
||||
links.add(new Link(path.slash(propertyMapping.getPath()).toString(), propertyMapping.getRel()));
|
||||
// This is an association. We added a Link.
|
||||
return true;
|
||||
}
|
||||
@@ -135,25 +135,20 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
private static class PersistentEntityResourceSerializer extends StdSerializer<PersistentEntityResource<?>> {
|
||||
|
||||
private final ResourceMappings mappings;
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResourceSerializer} using the given {@link ResourceMappings} and
|
||||
* {@link RepositoryRestConfiguration}.
|
||||
* Creates a new {@link PersistentEntityResourceSerializer} using the given {@link ResourceMappings}.
|
||||
*
|
||||
* @param mappings must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private PersistentEntityResourceSerializer(ResourceMappings mappings, RepositoryRestConfiguration configuration) {
|
||||
private PersistentEntityResourceSerializer(ResourceMappings mappings) {
|
||||
|
||||
super((Class) PersistentEntityResource.class);
|
||||
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");
|
||||
|
||||
this.mappings = mappings;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -168,19 +163,17 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
LOG.debug("Serializing PersistentEntity " + resource.getPersistentEntity());
|
||||
}
|
||||
|
||||
Object obj = resource.getContent();
|
||||
PersistentEntity<?, ?> entity = resource.getPersistentEntity();
|
||||
BeanWrapper<PersistentEntity<Object, ?>, Object> wrapper = BeanWrapper.create(obj, null);
|
||||
Object entityId = wrapper.getProperty(entity.getIdProperty());
|
||||
ResourceMetadata metadata = mappings.getMappingFor(entity.getType());
|
||||
URI baseUri = configuration.getBaseUri();
|
||||
final Link id = resource.getId();
|
||||
|
||||
if (id == null) {
|
||||
throw new JsonGenerationException(String.format("No self link found resource %s!", resource));
|
||||
}
|
||||
|
||||
final RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, baseUri).slash(entityId);
|
||||
final List<Link> links = new ArrayList<Link>();
|
||||
links.addAll(resource.getLinks());
|
||||
|
||||
// Add associations as links
|
||||
entity.doWithAssociations(new SimpleAssociationHandler() {
|
||||
resource.getPersistentEntity().doWithAssociations(new SimpleAssociationHandler() {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -190,11 +183,11 @@ public class PersistentEntityJackson2Module extends SimpleModule {
|
||||
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
|
||||
|
||||
PersistentProperty<?> property = association.getInverse();
|
||||
maybeAddAssociationLink(builder, mappings, property, links);
|
||||
maybeAddAssociationLink(new Path(id.expand().getHref()), mappings, property, links);
|
||||
}
|
||||
});
|
||||
|
||||
Resource<Object> resourceToRender = new Resource<Object>(obj, links);
|
||||
Resource<Object> resourceToRender = new Resource<Object>(resource.getContent(), links);
|
||||
provider.defaultSerializeValue(resourceToRender, jgen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,14 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.mapping.ResourceDescription;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.json.JsonSchema.ArrayProperty;
|
||||
import org.springframework.data.rest.webmvc.json.JsonSchema.Property;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -57,6 +58,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
private final ResourceMappings mappings;
|
||||
private final Repositories repositories;
|
||||
private final MessageSourceAccessor accessor;
|
||||
private final EntityLinks entityLinks;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityToJsonSchemaConverter} for the given {@link Repositories} and
|
||||
@@ -67,7 +69,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
* @param accessor
|
||||
*/
|
||||
public PersistentEntityToJsonSchemaConverter(Repositories repositories, ResourceMappings mappings,
|
||||
MessageSourceAccessor accessor) {
|
||||
MessageSourceAccessor accessor, EntityLinks entityLinks) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
@@ -75,6 +77,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
this.repositories = repositories;
|
||||
this.mappings = mappings;
|
||||
this.accessor = accessor;
|
||||
this.entityLinks = entityLinks;
|
||||
|
||||
for (Class<?> domainType : repositories) {
|
||||
convertiblePairs.add(new ConvertiblePair(domainType, JsonSchema.class));
|
||||
@@ -111,7 +114,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity((Class<?>) source);
|
||||
final PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity((Class<?>) source);
|
||||
final ResourceMetadata metadata = mappings.getMappingFor(persistentEntity.getType());
|
||||
final JsonSchema jsonSchema = new JsonSchema(persistentEntity.getName(), accessor.getMessage(metadata
|
||||
.getItemResourceDescription()));
|
||||
@@ -159,8 +162,8 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
return;
|
||||
}
|
||||
|
||||
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, null).slash("{id}");
|
||||
maybeAddAssociationLink(builder, mappings, persistentProperty, links);
|
||||
Link link = entityLinks.linkToCollectionResource(persistentEntity.getType());
|
||||
maybeAddAssociationLink(new Path(link.getHref()), mappings, persistentProperty, links);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.rest.core.projection.ProjectionDefinitions;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Projector} looking up a projection by name for the given source type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistentEntityProjector implements Projector {
|
||||
|
||||
private final ProjectionDefinitions projectionDefinitions;
|
||||
private final ProjectionFactory factory;
|
||||
private final String projection;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityProjector} using the given {@link ProjectionDefinitions},
|
||||
* {@link ProjectionFactory} and projection name.
|
||||
*
|
||||
* @param projectionDefinitions must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
* @param projection can be empty.
|
||||
*/
|
||||
public PersistentEntityProjector(ProjectionDefinitions projectionDefinitions, ProjectionFactory factory,
|
||||
String projection) {
|
||||
|
||||
Assert.notNull(projectionDefinitions, "ProjectionDefinitions must not be null!");
|
||||
Assert.notNull(factory, "ProjectionFactory must not be null!");
|
||||
|
||||
this.projectionDefinitions = projectionDefinitions;
|
||||
this.factory = factory;
|
||||
this.projection = projection;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.Projector#project(java.lang.Object)
|
||||
*/
|
||||
public Object project(Object source) {
|
||||
|
||||
if (!StringUtils.hasText(projection)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
Class<?> projectionType = projectionDefinitions.getProjectionType(source.getClass(), projection);
|
||||
return factory.createProjection(source, projectionType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface Projector {
|
||||
|
||||
public Object project(Object source);
|
||||
|
||||
enum NoOpProjector implements Projector {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.support.Projector#project(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object project(Object source) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,8 @@ package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
|
||||
import org.springframework.validation.FieldError;
|
||||
|
||||
@@ -18,22 +17,23 @@ public class RepositoryConstraintViolationExceptionMessage {
|
||||
private final List<ValidationError> errors = new ArrayList<ValidationError>();
|
||||
|
||||
public RepositoryConstraintViolationExceptionMessage(RepositoryConstraintViolationException violationException,
|
||||
MessageSource msgSrc, Locale locale) {
|
||||
MessageSourceAccessor accessor) {
|
||||
|
||||
for (FieldError fieldError : violationException.getErrors().getFieldErrors()) {
|
||||
|
||||
for (FieldError fe : violationException.getErrors().getFieldErrors()) {
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
args.add(fe.getObjectName());
|
||||
args.add(fe.getField());
|
||||
args.add(fe.getRejectedValue());
|
||||
if (null != fe.getArguments()) {
|
||||
for (Object o : fe.getArguments()) {
|
||||
args.add(fieldError.getObjectName());
|
||||
args.add(fieldError.getField());
|
||||
args.add(fieldError.getRejectedValue());
|
||||
if (null != fieldError.getArguments()) {
|
||||
for (Object o : fieldError.getArguments()) {
|
||||
args.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
String msg = msgSrc.getMessage(fe.getCode(), args.toArray(), fe.getDefaultMessage(), locale);
|
||||
this.errors.add(new ValidationError(fe.getObjectName(), msg, String.format("%s", fe.getRejectedValue()), fe
|
||||
.getField()));
|
||||
String message = accessor.getMessage(fieldError.getCode(), args.toArray(), fieldError.getDefaultMessage());
|
||||
this.errors.add(new ValidationError(fieldError.getObjectName(), message, String.format("%s",
|
||||
fieldError.getRejectedValue()), fieldError.getField()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,11 @@ public class RepositoryConstraintViolationExceptionMessage {
|
||||
}
|
||||
|
||||
public static class ValidationError {
|
||||
String entity;
|
||||
String message;
|
||||
String invalidValue;
|
||||
String property;
|
||||
|
||||
private final String entity;
|
||||
private final String message;
|
||||
private final String invalidValue;
|
||||
private final String property;
|
||||
|
||||
public ValidationError(String entity, String message, String invalidValue, String property) {
|
||||
this.entity = entity;
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
@@ -24,6 +27,7 @@ import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.core.AbstractEntityLinks;
|
||||
@@ -105,18 +109,22 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
public Link linkToCollectionResource(Class<?> type) {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(type);
|
||||
TemplateVariables variables = new TemplateVariables();
|
||||
String href = linkFor(type).withSelfRel().getHref();
|
||||
|
||||
if (metadata.isPagingResource()) {
|
||||
|
||||
Link link = linkFor(type).withSelfRel();
|
||||
String href = link.getHref();
|
||||
UriComponents components = UriComponentsBuilder.fromUriString(href).build();
|
||||
TemplateVariables variables = resolver.getPaginationTemplateVariables(null, components);
|
||||
|
||||
return new Link(new UriTemplate(href, variables), metadata.getRel());
|
||||
variables = variables.concat(resolver.getPaginationTemplateVariables(null, components));
|
||||
}
|
||||
|
||||
return linkFor(type).withRel(metadata.getRel());
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
|
||||
if (projectionConfiguration.hasProjectionFor(type)) {
|
||||
variables = variables.concat(new TemplateVariable(projectionConfiguration.getParameterName(), REQUEST_PARAM));
|
||||
}
|
||||
|
||||
return variables.asList().isEmpty() ? linkFor(type).withRel(metadata.getRel()) : new Link(new UriTemplate(href,
|
||||
variables), metadata.getRel());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -127,6 +135,17 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
public Link linkToSingleResource(Class<?> type, Object id) {
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(type);
|
||||
return linkFor(type).slash(id).withRel(metadata.getItemResourceRel());
|
||||
Link link = linkFor(type).slash(id).withRel(metadata.getItemResourceRel());
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
|
||||
if (!projectionConfiguration.hasProjectionFor(type)) {
|
||||
return link;
|
||||
}
|
||||
|
||||
String parameterName = projectionConfiguration.getParameterName();
|
||||
TemplateVariables templateVariables = new TemplateVariables(new TemplateVariable(parameterName, REQUEST_PARAM));
|
||||
UriTemplate template = new UriTemplate(link.getHref(), templateVariables);
|
||||
|
||||
return new Link(template.toString(), metadata.getItemResourceRel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.rest.webmvc;
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
@@ -25,6 +27,7 @@ import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
import org.springframework.data.rest.webmvc.support.Projector;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -40,11 +43,20 @@ import org.springframework.web.context.request.WebRequest;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { RepositoryRestMvcConfiguration.class })
|
||||
@ContextConfiguration
|
||||
public abstract class AbstractControllerIntegrationTests {
|
||||
|
||||
public static final Path BASE = new Path("http://localhost");
|
||||
|
||||
@Configuration
|
||||
static class TestConfiguration extends RepositoryRestMvcConfiguration {
|
||||
|
||||
@Bean
|
||||
public PersistentEntityResourceAssembler persistentEntityResourceAssembler() {
|
||||
return new PersistentEntityResourceAssembler(repositories(), entityLinks(), Projector.NoOpProjector.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired Repositories repositories;
|
||||
@Autowired RepositoryInvokerFactory invokerFactory;
|
||||
@Autowired ResourceMappings mappings;
|
||||
|
||||
@@ -45,7 +45,7 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
repository.save(new Address());
|
||||
|
||||
RootResourceInformation request = getResourceInformation(Address.class);
|
||||
controller.listEntities(request, null, null);
|
||||
controller.getCollectionResource(request, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +56,6 @@ public class RepositoryEntityControllerIntegrationTests extends AbstractControll
|
||||
|
||||
RootResourceInformation request = getResourceInformation(Address.class);
|
||||
|
||||
controller.postEntity(request, null);
|
||||
controller.postEntity(request, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -64,8 +64,8 @@ public class RepositoryRestHandlerMappingUnitTests {
|
||||
|
||||
mockRequest = new MockHttpServletRequest();
|
||||
|
||||
listEntitiesMethod = RepositoryEntityController.class.getMethod("listEntities", RootResourceInformation.class,
|
||||
Pageable.class, Sort.class);
|
||||
listEntitiesMethod = RepositoryEntityController.class.getMethod("getCollectionResource",
|
||||
RootResourceInformation.class, Pageable.class, Sort.class, PersistentEntityResourceAssembler.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
||||
@@ -46,6 +46,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
|
||||
@Autowired TestDataPopulator loader;
|
||||
@Autowired RepositorySearchController controller;
|
||||
@Autowired PersistentEntityResourceAssembler assembler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -86,7 +87,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
|
||||
|
||||
ResponseEntity<Resources<?>> response = controller.executeSearch(resourceInformation, getRequest(parameters),
|
||||
"firstname", null);
|
||||
"firstname", null, assembler);
|
||||
|
||||
ResourceTester tester = ResourceTester.of(response.getBody());
|
||||
PagedResources<Object> pagedResources = tester.assertIsPage();
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -430,6 +431,31 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
andExpect(status().isMethodNotAllowed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, that the server only returns the properties contained in the projection requested.
|
||||
*
|
||||
* @see OrderSummary
|
||||
* @see DATAREST-221
|
||||
*/
|
||||
@Test
|
||||
public void returnsProjectionIfRequested() throws Exception {
|
||||
|
||||
Link orders = discoverUnique("orders");
|
||||
|
||||
MockHttpServletResponse response = request(orders);
|
||||
Link orderLink = assertContentLinkWithRel("self", response, true).expand();
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(orderLink.getHref());
|
||||
String uri = builder.queryParam("projection", "summary").build().toUriString();
|
||||
|
||||
response = mvc.perform(get(uri)). //
|
||||
andExpect(status().isOk()). //
|
||||
andExpect(jsonPath("$.price", is(2.5))).//
|
||||
andReturn().getResponse();
|
||||
|
||||
assertJsonPathDoesntExist("$.lineItems", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the {@link Person} resource the given link points to contains siblings with the given names.
|
||||
*
|
||||
|
||||
@@ -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 java.math.BigDecimal;
|
||||
|
||||
import org.springframework.data.rest.core.config.Projection;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Projection(name = "summary", types = Order.class)
|
||||
public interface OrderSummary {
|
||||
|
||||
BigDecimal getPrice();
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import org.springframework.hateoas.PagedResources.PageMetadata;
|
||||
import org.springframework.hateoas.hal.HalLinkDiscoverer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
@@ -114,8 +113,11 @@ 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()));
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
mapper.writeValue(writer, PersistentEntityResource.wrap(persistentEntity, person));
|
||||
mapper.writeValue(writer, resource);
|
||||
|
||||
String s = writer.toString();
|
||||
|
||||
@@ -180,14 +182,15 @@ public class PersistentEntitySerializationTests {
|
||||
user.address.street = "Street";
|
||||
|
||||
PersistentEntityResource<User> userResource = new PersistentEntityResource<User>(
|
||||
repositories.getPersistentEntity(User.class), user);
|
||||
repositories.getPersistentEntity(User.class), user, new Link("/users/1"));
|
||||
|
||||
PagedResources<PersistentEntityResource<User>> persistentEntityResource = new PagedResources<PersistentEntityResource<User>>(
|
||||
Arrays.asList(userResource), new PageMetadata(1, 0, 10));
|
||||
|
||||
assertThat(
|
||||
mapper.writeValueAsString(persistentEntityResource),
|
||||
is("{\"_embedded\":{\"users\":[{\"address\":{\"street\":\"Street\"}}]},\"page\":{\"size\":1,\"totalElements\":10,\"totalPages\":10,\"number\":0}}"));
|
||||
is("{\"_embedded\":{\"users\":[{\"address\":{\"street\":\"Street\"},\"_links\":{\"self\":{\"href\":\"/users/1\"}}}]},"
|
||||
+ "\"page\":{\"size\":1,\"totalElements\":10,\"totalPages\":10,\"number\":0}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,12 +202,12 @@ public class PersistentEntitySerializationTests {
|
||||
Person creator = new Person("Dave", "Matthews");
|
||||
|
||||
Order order = new Order(creator);
|
||||
ReflectionTestUtils.setField(order, "id", 1L);
|
||||
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"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
PagedResources<PersistentEntityResource<Order>> persistentEntityResource = new PagedResources<PersistentEntityResource<Order>>(
|
||||
@@ -212,7 +215,7 @@ public class PersistentEntitySerializationTests {
|
||||
|
||||
assertThat(mapper.writeValueAsString(persistentEntityResource),
|
||||
is("{\"_embedded\":{\"orders\":[{\"lineItems\":[{\"name\":\"first\"},{\"name\":\"second\"}],\"price\":2.5"
|
||||
+ ",\"_links\":{\"creator\":{\"href\":\"http://localhost:8080/orders/1/creator\"}}}]},\""
|
||||
+ ",\"_links\":{\"self\":{\"href\":\"/orders/1\"},\"creator\":{\"href\":\"/orders/1/creator\"}}}]},\""
|
||||
+ "page\":{\"size\":1,\"totalElements\":10,\"totalPages\":10,\"number\":0}}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.json;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.rest.core.projection.ProjectionFactory;
|
||||
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
|
||||
/**
|
||||
* Integration tests for Jackson marshalling of projected objects.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ProjectionJacksonIntegrationTests {
|
||||
|
||||
ObjectMapper mapper;
|
||||
ProjectionFactory factory = new ProxyProjectionFactory(null);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
this.mapper = new ObjectMapper();
|
||||
this.mapper.registerModule(new Jackson2HalModule());
|
||||
this.mapper.setHandlerInstantiator(new HalHandlerInstantiator(new EvoInflectorRelProvider(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-221
|
||||
*/
|
||||
@Test
|
||||
public void considersJacksonAnnotationsOnProjectionInterfaces() throws Exception {
|
||||
|
||||
Customer customer = new Customer();
|
||||
customer.firstname = "Dave";
|
||||
customer.lastname = "Matthews";
|
||||
customer.address = new Address();
|
||||
|
||||
CustomerProjection projection = factory.createProjection(customer, CustomerProjection.class);
|
||||
|
||||
String result = mapper.writeValueAsString(projection);
|
||||
assertThat(JsonPath.read(result, "$firstname"), is((Object) "Dave"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-221
|
||||
*/
|
||||
@Test
|
||||
public void rendersHalContentCorrectly() throws Exception {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(new EvoInflectorRelProvider(), null));
|
||||
|
||||
Customer customer = new Customer();
|
||||
customer.firstname = "Dave";
|
||||
customer.lastname = "Matthews";
|
||||
customer.address = new Address();
|
||||
|
||||
CustomerProjection projection = factory.createProjection(customer, CustomerProjection.class);
|
||||
Resources<CustomerProjection> resources = new Resources<CustomerProjection>(Arrays.asList(projection));
|
||||
|
||||
String result = mapper.writeValueAsString(resources);
|
||||
|
||||
assertThat(JsonPath.read(result, "$_embedded.customers[0].firstname"), is((Object) "Dave"));
|
||||
}
|
||||
|
||||
static class Customer {
|
||||
String firstname, lastname;
|
||||
Address address;
|
||||
}
|
||||
|
||||
static class Address {
|
||||
|
||||
}
|
||||
|
||||
interface CustomerProjection {
|
||||
|
||||
String getFirstname();
|
||||
|
||||
@JsonIgnore
|
||||
String getLastname();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
|
||||
/**
|
||||
* Integration tests for MongoDB repositories.
|
||||
@@ -94,7 +93,6 @@ public class MongoWebTests extends AbstractWebIntegrationTests {
|
||||
Link usersLink = discoverUnique("users");
|
||||
Link userLink = assertHasContentLinkWithRel("self", request(usersLink));
|
||||
follow(userLink).//
|
||||
andDo(MockMvcResultHandlers.print()). //
|
||||
andExpect(jsonPath("$.address.zipCode").value(is(notNullValue())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.AbstractControllerIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.webmvc.jpa.Order;
|
||||
import org.springframework.data.rest.webmvc.jpa.Person;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -34,6 +36,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
public class RepositoryEntityLinksIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
|
||||
@Autowired RepositoryRestConfiguration configuration;
|
||||
@Autowired RepositoryEntityLinks entityLinks;
|
||||
|
||||
@Test
|
||||
@@ -54,4 +57,16 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt
|
||||
assertThat(link.getVariableNames(), hasItems("page", "size", "sort"));
|
||||
assertThat(link.getRel(), is("people"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-221
|
||||
*/
|
||||
@Test
|
||||
public void returnsLinkWithProjectionTemplateVariableIfProjectionIsDefined() {
|
||||
|
||||
Link link = entityLinks.linkToSingleResource(Order.class, 1);
|
||||
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItem(configuration.projectionConfiguration().getParameterName()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user