DATAREST-93 - More cleanups.

Merged core and repository modules into core. Renamed some packages for consistency in naming and in preparation to break up some package cycles. Removed @BaseUri and the according resolver. Refactored controllers a bit to have more reusable chunks of code.
This commit is contained in:
Oliver Gierke
2013-07-18 16:39:11 +02:00
parent 0f325bb0a0
commit d2c2ec8262
165 changed files with 1736 additions and 1661 deletions

View File

@@ -33,8 +33,8 @@ import org.springframework.core.convert.ConversionFailedException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.support.ExceptionMessage;
import org.springframework.data.rest.webmvc.support.RepositoryConstraintViolationExceptionMessage;
import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.annotation.BaseURI;
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;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class BaseUriMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final RepositoryRestConfiguration config;
public BaseUriMethodArgumentResolver(RepositoryRestConfiguration config) {
this.config = config;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (null != parameter.getParameterAnnotation(BaseURI.class) && parameter.getParameterType() == URI.class);
}
@Override
public URI resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
// Use configured URI if there is one or set the current one as the default if not.
if (null == config.getBaseUri()) {
URI baseUri = ServletUriComponentsBuilder.fromServletMapping(servletRequest).build().toUri();
config.setBaseUri(baseUri);
}
return config.getBaseUri();
}
}

View File

@@ -19,6 +19,7 @@ import java.util.Collections;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -35,14 +36,14 @@ public class ControllerUtils {
public static final Iterable<Resource<?>> EMPTY_RESOURCE_LIST = Collections.emptyList();
public static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
public static <R extends Resource<?>> ResponseEntity<Resource<?>> toResponseEntity(HttpHeaders headers, R resource,
HttpStatus status) {
public static <R extends ResourceSupport> ResponseEntity<ResourceSupport> toResponseEntity(HttpHeaders headers,
R resource, HttpStatus status) {
HttpHeaders hdrs = new HttpHeaders();
if (null != headers) {
hdrs.putAll(headers);
}
return new ResponseEntity<Resource<?>>(resource, hdrs, status);
return new ResponseEntity<ResourceSupport>(resource, hdrs, status);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import java.util.Arrays;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* A Spring HATEOAS {@link Resource} subclass that holds a reference to the entity's {@link PersistentEntity} metadata.
*
* @author Jon Brisbin
*/
public class PersistentEntityResource<T> extends Resource<T> {
@JsonIgnore private final PersistentEntity<?, ?> entity;
public static <T> PersistentEntityResource<T> wrap(PersistentEntity<?, ?> entity, T obj) {
return new PersistentEntityResource<T>(entity, obj);
}
public PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Link... links) {
this(entity, content, Arrays.asList(links));
}
private PersistentEntityResource(PersistentEntity<?, ?> entity, T content, Iterable<Link> links) {
super(content, links);
this.entity = entity;
}
public PersistentEntity<?, ?> getPersistentEntity() {
return entity;
}
}

View File

@@ -18,7 +18,6 @@ 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.repository.PersistentEntityResource;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceAssembler;

View File

@@ -1,11 +1,11 @@
package org.springframework.data.rest.webmvc;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;

View File

@@ -17,8 +17,8 @@ package org.springframework.data.rest.webmvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -34,23 +34,23 @@ import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterCreateEvent;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
import org.springframework.data.rest.repository.context.AfterSaveEvent;
import org.springframework.data.rest.repository.context.BeforeCreateEvent;
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.mapping.SearchResourceMappings;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.event.AfterCreateEvent;
import org.springframework.data.rest.core.event.AfterDeleteEvent;
import org.springframework.data.rest.core.event.AfterSaveEvent;
import org.springframework.data.rest.core.event.BeforeCreateEvent;
import org.springframework.data.rest.core.event.BeforeDeleteEvent;
import org.springframework.data.rest.core.event.BeforeSaveEvent;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.data.rest.core.support.DomainObjectMerger;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -160,7 +160,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" }, produces = {
"application/json", "text/uri-list" })
public ResponseEntity<Resource<?>> createNewEntity(RepositoryRestRequest repoRequest,
public ResponseEntity<ResourceSupport> createNewEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<?> incoming) {
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
@@ -222,7 +222,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT, consumes = { "application/json" },
produces = { "application/json", "text/uri-list" })
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest request,
public ResponseEntity<? extends ResourceSupport> updateEntity(RepositoryRestRequest request,
PersistentEntityResource<Object> incoming, @PathVariable String id) {
RepositoryInvoker invoker = request.getRepositoryInvoker();

View File

@@ -16,8 +16,8 @@
package org.springframework.data.rest.webmvc;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;

View File

@@ -1,9 +1,9 @@
package org.springframework.data.rest.webmvc;
import org.springframework.hateoas.Resources;
import java.util.Collections;
import org.springframework.hateoas.Resources;
/**
* @author Jon Brisbin
*/

View File

@@ -15,16 +15,15 @@
*/
package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
@@ -35,19 +34,20 @@ import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.core.event.AfterLinkDeleteEvent;
import org.springframework.data.rest.core.event.AfterLinkSaveEvent;
import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent;
import org.springframework.data.rest.core.event.BeforeLinkSaveEvent;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.util.Function;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterLinkDeleteEvent;
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
import org.springframework.data.rest.repository.context.BeforeLinkDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -64,29 +64,26 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Oliver Gierke
*/
@RestController
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings({ "unchecked" })
public class RepositoryPropertyReferenceController extends AbstractRepositoryRestController implements
ApplicationEventPublisherAware {
private static final String BASE_MAPPING = "/{repository}/{id}/{property}";
private final Repositories repositories;
private final RepositoryRestConfiguration config;
private final PersistentEntityResourceAssembler<Object> perAssembler;
private final DomainClassConverter<?> converter;
private ApplicationEventPublisher publisher;
@Autowired
public RepositoryPropertyReferenceController(Repositories repositories, RepositoryRestConfiguration config,
DomainClassConverter<?> domainClassConverter, PagedResourcesAssembler<Object> assembler,
PersistentEntityResourceAssembler<Object> perAssembler) {
public RepositoryPropertyReferenceController(Repositories repositories, DomainClassConverter<?> domainClassConverter,
PagedResourcesAssembler<Object> assembler, PersistentEntityResourceAssembler<Object> perAssembler) {
super(assembler, perAssembler);
this.repositories = repositories;
this.perAssembler = perAssembler;
this.config = config;
this.converter = domainClassConverter;
}
@@ -102,12 +99,15 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public ResponseEntity<Resource<?>> followPropertyReference(final RepositoryRestRequest repoRequest,
public ResponseEntity<ResourceSupport> followPropertyReference(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException {
final HttpHeaders headers = new HttpHeaders();
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
public ResourceSupport apply(ReferencedProperty prop) {
if (null == prop.propertyValue) {
throw new ResourceNotFoundException();
@@ -121,7 +121,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
resources.add(perAssembler.toResource(obj));
}
return new Resource<Object>(resources);
return new Resources<Resource<?>>(resources);
} else if (prop.property.isMap()) {
@@ -141,13 +141,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
}
};
Resource<?> responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(headers, responseResource, HttpStatus.OK);
}
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.DELETE)
@ResponseBody
public ResponseEntity<Resource<?>> deletePropertyReference(final RepositoryRestRequest repoRequest,
public ResponseEntity<? extends ResourceSupport> deletePropertyReference(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException,
HttpRequestMethodNotSupportedException {
@@ -157,12 +158,15 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
if (null == prop.propertyValue) {
return null;
}
if (prop.property.isCollectionLike()) {
throw new IllegalArgumentException(new HttpRequestMethodNotSupportedException("DELETE"));
} else if (prop.property.isMap()) {
@@ -174,6 +178,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
@@ -188,16 +193,19 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json", "application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> followPropertyReference(final RepositoryRestRequest repoRequest,
public ResponseEntity<ResourceSupport> followPropertyReference(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
throws ResourceNotFoundException, NoSuchMethodException {
final HttpHeaders headers = new HttpHeaders();
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
public ResourceSupport apply(ReferencedProperty prop) {
if (null == prop.propertyValue) {
throw new ResourceNotFoundException();
}
@@ -234,46 +242,54 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
};
Resource<?> responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(headers, responseResource, HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> followPropertyReferenceCompact(RepositoryRestRequest repoRequest,
public ResponseEntity<ResourceSupport> followPropertyReferenceCompact(RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException {
ResponseEntity<Resource<?>> response = followPropertyReference(repoRequest, id, property);
ResponseEntity<ResourceSupport> response = followPropertyReference(repoRequest, id, property);
if (response.getStatusCode() != HttpStatus.OK) {
return response;
}
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(property);
ResourceMapping propertyMapping = repoMapping.getMappingFor(persistentProp);
Class<?> propType = persistentProp.isCollectionLike() || persistentProp.isMap() ? persistentProp.getComponentType()
: persistentProp.getType();
ResourceMapping propRepoMapping = getResourceMapping(config, repositories.getRepositoryInformationFor(propType));
String propRel = String.format("%s.%s.%s", repoMapping.getSingleResourceRel(), property, propRepoMapping.getRel());
Resource<?> resource = response.getBody();
ResourceSupport resource = response.getBody();
List<Link> links = new ArrayList<Link>();
URI entityBaseUri = buildUri(repoRequest.getBaseUri(), repoMapping.getPath().toString(), id, property);
ControllerLinkBuilder linkBuilder = linkTo(methodOn(RepositoryPropertyReferenceController.class)
.followPropertyReference(repoRequest, id, property));
if (resource.getContent() instanceof Iterable) {
for (Resource<?> res : (Iterable<Resource<?>>) resource.getContent()) {
Link propLink = propertyReferenceLink(res, entityBaseUri, propRel);
links.add(propLink);
}
} else if (resource.getContent() instanceof Map) {
for (Map.Entry<Object, Resource<?>> entry : ((Map<Object, Resource<?>>) resource.getContent()).entrySet()) {
Link l = new Link(entry.getValue().getLink("self").getHref(), entry.getKey().toString());
links.add(l);
if (resource instanceof Resource) {
Object content = ((Resource<?>) resource).getContent();
if (content instanceof Iterable) {
for (Resource<?> res : (Iterable<Resource<?>>) content) {
links.add(linkBuilder.withRel(propertyMapping.getRel()));
}
} else if (content instanceof Map) {
Map<Object, Resource<?>> map = (Map<Object, Resource<?>>) content;
for (Entry<Object, Resource<?>> entry : map.entrySet()) {
Link l = new Link(entry.getValue().getLink("self").getHref(), entry.getKey().toString());
links.add(l);
}
}
} else {
links.add(new Link(entityBaseUri.toString(), propRel));
links.add(linkBuilder.withRel(propertyMapping.getRel()));
}
return ControllerUtils.toResponseEntity(null, new Resource<Object>(EMPTY_RESOURCE_LIST, links), HttpStatus.OK);
@@ -282,7 +298,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@RequestMapping(value = BASE_MAPPING, method = { RequestMethod.POST, RequestMethod.PUT }, consumes = {
"application/json", "application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> createPropertyReference(final RepositoryRestRequest repoRequest,
public ResponseEntity<? extends ResourceSupport> createPropertyReference(final RepositoryRestRequest repoRequest,
final @RequestBody Resource<Object> incoming, @PathVariable String id, @PathVariable String property)
throws NoSuchMethodException {
@@ -291,38 +307,53 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
if (!invoker.exposesSave()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
public ResourceSupport apply(ReferencedProperty prop) {
if (prop.property.isCollectionLike()) {
Collection<Object> coll = new ArrayList<Object>();
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
coll.addAll((Collection<Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
Object propVal = loadPropertyValue(prop.propertyType, l.getHref());
coll.add(propVal);
}
prop.wrapper.setProperty(prop.property, coll);
} else if (prop.property.isMap()) {
Map<String, Object> m = new HashMap<String, Object>();
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
m.putAll((Map<String, Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
Object propVal = loadPropertyValue(prop.propertyType, l.getHref());
m.put(l.getRel(), propVal);
}
prop.wrapper.setProperty(prop.property, m);
} else {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
throw new IllegalStateException(
"Cannot POST a reference to this singular property since the property type is not a List or a Map.");
}
if (incoming.getLinks().size() != 1) {
throw new IllegalArgumentException(
"Must send only 1 link to update a property reference that isn't a List or a Map.");
}
Object propVal = loadPropertyValue(prop.propertyType, incoming.getLinks().get(0).getHref());
prop.wrapper.setProperty(prop.property, propVal);
}
@@ -330,6 +361,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
publisher.publishEvent(new BeforeLinkSaveEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkSaveEvent(result, prop.propertyValue));
return null;
}
};
@@ -341,7 +373,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.DELETE)
@ResponseBody
public ResponseEntity<Resource<?>> deletePropertyReferenceId(final RepositoryRestRequest repoRequest,
public ResponseEntity<ResourceSupport> deletePropertyReferenceId(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
throws NoSuchMethodException {
@@ -351,12 +383,15 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new NoSuchMethodError();
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
public ResourceSupport apply(ReferencedProperty prop) {
if (null == prop.propertyValue) {
return null;
}
if (prop.property.isCollectionLike()) {
Collection<Object> coll = new ArrayList<Object>();
for (Object obj : (Collection<Object>) prop.propertyValue) {
@@ -384,6 +419,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
@@ -393,28 +429,21 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
}
private Link propertyReferenceLink(Resource<?> resource, URI baseUri, String rel) {
Link selfLink = resource.getLink("self");
String objId = selfLink.getHref().substring(selfLink.getHref().lastIndexOf('/') + 1);
return new Link(buildUri(baseUri, objId).toString(), rel);
}
private Object loadPropertyValue(Class<?> type, String href) {
String id = href.substring(href.lastIndexOf('/') + 1);
return converter.convert(id, STRING_TYPE, TypeDescriptor.valueOf(type));
}
private Resource<?> doWithReferencedProperty(RepositoryRestRequest repoRequest, String id, String propertyPath,
Function<ReferencedProperty, Resource<?>> handler) throws NoSuchMethodException {
private ResourceSupport doWithReferencedProperty(RepositoryRestRequest repoRequest, String id, String propertyPath,
Function<ReferencedProperty, ResourceSupport> handler) throws NoSuchMethodException {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
if (!invoker.exposesFindOne()) {
throw new NoSuchMethodException();
}
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
Object domainObj = invoker.invokeFindOne(id);
if (null == domainObj) {
throw new ResourceNotFoundException();
@@ -444,13 +473,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
this.property = property;
this.propertyValue = propertyValue;
this.wrapper = wrapper;
if (property.isCollectionLike()) {
this.propertyType = property.getComponentType();
} else if (property.isMap()) {
this.propertyType = property.getMapValueType();
} else {
this.propertyType = property.getType();
}
this.propertyType = property.getActualType();
this.entity = repositories.getPersistentEntity(propertyType);
}
}

View File

@@ -12,8 +12,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.support.JpaHelper;
import org.springframework.http.MediaType;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;

View File

@@ -15,13 +15,12 @@
*/
package org.springframework.data.rest.webmvc;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.request.NativeWebRequest;
@@ -32,16 +31,14 @@ import org.springframework.web.context.request.NativeWebRequest;
class RepositoryRestRequest {
private final NativeWebRequest request;
private final URI baseUri;
private final ResourceMetadata resourceMetadata;
private final RepositoryInvoker repoInvoker;
private final PersistentEntity<?, ?> persistentEntity;
public RepositoryRestRequest(PersistentEntity<?, ?> entity, NativeWebRequest request, URI baseUri,
ResourceMetadata repoInfo, RepositoryInvoker invoker) {
public RepositoryRestRequest(PersistentEntity<?, ?> entity, NativeWebRequest request, ResourceMetadata repoInfo,
RepositoryInvoker invoker) {
this.request = request;
this.baseUri = baseUri;
this.resourceMetadata = repoInfo;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
@@ -62,14 +59,18 @@ class RepositoryRestRequest {
return HttpMethod.valueOf(request.getNativeRequest(HttpServletRequest.class).getMethod());
}
URI getBaseUri() {
return baseUri;
Class<?> getDomainType() {
return resourceMetadata.getDomainType();
}
ResourceMetadata getResourceMetadata() {
return resourceMetadata;
}
SearchResourceMappings getSearchMappings() {
return resourceMetadata.getSearchResourceMappings();
}
RepositoryInvoker getRepositoryInvoker() {
return repoInvoker;
}

View File

@@ -15,15 +15,13 @@
*/
package org.springframework.data.rest.webmvc;
import java.net.URI;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
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.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
@@ -39,7 +37,6 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
private final Repositories repositories;
private final RepositoryInvokerFactory invokerFactory;
private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
private final BaseUriMethodArgumentResolver baseUriResolver;
/**
* Creates a new {@link RepositoryRestRequestHandlerMethodArgumentResolver} using the given {@link Repositories} and
@@ -49,8 +46,7 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
* @param conversionService must not be {@literal null}.
*/
public RepositoryRestRequestHandlerMethodArgumentResolver(Repositories repositories,
ConversionService conversionService, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver,
BaseUriMethodArgumentResolver baseUriResolver) {
ConversionService conversionService, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
@@ -58,7 +54,6 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
this.repositories = repositories;
this.invokerFactory = new RepositoryInvokerFactory(repositories, conversionService);
this.resourceMetadataResolver = resourceMetadataResolver;
this.baseUriResolver = baseUriResolver;
}
/*
@@ -78,7 +73,6 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
public RepositoryRestRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = resourceMetadataResolver.resolveArgument(parameter, mavContainer, webRequest,
binderFactory);
@@ -87,6 +81,6 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
// TODO reject if ResourceMetadata cannot be resolved
return new RepositoryRestRequest(persistentEntity, webRequest, baseUri, repoInfo, repositoryInvoker);
return new RepositoryRestRequest(persistentEntity, webRequest, repoInfo, repositoryInvoker);
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -26,63 +25,201 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.mapping.SearchResourceMappings;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
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.core.mapping.SearchResourceMappings;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Controller to lookup and execute searches on a given repository.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
@RestController
class RepositorySearchController extends AbstractRepositoryRestController {
private static final String BASE_MAPPING = "/{repository}/search";
private static final String SEARCH = "/search";
private static final String BASE_MAPPING = "/{repository}" + SEARCH;
private final EntityLinks entityLinks;
private final ResourceMappings mappings;
/**
* Creates a new {@link RepositorySearchController} using the given {@link PagedResourcesAssembler},
* {@link PersistentEntityResourceAssembler}, {@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) {
super(assembler, perAssembler);
Assert.notNull(entityLinks, "EntityLinks must not be null!");
Assert.notNull(mappings, "ResourceMappings must not be null!");
this.entityLinks = entityLinks;
this.mappings = mappings;
}
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-compact+json" })
/**
* Exposes links to the individual search resources exposed by the backing repository.
*
* @param request
* @return
*/
@ResponseBody
public Resource<?> list(RepositoryRestRequest repoRequest) throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
links.addAll(queryMethodLinks(repoRequest.getBaseUri(), repoRequest.getPersistentEntity().getType()));
if (links.isEmpty()) {
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, //
produces = { "application/json", "application/x-spring-data-compact+json" })
public Resource<?> listSearches(RepositoryRestRequest request) {
SearchResourceMappings resourceMappings = request.getSearchMappings();
if (!resourceMappings.isExported()) {
throw new ResourceNotFoundException();
}
return new Resource<Object>(Collections.emptyList(), links);
Links queryMethodLinks = getSearchLinks(request.getDomainType());
if (queryMethodLinks.isEmpty()) {
throw new ResourceNotFoundException();
}
return new Resource<Object>(Collections.emptyList(), queryMethodLinks);
}
protected List<Link> queryMethodLinks(URI baseUri, Class<?> domainType) {
/**
* Executes the search with the given name.
*
* @param request
* @param repository
* @param search
* @param pageable
* @return
* @throws ResourceNotFoundException
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.GET, //
produces = { "application/json", "application/x-spring-data-verbose+json" })
public ResponseEntity<Resources<?>> executeSearch(RepositoryRestRequest request, @PathVariable String search,
Pageable pageable) {
Method method = checkExecutability(request, search);
Resources<?> resources = executeQueryMethod(request, method, pageable);
return new ResponseEntity<Resources<?>>(resources, HttpStatus.OK);
}
/**
* Executes a query method and exposes the results in compact form.
*
* @param repoRequest
* @param repository
* @param method
* @param pageable
* @return
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, //
produces = { "application/x-spring-data-compact+json" })
public ResourceSupport executeSearchCompact(RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String search, Pageable pageable) {
Method method = checkExecutability(repoRequest, search);
ResourceSupport resource = executeQueryMethod(repoRequest, method, pageable);
List<Link> links = new ArrayList<Link>();
LinkBuilder builder = entityLinks.linkFor(domainType).slash("search");
if (resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {
for (Object obj : ((Resources<?>) resource).getContent()) {
if (null != obj && obj instanceof Resource) {
Resource<?> res = (Resource<?>) obj;
links.add(resourceLink(repoRequest, res));
}
}
} else if (resource instanceof Resource) {
Resource<?> res = (Resource<?>) resource;
links.add(resourceLink(repoRequest, res));
}
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
}
/**
* Checks that the given request is actually executable. Will reject execution if we don't find a search with the
* given name.
*
* @param request
* @param searchName
* @return
*/
private Method checkExecutability(RepositoryRestRequest request, String searchName) {
ResourceMetadata metadata = request.getResourceMetadata();
SearchResourceMappings searchMapping = metadata.getSearchResourceMappings();
if (!searchMapping.isExported()) {
throw new ResourceNotFoundException();
}
Method method = searchMapping.getMappedMethod(searchName);
if (method == null) {
throw new ResourceNotFoundException();
}
return method;
}
/**
* @param repoRequest
* @param method
* @param pageable
* @return
*/
private Resources<?> executeQueryMethod(final RepositoryRestRequest repoRequest, Method method, Pageable pageable) {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
Map<String, String[]> parameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(method, parameters, pageable, null);
return resultToResources(result);
}
/**
* Returns {@link Links} to the individual searches exposed.
*
* @param domainType the domain type we want to obtain the search links for.
* @return
*/
private Links getSearchLinks(Class<?> domainType) {
List<Link> links = new ArrayList<Link>();
LinkBuilder builder = entityLinks.linkFor(domainType).slash(SEARCH);
for (ResourceMapping mapping : mappings.getSearchResourceMappings(domainType)) {
@@ -90,62 +227,9 @@ class RepositorySearchController extends AbstractRepositoryRestController {
continue;
}
links.add(builder.slash(mapping.getPath().toString()).withRel(mapping.getRel()));
links.add(builder.slash(mapping.getPath()).withRel(mapping.getRel()));
}
return links;
}
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public ResponseEntity<ResourceSupport> query(final RepositoryRestRequest repoRequest,
@PathVariable String repository, @PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
ResourceMetadata metadata = repoRequest.getResourceMetadata();
SearchResourceMappings searchMapping = metadata.getSearchResourceMappings();
if (searchMapping.isExported()) {
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
Method mappedMethod = searchMapping.getMappedMethod(method);
if (mappedMethod == null) {
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
Map<String, String[]> parameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(mappedMethod, parameters, pageable, null);
return new ResponseEntity<ResourceSupport>(resultToResources(result), HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET,
produces = { "application/x-spring-data-compact+json" })
public ResourceSupport queryCompact(RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) {
List<Link> links = new ArrayList<Link>();
ResponseEntity<ResourceSupport> entity = query(repoRequest, repository, method, pageable);
ResourceSupport resource = entity.getBody();
links.addAll(resource.getLinks());
if (resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {
for (Object obj : ((Resources<?>) resource).getContent()) {
if (null != obj && obj instanceof Resource) {
Resource<?> res = (Resource<?>) obj;
links.add(resourceLink(repoRequest, res));
}
}
} else if (resource instanceof Resource) {
Resource<?> res = (Resource<?>) resource;
links.add(resourceLink(repoRequest, res));
}
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
return new Links(links);
}
}

View File

@@ -23,8 +23,8 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

View File

@@ -1,16 +1,20 @@
package org.springframework.data.rest.webmvc;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Indicates a resource was not found.
*
* @author Jon Brisbin
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 7992904489502842099L;
public ResourceNotFoundException() {
super("Resource not found");
super("Resource not found!");
}
public ResourceNotFoundException(String message) {

View File

@@ -15,6 +15,14 @@
*/
package org.springframework.data.rest.webmvc;
import static org.springframework.data.util.ClassTypeInformation.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -33,14 +41,6 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.springframework.data.util.ClassTypeInformation.from;
/**
* {@link HandlerMethodReturnValueHandler} to post-process the objects returned from controller methods using the
* configured {@link ResourceProcessor}s.

View File

@@ -1,16 +0,0 @@
package org.springframework.data.rest.webmvc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marker annotation to denote which {@link java.net.URI} parameter should be resolved to the request base URI.
*
* @author Jon Brisbin
*/
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseURI {
}

View File

@@ -32,15 +32,14 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.convert.ISO8601DateConverter;
import org.springframework.data.rest.convert.UUIDConverter;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.context.AnnotatedHandlerBeanPostProcessor;
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.rest.webmvc.BaseUriMethodArgumentResolver;
import org.springframework.data.rest.core.UriDomainClassConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.mapping.ResourceMappings;
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.RepositoryRestHandlerAdapter;
@@ -107,7 +106,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(UUIDConverter.INSTANCE);
conversionService.addConverter(ISO8601DateConverter.INSTANCE);
configureConversionService(conversionService);
return conversionService;
}
@@ -185,16 +183,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return new DomainObjectMerger(repositories(), defaultConversionService());
}
/**
* Resolves the base {@link java.net.URI} under which this application is configured.
*
* @return
*/
@Bean
public BaseUriMethodArgumentResolver baseUriMethodArgumentResolver() {
return new BaseUriMethodArgumentResolver(config());
}
/**
* Turns an {@link javax.servlet.http.HttpServletRequest} into a
* {@link org.springframework.http.server.ServerHttpRequest}.
@@ -214,7 +202,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
return new RepositoryRestRequestHandlerMethodArgumentResolver(repositories(), defaultConversionService(),
resourceMetadataHandlerMethodArgumentResolver(), baseUriMethodArgumentResolver());
resourceMetadataHandlerMethodArgumentResolver());
}
@Bean
@@ -378,6 +366,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return er;
}
@Bean
public RepositoryInvokerFactory repositoryInvokerFactory() {
return new RepositoryInvokerFactory(repositories(), defaultConversionService());
}
private List<HttpMessageConverter<?>> defaultMessageConverters() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(jacksonHttpMessageConverter());
@@ -386,8 +379,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
}
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
return Arrays.asList(baseUriMethodArgumentResolver(), pageableResolver(), sortResolver(),
serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(),
repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
resourceMetadataHandlerMethodArgumentResolver());
}

View File

@@ -25,11 +25,11 @@ import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.UriDomainClassConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
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.http.converter.HttpMessageNotReadableException;

View File

@@ -9,7 +9,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.validation.constraints.NotNull;
import org.springframework.core.convert.TypeDescriptor;
@@ -20,9 +19,9 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.annotation.Description;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
import org.springframework.hateoas.Link;
@@ -90,9 +89,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
public void doWithPersistentProperty(PersistentProperty persistentProperty) {
Class<?> propertyType = persistentProperty.getType();
String type = uncapitalize(propertyType.getSimpleName());
boolean notNull = persistentProperty.getField().isAnnotationPresent(Nonnull.class)
|| persistentProperty.getGetter().isAnnotationPresent(Nonnull.class)
|| persistentProperty.getField().isAnnotationPresent(NotNull.class)
boolean notNull = persistentProperty.getField().isAnnotationPresent(NotNull.class)
|| persistentProperty.getGetter().isAnnotationPresent(NotNull.class);
String desc = persistentProperty.getField().isAnnotationPresent(Description.class) ? persistentProperty
.getField().getAnnotation(Description.class).value() : persistentProperty.getGetter().isAnnotationPresent(

View File

@@ -0,0 +1,64 @@
package org.springframework.data.rest.webmvc.support;
import static org.springframework.util.ClassUtils.*;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
/**
* @author Jon Brisbin
*/
public class PersistentEntityResourceProcessor implements ResourceProcessor<PersistentEntityResource<?>> {
private final List<DomainTypeResourceProcessor> resourceProcessors = new ArrayList<DomainTypeResourceProcessor>();
@Autowired
public PersistentEntityResourceProcessor(Repositories repositories,
List<ResourceProcessor<Resource<?>>> resourceProcessors) {
if (null != resourceProcessors) {
for (ResourceProcessor<Resource<?>> rp : resourceProcessors) {
TypeInformation<?> typeInfo = ClassTypeInformation.from(rp.getClass());
TypeInformation<?> domainType = typeInfo.getTypeArguments().get(0);
if (null != repositories.getPersistentEntity(domainType.getType())) {
this.resourceProcessors.add(new DomainTypeResourceProcessor(domainType.getType(), rp));
}
}
}
}
@Override
public PersistentEntityResource<?> process(PersistentEntityResource<?> resource) {
Object content = resource.getContent();
if (null == content) {
return resource;
}
Class<?> domainType = content.getClass();
for (DomainTypeResourceProcessor rp : resourceProcessors) {
if (isAssignable(domainType, rp.domainType)) {
rp.resourceProcessor.process(resource);
}
}
return resource;
}
private static class DomainTypeResourceProcessor {
final Class<?> domainType;
final ResourceProcessor<Resource<?>> resourceProcessor;
private DomainTypeResourceProcessor(Class<?> domainType, ResourceProcessor<Resource<?>> resourceProcessor) {
this.domainType = domainType;
this.resourceProcessor = resourceProcessor;
}
}
}

View File

@@ -4,11 +4,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.context.MessageSource;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.validation.FieldError;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Jon Brisbin
*/

View File

@@ -2,9 +2,9 @@ package org.springframework.data.rest.webmvc.support;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.core.AbstractEntityLinks;

View File

@@ -18,7 +18,7 @@ package org.springframework.data.rest.webmvc.support;
import java.net.URI;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.RepositoryController;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.core.LinkBuilderSupport;

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
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.jpa.JpaRepositoryConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Base class to write integration tests for controllers.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RepositoryRestMvcConfiguration.class, JpaRepositoryConfig.class })
public class AbstractControllerIntegrationTests {
public static final Path BASE = new Path("http://localhost");
@Autowired Repositories repositories;
@Autowired RepositoryInvokerFactory invokerFactory;
@Autowired ResourceMappings mappings;
/**
* Returns a {@link RepositoryRestRequest} for the given domain type.
*
* @param domainType must not be {@literal null}.
* @return
*/
protected RepositoryRestRequest getRequest(Class<?> domainType) {
return getRequest(domainType, RequestParameters.NONE);
}
protected RepositoryRestRequest getRequest(Class<?> domainType, RequestParameters parameters) {
Assert.notNull(domainType, "Domain type must not be null!");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameters(parameters.asMap());
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
return new RepositoryRestRequest(entity, new ServletWebRequest(request), mappings.getMappingFor(domainType),
invokerFactory.getInvokerFor(domainType));
}
protected ResourceMetadata getMetadata(Class<?> domainType) {
return mappings.getMappingFor(domainType);
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.annotation.BaseURI;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class CustomMethodArgumentResolverTests {
static final MethodParameter BASE_URI;
static final MethodParameter PAGE_SORT;
static {
try {
BASE_URI = MethodParameter.forMethodOrConstructor(Methods.class.getDeclaredMethod("baseUri", URI.class), 0);
PAGE_SORT = MethodParameter.forMethodOrConstructor(
Methods.class.getDeclaredMethod("pagingAndSorting", Pageable.class), 0);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
private final RepositoryRestConfiguration config = new RepositoryRestConfiguration().setBaseUri(URI
.create("http://localhost:8080"));
private final BaseUriMethodArgumentResolver baseUriResolver = new BaseUriMethodArgumentResolver(config);
private final PageableHandlerMethodArgumentResolver pageSortResolver = new PageableHandlerMethodArgumentResolver();
private ModelAndViewContainer mavContainer;
@Mock WebDataBinderFactory webDataBinderFactory;
@Before
public void setup() {
mavContainer = new ModelAndViewContainer();
pageSortResolver.setOneIndexedParameters(true);
pageSortResolver.setFallbackPageable(new PageRequest(1, 5));
}
@Test
public void baseUriMethodArgumentResolver() throws Exception {
assertThat("Finds @BaseURI-annotated java.net.URI parameter", baseUriResolver.supportsParameter(BASE_URI), is(true));
// Resolve the base URI
URI baseUri = (URI) baseUriResolver.resolveArgument(BASE_URI, mavContainer, new ServletWebRequest(
Requests.ROOT_REQUEST), webDataBinderFactory);
assertThat("Base URI should be 'http://localhost:8080'", baseUri.toString(), is("http://localhost:8080"));
}
@Test
public void pagingAndSortingMethodArgumentResolver() throws Exception {
assertThat("Finds PagingAndSorting parameter", pageSortResolver.supportsParameter(PAGE_SORT), is(true));
// Resolve Page and Sort information
Pageable pageSort = pageSortResolver.resolveArgument(PAGE_SORT, mavContainer, new ServletWebRequest(
Requests.PAGE_REQUEST), webDataBinderFactory);
assertThat("Finds page parameter value", pageSort.getPageNumber(), is(1));
assertThat("Finds limit parameter value", pageSort.getPageSize(), is(10));
}
static class Methods {
void baseUri(@BaseURI URI baseUri) {}
void pagingAndSorting(Pageable pageSort) {}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.ResourceTester.HasSelfLink;
import org.springframework.data.rest.webmvc.jpa.CreditCard;
import org.springframework.data.rest.webmvc.jpa.Order;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.ResponseEntity;
/**
* Integration tests for the {@link RepositorySearchController}.
*
* @author Oliver Gierke
*/
public class RepositorySearchControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositorySearchController controller;
@Test
public void rendersCorrectSearchLinksForPersons() {
RepositoryRestRequest request = getRequest(Person.class);
Resource<?> resource = controller.listSearches(request);
ResourceTester tester = ResourceTester.of(resource);
tester.assertNumberOfLinks(4);
tester.assertHasLink("findFirstPersonByFirstName", "http://localhost/people/search/findFirstPersonByFirstName");
tester.assertHasLink("firstname", "http://localhost/people/search/firstname");
tester.assertHasLink("findByCreatedUsingISO8601Date",
"http://localhost/people/search/findByCreatedUsingISO8601Date");
tester.assertHasLink("findByCreatedGreaterThan", "http://localhost/people/search/findByCreatedGreaterThan");
}
@Test(expected = ResourceNotFoundException.class)
public void returns404ForUnexportedRepository() {
RepositoryRestRequest request = getRequest(CreditCard.class);
controller.listSearches(request);
}
@Test(expected = ResourceNotFoundException.class)
public void returns404ForRepositoryWithoutSearches() {
RepositoryRestRequest request = getRequest(Order.class);
controller.listSearches(request);
}
@Test
public void executesSearchAgainstRepository() {
RequestParameters parameters = new RequestParameters("firstName", "John");
RepositoryRestRequest request = getRequest(Person.class, parameters);
ResponseEntity<Resources<?>> response = controller.executeSearch(request, "firstname", null);
ResourceTester tester = ResourceTester.of(response.getBody());
PagedResources<Object> pagedResources = tester.assertIsPage();
assertThat(pagedResources.getContent().size(), is(1));
ResourceMetadata metadata = getMetadata(Person.class);
tester.withContentResource(new HasSelfLink(BASE.slash(metadata.getPath()).slash("{id}")));
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
public class RequestParameters {
public static RequestParameters NONE = new RequestParameters();
private final Map<String, String[]> parameters;
public RequestParameters(String key, String... values) {
this(new HashMap<String, String[]>(), key, values);
}
private RequestParameters(Map<String, String[]> parameters, String key, String... values) {
Assert.notNull(parameters, "Parameters must not be null!");
Assert.hasText(key, "Key must not be null or empty!");
this.parameters = new HashMap<String, String[]>(parameters);
this.parameters.put(key, values);
}
private RequestParameters() {
this.parameters = new HashMap<String, String[]>();
}
public RequestParameters and(String key, String... values) {
return new RequestParameters(parameters, key, values);
}
public Map<String, String[]> asMap() {
return Collections.unmodifiableMap(parameters);
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.rest.webmvc.HttpEntityMatcher.*;
import static org.springframework.util.ReflectionUtils.*;
@@ -44,6 +45,7 @@ import org.springframework.hateoas.mvc.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.springframework.data.rest.core.Path;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.util.Assert;
import org.springframework.web.util.UriTemplate;
/**
* Simple wrapper for {@link Resource}s to allow easy assertions on it.
*
* @author Oliver Gierke
*/
public class ResourceTester {
private final ResourceSupport resource;
public static ResourceTester of(Object object) {
assertThat(object, is(instanceOf(ResourceSupport.class)));
return new ResourceTester((ResourceSupport) object);
}
/**
* Creates a new {@link ResourceTester} for the given {@link ResourceSupport}.
*
* @param resource must not be {@literal null}.
*/
private ResourceTester(ResourceSupport resource) {
Assert.notNull(resource, "Resource must not be null!");
this.resource = resource;
}
/**
* Asserts that the {@link Resource} contains the given number of {@link Link}s.
*
* @param number
*/
public void assertNumberOfLinks(int number) {
assertThat(resource.getLinks().size(), is(number));
}
/**
* Asserts that the {@link Resource} has a linke with the given rel and href.
*
* @param rel must not be {@literal null}.
* @param href can be {@literal null}, if so, only the presence of a {@link Link} with the given rel is checked.
*/
public Link assertHasLink(String rel, String href) {
Link link = resource.getLink(rel);
assertThat("Expected link with rel '" + rel + "' but didn't find it in " + resource.getLinks(), link,
is(notNullValue()));
if (href != null) {
assertThat(link.getHref(), is(href));
}
return link;
}
@SuppressWarnings("unchecked")
public <T> PagedResources<T> assertIsPage() {
assertThat(resource, is(instanceOf(PagedResources.class)));
return (PagedResources<T>) resource;
}
public ResourceTester getContentResource() {
assertThat(resource, is(instanceOf(Resources.class)));
Object next = ((Resources<?>) resource).getContent().iterator().next();
assertThat(next, is(instanceOf(ResourceSupport.class)));
return new ResourceTester((ResourceSupport) next);
}
public void withContentResource(ContentResourceHandler handler) {
assertThat(resource, is(instanceOf(Resources.class)));
for (Object element : ((Resources<?>) resource).getContent()) {
assertThat(element, is(instanceOf(ResourceSupport.class)));
handler.doWith(of(element));
}
}
public interface ContentResourceHandler {
void doWith(ResourceTester content);
}
public static class HasSelfLink implements ContentResourceHandler {
private final Path template;
public HasSelfLink(Path template) {
this.template = template;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.ResourceTester.ContentResourceHandler#doWith(org.springframework.data.rest.webmvc.ResourceTester)
*/
@Override
public void doWith(ResourceTester content) {
String href = content.assertHasLink("self", null).getHref();
UriTemplate uriTemplate = new UriTemplate(template.toString());
assertThat(String.format("Expected %s to match %s!", href, uriTemplate.toString()), uriTemplate.matches(href),
is(true));
}
}
}

View File

@@ -17,14 +17,15 @@ package org.springframework.data.rest.webmvc.gemfire;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
/**
* Value object to represent email addresses.
*

View File

@@ -22,7 +22,7 @@ import java.util.Arrays;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
import org.springframework.hateoas.Link;
import org.springframework.mock.web.MockHttpServletResponse;

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2013 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 javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
/**
* @author Oliver Gierke
*/
@Entity
public class Order {
@Id private Long id;
@ManyToOne private Person creator;
public Order(Person creator) {
this.creator = creator;
}
protected Order() {
}
public Long getId() {
return id;
}
public Person getCreator() {
return creator;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.jpa;
import org.springframework.data.repository.CrudRepository;
/**
* @author Oliver Gierke
*/
public interface OrderRepository extends CrudRepository<Order, Long> {
}

View File

@@ -5,6 +5,7 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@@ -13,7 +14,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import org.springframework.data.rest.repository.annotation.Description;
import org.springframework.data.rest.core.annotation.Description;
/**
* An entity that represents a person.

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2013 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.util.Date;
@@ -7,9 +22,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.convert.ISO8601DateConverter;
import org.springframework.data.rest.repository.annotation.ConvertWith;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
/**
* A repository to manage {@link Person}s.
@@ -20,14 +35,13 @@ import org.springframework.data.rest.repository.annotation.RestResource;
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
@RestResource(rel = "firstname", path = "firstname")
public Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
public Person findFirstPersonByFirstName(@Param("firstName") String firstName);
Person findFirstPersonByFirstName(@Param("firstName") String firstName);
public Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
@Query("select p from Person p where p.created > :date")
public Page<Person> findByCreatedUsingISO8601Date(@Param("date") @ConvertWith(ISO8601DateConverter.class) Date date,
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
Pageable pageable);
}

View File

@@ -13,7 +13,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
import org.springframework.hateoas.Link;

View File

@@ -9,9 +9,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.UriDomainClassConverter;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.core.UriDomainClassConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;

View File

@@ -17,7 +17,6 @@ package org.springframework.data.rest.webmvc.mongodb;
import java.net.UnknownHostException;
import com.mongodb.Mongo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@@ -26,6 +25,8 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.Mongo;
/**
* @author Jon Brisbin
*/