DATAREST-93 - More cleanups in code structure.

Removed the need for RepositoryInformationSupport. Refactored Converter implementations to get dependencies injected directly and use constructor injection.

Added new RepositoryInvoker abstraction to avoid reflection for known CrudRepository or PagingAndSortingRepository. Refactored existing RepositoryMethodInvoker to implement the new abstraction.

Refactored AbstractRepositoryRestController and RepositoryEntityContoller to use new mapping API.
This commit is contained in:
Oliver Gierke
2013-07-08 13:27:13 +02:00
parent 86a298efba
commit f8e53058f8
17 changed files with 598 additions and 191 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.config.ResourceMapping;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.repository.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;
@@ -196,8 +196,8 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
}
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMapping entityMapping = repoRequest.getPersistentEntityResourceMapping();
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata entityMapping = repoRequest.getPersistentEntityResourceMapping();
Link selfLink = resource.getLink("self");
String rel = repoMapping.getRel() + "." + entityMapping.getRel();
@@ -205,11 +205,11 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
}
@SuppressWarnings({ "unchecked" })
protected Resources resultToResources(Object result, Link baseLink) {
protected Resources resultToResources(Object result) {
if (result instanceof Page) {
Page<Object> page = (Page<Object>) result;
return entitiesToResources(page, baseLink, assembler);
return entitiesToResources(page, assembler);
} else if (result instanceof Iterable) {
return entitiesToResources((Iterable<Object>) result);
} else if (null == result) {
@@ -220,10 +220,10 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
}
}
protected Resources<? extends Resource<Object>> entitiesToResources(Page<Object> page, Link baseLink,
final PagedResourcesAssembler<Object> assembler) {
protected Resources<? extends Resource<Object>> entitiesToResources(Page<Object> page,
PagedResourcesAssembler<Object> assembler) {
return assembler.toResource(page, perAssembler, baseLink);
return assembler.toResource(page, perAssembler);
}
protected Resources<Resource<Object>> entitiesToResources(Iterable<Object> entities) {

View File

@@ -44,6 +44,7 @@ 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.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
@@ -134,14 +135,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
throw new ResourceNotFoundException();
}
ResourceMapping repoMapping = request.getRepositoryResourceMapping();
ResourceMetadata repoMapping = request.getRepositoryResourceMapping();
if (!repoMethodInvoker.getQueryMethods().isEmpty()) {
links.add(entityLinks.linkForSingleResource(request.getPersistentEntity().getType(), "search").withRel(
repoMapping.getRel() + ".search"));
}
Link baseLink = request.getRepositoryLink();
Resources<?> resources = resultToResources(results, baseLink);
Resources<?> resources = resultToResources(results);
resources.add(links);
return resources;
}

View File

@@ -0,0 +1,63 @@
/*
* 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.springframework.core.MethodParameter;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvokerFactory;
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;
/**
* @author Oliver Gierke
*/
public class RepositoryInvokerHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final RepositoryRestRequestHandlerMethodArgumentResolver requestResolver;
private final RepositoryInvokerFactory invokerFactory;
/**
* @param requestResolver
* @param invokerFactory
*/
private RepositoryInvokerHandlerMethodArgumentResolver(
RepositoryRestRequestHandlerMethodArgumentResolver requestResolver, RepositoryInvokerFactory invokerFactory) {
this.requestResolver = requestResolver;
this.invokerFactory = invokerFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RepositoryInvoker.class.isAssignableFrom(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 {
RepositoryRestRequest request = requestResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
return invokerFactory.getInvokerFor(request.getPersistentEntity().getType());
}
}

View File

@@ -15,19 +15,16 @@
*/
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 java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.core.RepositoryInformation;
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.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.hateoas.Link;
/**
@@ -39,30 +36,22 @@ class RepositoryRestRequest {
private final HttpServletRequest request;
private final URI baseUri;
private final ResourceMapping repoMapping;
private final ResourceMetadata resourceMetadata;
private final Link repoLink;
private final Object repository;
private final RepositoryMethodInvoker repoMethodInvoker;
private final PersistentEntity<?, ?> persistentEntity;
private final ResourceMapping entityMapping;
public RepositoryRestRequest(RepositoryRestConfiguration config, Repositories repositories,
HttpServletRequest request, URI baseUri, RepositoryInformation repoInfo, ConversionService conversionService) {
HttpServletRequest request, URI baseUri, ResourceMetadata repoInfo, ConversionService conversionService) {
this.request = request;
this.baseUri = baseUri;
this.repoMapping = getResourceMapping(config, repoInfo);
if (null == repoMapping || !repoMapping.isExported()) {
this.resourceMetadata = repoInfo;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
this.repoLink = null;
this.repository = null;
this.repoMethodInvoker = null;
this.persistentEntity = null;
this.entityMapping = null;
} else {
this.repoLink = new Link(buildUri(baseUri, repoMapping.getPath()).toString(), repoMapping.getRel());
this.repository = repositories.getRepositoryFor(repoInfo.getDomainType());
this.persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
this.repoMethodInvoker = new RepositoryMethodInvoker(repository, repoInfo, conversionService);
this.entityMapping = getResourceMapping(config, persistentEntity);
}
}
@@ -74,12 +63,8 @@ class RepositoryRestRequest {
return baseUri;
}
ResourceMapping getRepositoryResourceMapping() {
return repoMapping;
}
Link getRepositoryLink() {
return repoLink;
ResourceMetadata getRepositoryResourceMapping() {
return resourceMetadata;
}
RepositoryMethodInvoker getRepositoryMethodInvoker() {
@@ -89,8 +74,4 @@ class RepositoryRestRequest {
PersistentEntity<?, ?> getPersistentEntity() {
return persistentEntity;
}
ResourceMapping getPersistentEntityResourceMapping() {
return entityMapping;
}
}

View File

@@ -16,14 +16,15 @@
package org.springframework.data.rest.webmvc;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -39,25 +40,32 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
@Autowired private RepositoryRestConfiguration config;
@Autowired private Repositories repositories;
@Autowired private RepositoryInformationHandlerMethodArgumentResolver repoInfoResolver;
@Autowired private ResourceMetadataHandlerMethodArgumentResolver repoInfoResolver;
@Autowired private BaseUriMethodArgumentResolver baseUriResolver;
public RepositoryRestRequestHandlerMethodArgumentResolver(ConversionService conversionService) {
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RepositoryRestRequest.class.isAssignableFrom(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,
public RepositoryRestRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
URI baseUri = (URI) baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
RepositoryInformation repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest,
binderFactory);
URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class),
baseUri, repoInfo, conversionService);

View File

@@ -16,12 +16,16 @@
package org.springframework.data.rest.webmvc;
import static org.springframework.util.ClassUtils.*;
import static org.springframework.util.StringUtils.*;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.rest.repository.support.RepositoryInformationSupport;
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.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -32,16 +36,39 @@ import org.springframework.web.util.UrlPathHelper;
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class RepositoryInformationHandlerMethodArgumentResolver extends RepositoryInformationSupport implements
HandlerMethodArgumentResolver {
public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final Repositories repositories;
private final ResourceMappings mappings;
/**
* @param repositories must not be {@literal null}.
* @param mappings must not be {@literal null}.
*/
public ResourceMetadataHandlerMethodArgumentResolver(Repositories repositories, ResourceMappings mappings) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(mappings, "ResourceMappings must not be null!");
this.repositories = repositories;
this.mappings = mappings;
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return isAssignable(parameter.getParameterType(), RepositoryInformation.class);
}
/*
* (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 RepositoryInformation resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
public ResourceMetadata resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
@@ -60,4 +87,20 @@ public class RepositoryInformationHandlerMethodArgumentResolver extends Reposito
return findRepositoryInfoFor(parts[0]);
}
private ResourceMetadata findRepositoryInfoFor(String pathSegment) {
if (!hasText(pathSegment)) {
return null;
}
for (Class<?> domainType : repositories) {
ResourceMetadata mapping = mappings.getMappingFor(domainType);
if (pathSegment.equals(mapping.getPath()) && mapping.isExported()) {
return mapping;
}
}
return null;
}
}

View File

@@ -3,6 +3,7 @@ package org.springframework.data.rest.webmvc.json;
import static org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.*;
import static org.springframework.util.StringUtils.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -11,7 +12,6 @@ import java.util.Set;
import javax.annotation.Nonnull;
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.mapping.Association;
@@ -19,40 +19,53 @@ import org.springframework.data.mapping.AssociationHandler;
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.repository.support.RepositoryInformationSupport;
import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder;
import org.springframework.hateoas.Link;
/**
* @author Jon Brisbin
*/
public class PersistentEntityToJsonSchemaConverter extends RepositoryInformationSupport implements
ConditionalGenericConverter, InitializingBean {
public class PersistentEntityToJsonSchemaConverter implements ConditionalGenericConverter {
private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor SCHEMA_TYPE = TypeDescriptor.valueOf(JsonSchema.class);
private Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
private ResourceMappings mappings;
@Override
public void afterPropertiesSet() throws Exception {
private final Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
private final ResourceMappings mappings;
private final Repositories repositories;
/**
* @param repositories must not be {@literal null}.
* @param mappings must not be {@literal null}.
*/
public PersistentEntityToJsonSchemaConverter(Repositories repositories, ResourceMappings mappings) {
this.repositories = repositories;
this.mappings = mappings;
for (Class<?> domainType : repositories) {
convertiblePairs.add(new ConvertiblePair(domainType, JsonSchema.class));
}
this.mappings = new ResourceMappings(config, repositories);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
*/
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (Class.class.isAssignableFrom(sourceType.getType()) && JsonSchema.class.isAssignableFrom(targetType
.getType()));
return Class.class.isAssignableFrom(sourceType.getType())
&& JsonSchema.class.isAssignableFrom(targetType.getType());
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
*/
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return convertiblePairs;
@@ -68,8 +81,8 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity((Class<?>) source);
final ResourceMetadata metadata = mappings.getMappingFor(persistentEntity.getClass());
String entityDesc = persistentEntity.getType().isAnnotationPresent(Description.class) ? ((Description) persistentEntity
.getType().getAnnotation(Description.class)).value() : null;
String entityDesc = persistentEntity.getType().isAnnotationPresent(Description.class) ? persistentEntity.getType()
.getAnnotation(Description.class).value() : null;
final JsonSchema jsonSchema = new JsonSchema(persistentEntity.getName(), entityDesc);
persistentEntity.doWithProperties(new PropertyHandler() {
@@ -77,10 +90,10 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation
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) || persistentProperty.getGetter()
.isAnnotationPresent(NotNull.class));
boolean notNull = persistentProperty.getField().isAnnotationPresent(Nonnull.class)
|| persistentProperty.getGetter().isAnnotationPresent(Nonnull.class)
|| 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(
Description.class) ? persistentProperty.getGetter().getAnnotation(Description.class).value() : null;
@@ -98,14 +111,21 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation
final List<Link> links = new ArrayList<Link>();
persistentEntity.doWithAssociations(new AssociationHandler() {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.AssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
*/
@Override
public void doWithAssociation(Association association) {
PersistentProperty persistentProperty = association.getInverse();
if (!metadata.isMapped(persistentProperty)) {
return;
}
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, config.getBaseUri()).slash("{id}");
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, URI.create("{id}"));
maybeAddAssociationLink(builder, mappings, persistentProperty, links);
}
});
@@ -114,5 +134,4 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation
return jsonSchema;
}
}