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

@@ -1,59 +1,104 @@
/*
* 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.repository;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.rest.repository.support.RepositoryInformationSupport;
import org.springframework.data.repository.support.Repositories;
import org.springframework.util.Assert;
/**
* A {@link ConditionalGenericConverter} that can convert a {@link URI} domain entity.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
public class UriDomainClassConverter extends RepositoryInformationSupport implements ConditionalGenericConverter,
InitializingBean {
public class UriDomainClassConverter implements ConditionalGenericConverter {
private static TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
@Autowired private DomainClassConverter<?> domainClassConverter;
private Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
private final Repositories repositories;
private final DomainClassConverter<?> domainClassConverter;
private final Set<ConvertiblePair> convertiblePairs;
/**
* Creates a new {@link UriDomainClassConverter} using the given {@link Repositories} and {@link DomainClassConverter}
* .
*
* @param repositories must not be {@literal null}.
* @param domainClassConverter must not be {@literal null}.
*/
public UriDomainClassConverter(Repositories repositories, DomainClassConverter<?> domainClassConverter) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(domainClassConverter, "DomainClassConverter must not be null!");
this.repositories = repositories;
this.domainClassConverter = domainClassConverter;
this.convertiblePairs = new HashSet<ConvertiblePair>();
@Override
public void afterPropertiesSet() throws Exception {
for (Class<?> domainType : repositories) {
convertiblePairs.add(new ConvertiblePair(URI.class, domainType));
}
}
/*
* (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 URI.class.isAssignableFrom(sourceType.getType())
&& (null != repositories.getPersistentEntity(targetType.getType()));
&& repositories.getPersistentEntity(targetType.getType()) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
*/
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return convertiblePairs;
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
*/
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(targetType.getType());
if (null == entity || !domainClassConverter.matches(STRING_TYPE, targetType)) {
if (entity != null || !domainClassConverter.matches(STRING_TYPE, targetType)) {
throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException(
"No PersistentEntity information available for " + targetType.getType()));
}
URI uri = (URI) source;
String[] parts = uri.getPath().split("/");
if (parts.length < 2) {
throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException(
"Cannot resolve URI " + uri + ". Is it local or remote? Only local URIs are resolvable."));

View File

@@ -0,0 +1,173 @@
/*
* 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.repository.invoke;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
/**
* @author Oliver Gierke
*/
class CrudRepositoryInvoker implements RepositoryInvoker {
private final CrudRepository<Object, Serializable> repository;
/**
* @param repository must not be {@literal null}.
*/
public CrudRepositoryInvoker(CrudRepository<Object, Serializable> repository) {
this.repository = repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> findAll(Sort sort) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
*/
@Override
public Page<Object> findAll(Pageable pageable) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Override
public <S> S save(S entity) {
return repository.save(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
public <S> Iterable<S> save(Iterable<S> entities) {
return repository.save(entities);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
*/
@Override
public Object findOne(Serializable id) {
return repository.findOne(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
*/
@Override
public boolean exists(Serializable id) {
return repository.exists(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
public Iterable<Object> findAll() {
return repository.findAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
public Iterable<Object> findAll(Iterable<Serializable> ids) {
return repository.findAll(ids);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
*/
@Override
public long count() {
return repository.count();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Override
public void delete(Serializable id) {
repository.delete(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Override
public void delete(Object entity) {
repository.delete(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Override
public void delete(Iterable<? extends Object> entities) {
repository.delete(entities);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#deleteAll()
*/
@Override
public void deleteAll() {
repository.deleteAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#hasFindOne()
*/
@Override
public boolean hasFindOne() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#hasFindAll()
*/
@Override
public boolean hasFindAll() {
return true;
}
}

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.repository.invoke;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author Oliver Gierke
*/
class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
private final PagingAndSortingRepository<Object, Serializable> repository;
/**
* @param repository must not be {@literal null}.
*/
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Serializable> repository) {
super(repository);
this.repository = repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Pageable)
*/
@Override
public Page<Object> findAll(Pageable pageable) {
return repository.findAll(pageable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> findAll(Sort sort) {
return repository.findAll(sort);
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.repository.invoke;
/**
* @author Oliver Gierke
*/
public interface RepositoryInvocationInformation {
boolean hasFindOne();
boolean hasFindAll();
}

View File

@@ -0,0 +1,28 @@
/*
* 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.repository.invoke;
import java.io.Serializable;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author Oliver Gierke
*/
public interface RepositoryInvoker extends PagingAndSortingRepository<Object, Serializable>,
RepositoryInvocationInformation {
}

View File

@@ -0,0 +1,68 @@
/*
* 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.repository.invoke;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.support.Repositories;
/**
* @author Oliver Gierke
*/
public class RepositoryInvokerFactory {
private final Repositories repositories;
private final Map<Class<?>, RepositoryInvoker> invokers;
/**
* @param repositories
* @param invokers
*/
public RepositoryInvokerFactory(Repositories repositories) {
this.repositories = repositories;
this.invokers = new HashMap<Class<?>, RepositoryInvoker>();
prepareInvokers(repositories);
}
@SuppressWarnings("unchecked")
private final void prepareInvokers(Repositories repositories) {
for (Class<?> domainType : repositories) {
Object repository = repositories.getRepositoryFor(domainType);
RepositoryInvoker invoker = null;
if (repository instanceof PagingAndSortingRepository) {
invoker = new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository<Object, Serializable>) repository);
} else if (repository instanceof CrudRepository) {
invoker = new CrudRepositoryInvoker((CrudRepository<Object, Serializable>) repository);
} else {
invoker = new RepositoryMethodInvoker(repository, null, null);
}
invokers.put(domainType, invoker);
}
}
public RepositoryInvoker getInvokerFor(Class<?> domainType) {
return invokers.get(domainType);
}
}

View File

@@ -13,7 +13,6 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
@@ -22,7 +21,7 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public class RepositoryMethodInvoker implements PagingAndSortingRepository<Object, Serializable> {
public class RepositoryMethodInvoker implements RepositoryInvoker {
private final Object repository;
private final Map<String, RepositoryMethod> queryMethods = new HashMap<String, RepositoryMethod>();
@@ -57,11 +56,11 @@ public class RepositoryMethodInvoker implements PagingAndSortingRepository<Objec
}
String name = method.getName();
int cardinality = method.getParameterTypes().length;
Class<?> paramType = (cardinality == 1 ? method.getParameterTypes()[0] : null);
boolean someMethod = (null != paramType && Iterable.class.isAssignableFrom(paramType));
boolean byIdMethod = (null != paramType && paramType == Serializable.class);
boolean sortable = (null != paramType && Sort.class.isAssignableFrom(paramType));
boolean pageable = (null != paramType && Pageable.class.isAssignableFrom(paramType));
Class<?> paramType = cardinality == 1 ? method.getParameterTypes()[0] : null;
boolean someMethod = null != paramType && Iterable.class.isAssignableFrom(paramType);
boolean byIdMethod = null != paramType && paramType == Serializable.class;
boolean sortable = null != paramType && Sort.class.isAssignableFrom(paramType);
boolean pageable = null != paramType && Pageable.class.isAssignableFrom(paramType);
RepositoryMethod repoMethod = new RepositoryMethod(method);
if ("save".equals(name) && someMethod) {

View File

@@ -1,78 +0,0 @@
package org.springframework.data.rest.repository.support;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.util.ReflectionUtils.*;
import static org.springframework.util.StringUtils.*;
import java.lang.reflect.Method;
import org.springframework.beans.factory.annotation.Autowired;
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.RepositoryMethod;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public abstract class RepositoryInformationSupport {
protected Repositories repositories;
protected RepositoryRestConfiguration config;
protected MultiValueMap<Class<?>, RepositoryMethod> repositoryMethods = new LinkedMultiValueMap<Class<?>, RepositoryMethod>();
public Repositories getRepositories() {
return repositories;
}
@Autowired
public void setRepositories(Repositories repositories) {
this.repositories = repositories;
for (Class<?> domainType : repositories) {
final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
doWithMethods(repoInfo.getRepositoryInterface(), new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
repositoryMethods.add(repoInfo.getRepositoryInterface(), new RepositoryMethod(method));
}
});
}
}
public RepositoryRestConfiguration getConfig() {
return config;
}
@Autowired
public void setConfig(RepositoryRestConfiguration config) {
this.config = config;
}
protected RepositoryInformation findRepositoryInfoFor(String pathSegment) {
if (!hasText(pathSegment)) {
return null;
}
for (Class<?> domainType : repositories) {
RepositoryInformation repoInfo = findRepositoryInfoFor(domainType);
ResourceMapping mapping = getResourceMapping(config, repoInfo);
if (pathSegment.equals(mapping.getPath()) && mapping.isExported()) {
return repoInfo;
}
}
return null;
}
protected RepositoryInformation findRepositoryInfoFor(Class<?> domainType) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
if (null != entity) {
return repositories.getRepositoryInformationFor(domainType);
}
return null;
}
}

View File

@@ -25,12 +25,8 @@ import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.data.rest.repository.domain.jpa.AnnotatedPersonRepository;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository;
import org.springframework.data.rest.repository.mapping.RepositoryCollectionResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMapping;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
@@ -44,27 +40,6 @@ import org.springframework.hateoas.core.EvoInflectorRelProvider;
public class ResourceMappingUnitTests {
RelProvider relProvider = new EvoInflectorRelProvider();
@Test
public void shouldDetectDefaultRelAndPath() throws Exception {
ResourceMapping newMapping = new RepositoryCollectionResourceMapping(PlainPersonRepository.class, relProvider);
assertThat(newMapping.getRel(), is("persons"));
assertThat(newMapping.getPath(), is(new Path("person")));
assertThat(newMapping.isExported(), is(true));
}
@Test
public void shouldDetectAnnotatedRelAndPath() throws Exception {
ResourceMapping newMapping = new RepositoryCollectionResourceMapping(AnnotatedPersonRepository.class, relProvider);
assertThat(newMapping.getRel(), is("people"));
assertThat(newMapping.getPath(), is(new Path("person")));
assertThat(newMapping.isExported(), is(false));
}
@Test
public void shouldDetectPathAndRemoveLeadingSlashIfAny() {

View File

@@ -56,6 +56,6 @@ public class RepositoryTestsConfig {
@Bean
public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
return new UriDomainClassConverter(repositories(), domainClassConverter());
}
}

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;
}
}