DATAREST-103 - Added support for non-CrudRepositor implementations.

The exporter can now work with repositories that do not implement CrudRepository. Introduced a new RepositoryInvoker abstraction that transparently exposes which CRUD methods the repository offers. 

We still offer special invokers that can be used for CrudRepository and PagingaAndSortingRepository implementations to avoid the reflection overhead the general mechanism introduces.
This commit is contained in:
Oliver Gierke
2013-07-17 15:14:45 +02:00
parent a4d8a22428
commit 975b333746
16 changed files with 591 additions and 588 deletions

View File

@@ -91,7 +91,7 @@ public class UriDomainClassConverter implements ConditionalGenericConverter {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(targetType.getType());
if (entity != null || !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()));
}

View File

@@ -17,157 +17,88 @@ package org.springframework.data.rest.repository.invoke;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryInformation;
/**
* {@link RepositoryInvoker} to shortcut execution of CRUD methods into direct calls on a {@link CrudRepository}. Used
* to avoid reflection overhead introduced by the base class if we know we work with a {@link CrudRepository}.
*
* @author Oliver Gierke
*/
class CrudRepositoryInvoker implements RepositoryInvoker {
class CrudRepositoryInvoker extends ReflectionRepositoryInvoker {
private final CrudRepository<Object, Serializable> repository;
/**
* Creates a new {@link CrudRepositoryInvoker} for the given {@link CrudRepository}, {@link RepositoryInformation} and
* {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param information must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public CrudRepositoryInvoker(CrudRepository<Object, Serializable> repository) {
public CrudRepositoryInvoker(CrudRepository<Object, Serializable> repository, RepositoryInformation information,
ConversionService conversionService) {
super(repository, information, conversionService);
this.repository = repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
/**
* Invokes the method equivalent to {@link CrudRepository#findAll()}.
*
* @return
*/
@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() {
protected Iterable<Object> invokeFindAll() {
return repository.findAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> findAll(Iterable<Serializable> ids) {
return repository.findAll(ids);
public Iterable<Object> invokeFindAll(Sort pageable) {
return repository.findAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
public long count() {
return repository.count();
public Iterable<Object> invokeFindAll(Pageable pageable) {
return repository.findAll();
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
*/
@Override
public void delete(Serializable id) {
repository.delete(id);
public Object invokeFindOne(Serializable id) {
return repository.findOne(convertId(id));
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
* @see org.springframework.data.rest.repository.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object)
*/
@Override
public void delete(Object entity) {
repository.delete(entity);
public Object invokeSave(Object entity) {
return repository.save(entity);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
*/
@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;
public void invokeDelete(Serializable id) {
repository.delete(convertId(id));
}
}

View File

@@ -17,12 +17,16 @@ package org.springframework.data.rest.repository.invoke;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.core.convert.ConversionService;
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;
/**
* A special {@link RepositoryInvoker} that shortcuts invocations to methods on {@link PagingAndSortingRepository} to
* avoid reflection overhead introduced by the superclass.
*
* @author Oliver Gierke
*/
class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
@@ -30,28 +34,35 @@ class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
private final PagingAndSortingRepository<Object, Serializable> repository;
/**
* Creates a new {@link PagingAndSortingRepositoryInvoker} using the given repository, {@link RepositoryInformation}
* and {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param information must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Serializable> repository) {
super(repository);
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Serializable> repository,
RepositoryInformation information, ConversionService conversionService) {
super(repository, information, conversionService);
this.repository = repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Pageable)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
public Page<Object> findAll(Pageable pageable) {
return repository.findAll(pageable);
public Iterable<Object> invokeFindAll(Sort sort) {
return sort == null ? invokeFindAll() : repository.findAll(sort);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Sort)
* @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
public Iterable<Object> findAll(Sort sort) {
return repository.findAll(sort);
public Iterable<Object> invokeFindAll(Pageable pageable) {
return pageable == null ? invokeFindAll() : repository.findAll(pageable);
}
}

View File

@@ -0,0 +1,244 @@
/*
* 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.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.support.ArgumentConvertingMethodInvoker;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.util.Assert;
/**
* Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories.
*
* @author Oliver Gierke
*/
class ReflectionRepositoryInvoker implements RepositoryInvoker {
private final Object repository;
private final CrudMethods methods;
private final RepositoryInformation information;
private final ConversionService conversionService;
/**
* Creates a new {@link ReflectionRepositoryInvoker} for the given repository, {@link RepositoryInformation} and
* {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param information must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public ReflectionRepositoryInvoker(Object repository, RepositoryInformation information,
ConversionService conversionService) {
Assert.notNull(repository, "Repository must not be null!");
Assert.notNull(information, "RepositoryInformation must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.repository = repository;
this.methods = information.getCrudMethods();
this.information = information;
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#exposesFindAll()
*/
@Override
public boolean exposesFindAll() {
return methods.hasFindAllMethod() && exposes(methods.getFindAllMethod());
}
/* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> invokeFindAll(Sort sort) {
return (Iterable<Object>) invoke(methods.getFindAllMethod(), sort);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> invokeFindAll(Pageable pageable) {
return (Iterable<Object>) invoke(methods.getFindAllMethod(), pageable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#exposesSave()
*/
@Override
public boolean exposesSave() {
return methods.hasSaveMethod() && exposes(methods.getSaveMethod());
}
/* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeSave(java.lang.Object)
*/
@Override
public Object invokeSave(Object object) {
return invoke(methods.getSaveMethod(), object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#exposesFindOne()
*/
@Override
public boolean exposesFindOne() {
return methods.hasFindOneMethod() && exposes(methods.getFindOneMethod());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
*/
@Override
public Object invokeFindOne(Serializable id) {
return invoke(methods.getFindOneMethod(), convertId(id));
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#exposesDelete()
*/
@Override
public boolean exposesDelete() {
return methods.hasDelete() && exposes(methods.getDeleteMethod());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
*/
@Override
public void invokeDelete(Serializable id) {
Method method = methods.getDeleteMethod();
if (method.getParameterTypes()[0].equals(Serializable.class)) {
invoke(method, convertId(id));
} else {
invoke(method, invokeFindOne(id));
}
}
private boolean exposes(Method method) {
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
return annotation == null ? true : annotation.exported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.invoke.RepositoryInvoker#invokeQueryMethod(java.lang.reflect.Method, java.util.Map, org.springframework.data.domain.Pageable, org.springframework.data.domain.Sort)
*/
@Override
public Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort) {
return invoke(method, prepareParameters(method, parameters, pageable, sort));
}
private Object[] prepareParameters(Method method, Map<String, String[]> rawParameters, Pageable pageable, Sort sort) {
List<MethodParameter> parameters = new MethodParameters(method).getParameters();
if (parameters.isEmpty()) {
return new Object[0];
}
Object[] result = new Object[parameters.size()];
Sort sortToUse = pageable == null ? sort : pageable.getSort();
for (int i = 0; i < result.length; i++) {
MethodParameter param = parameters.get(i);
Class<?> targetType = param.getParameterType();
if (Pageable.class.isAssignableFrom(targetType)) {
result[i] = pageable;
} else if (Sort.class.isAssignableFrom(targetType)) {
result[i] = sortToUse;
} else {
String parameterName = param.getParameterName();
String[] parameterValue = rawParameters.get(parameterName);
Object value = parameterValue.length == 1 ? parameterValue[0] : parameterValue;
if (value == null) {
if (parameterName.startsWith("arg")) {
throw new IllegalArgumentException("No @Param annotation found on query method " + method.getName()
+ " for parameter " + parameterName);
} else {
throw new IllegalArgumentException("No query parameter specified for " + method.getName() + " param '"
+ parameterName + "'");
}
}
result[i] = conversionService.convert(parameterValue, targetType);
}
}
return result;
}
private Object invoke(Method method, Object... arguments) {
BeanWrapperImpl wrapper = new BeanWrapperImpl();
wrapper.setConversionService(conversionService);
ArgumentConvertingMethodInvoker invoker = new ArgumentConvertingMethodInvoker();
invoker.setTargetObject(repository);
invoker.setTargetMethod(method.getName());
invoker.setArguments(arguments);
invoker.setTypeConverter(wrapper);
try {
invoker.prepare();
return invoker.invoke();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* Converts the given id into the id type of the backing repository.
*
* @param id must not be {@literal null}.
* @return
*/
protected Serializable convertId(Serializable id) {
Assert.notNull(id, "Id must not be null!");
return conversionService.convert(id, information.getIdType());
}
}

View File

@@ -16,11 +16,17 @@
package org.springframework.data.rest.repository.invoke;
/**
* Meta-information about the methods a repository exposes.
*
* @author Oliver Gierke
*/
public interface RepositoryInvocationInformation {
boolean hasFindOne();
boolean exposesSave();
boolean hasFindAll();
boolean exposesDelete();
boolean exposesFindOne();
boolean exposesFindAll();
}

View File

@@ -16,13 +16,26 @@
package org.springframework.data.rest.repository.invoke;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
/**
* @author Oliver Gierke
*/
public interface RepositoryInvoker extends PagingAndSortingRepository<Object, Serializable>,
RepositoryInvocationInformation {
public interface RepositoryInvoker extends RepositoryInvocationInformation {
Object invokeSave(Object object);
Object invokeFindOne(Serializable id);
Iterable<Object> invokeFindAll(Pageable pageable);
Iterable<Object> invokeFindAll(Sort pageable);
void invokeDelete(Serializable serializable);
Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort);
}

View File

@@ -19,8 +19,10 @@ import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
/**
@@ -29,40 +31,49 @@ import org.springframework.data.repository.support.Repositories;
public class RepositoryInvokerFactory {
private final Repositories repositories;
private final ConversionService conversionService;
private final Map<Class<?>, RepositoryInvoker> invokers;
/**
* @param repositories
* @param invokers
*/
public RepositoryInvokerFactory(Repositories repositories) {
public RepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) {
this.repositories = repositories;
this.conversionService = conversionService;
this.invokers = new HashMap<Class<?>, RepositoryInvoker>();
prepareInvokers(repositories);
}
@SuppressWarnings("unchecked")
private final void prepareInvokers(Repositories repositories) {
private RepositoryInvoker prepareInvokers(Class<?> domainType) {
for (Class<?> domainType : repositories) {
Object repository = repositories.getRepositoryFor(domainType);
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
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);
if (repository instanceof PagingAndSortingRepository) {
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository<Object, Serializable>) repository,
information, conversionService);
} else if (repository instanceof CrudRepository) {
return new CrudRepositoryInvoker((CrudRepository<Object, Serializable>) repository, information,
conversionService);
} else {
return new ReflectionRepositoryInvoker(repository, information, conversionService);
}
}
public RepositoryInvoker getInvokerFor(Class<?> domainType) {
return invokers.get(domainType);
RepositoryInvoker invoker = invokers.get(domainType);
if (invoker != null) {
return invoker;
}
invoker = prepareInvokers(domainType);
invokers.put(domainType, invoker);
return invoker;
}
}

View File

@@ -1,282 +0,0 @@
package org.springframework.data.rest.repository.invoke;
import static org.springframework.util.ReflectionUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
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.core.RepositoryInformation;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
/**
* @author Jon Brisbin
*/
@SuppressWarnings("deprecation")
public class RepositoryMethodInvoker implements RepositoryInvoker {
private final Object repository;
private final Map<String, RepositoryMethod> queryMethods = new HashMap<String, RepositoryMethod>();
private final ConversionService conversionService;
private RepositoryMethod saveOne;
private RepositoryMethod saveSome;
private RepositoryMethod findOne;
private RepositoryMethod exists;
private RepositoryMethod findAll;
private RepositoryMethod findAllSorted;
private RepositoryMethod findAllPaged;
private RepositoryMethod findSome;
private RepositoryMethod count;
private RepositoryMethod deleteOne;
private RepositoryMethod deleteOneById;
private RepositoryMethod deleteSome;
private RepositoryMethod deleteAll;
public RepositoryMethodInvoker(Object repository, RepositoryInformation repoInfo, ConversionService conversionService) {
this.repository = repository;
this.conversionService = conversionService;
Class<?> repoType = repoInfo.getRepositoryInterface();
doWithMethods(repoType, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
boolean exported = ResourceMappingUtils.findExported(method);
if (!exported) {
return;
}
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);
RepositoryMethod repoMethod = new RepositoryMethod(method);
if ("save".equals(name) && someMethod) {
saveSome = repoMethod;
} else if ("save".equals(name)) {
saveOne = repoMethod;
} else if ("findOne".equals(name)) {
findOne = repoMethod;
} else if ("exists".equals(name)) {
exists = repoMethod;
} else if ("findAll".equals(name) && sortable) {
findAllSorted = repoMethod;
} else if ("findAll".equals(name) && someMethod) {
findSome = repoMethod;
} else if ("findAll".equals(name) && pageable) {
findAllPaged = repoMethod;
} else if ("findAll".equals(name)) {
findAll = repoMethod;
} else if ("count".equals(name)) {
count = repoMethod;
} else if ("delete".equals(name) && byIdMethod) {
deleteOneById = repoMethod;
} else if ("delete".equals(name) && someMethod) {
deleteSome = repoMethod;
} else if ("delete".equals(name)) {
deleteOne = repoMethod;
} else if ("deleteAll".equals(name)) {
deleteAll = repoMethod;
} else {
queryMethods.put(name, repoMethod);
}
}
});
}
@SuppressWarnings({ "unchecked" })
@Override
public <S extends Object> S save(S entity) {
return (S) invokeMethod(saveOne.getMethod(), repository, entity);
}
public boolean hasSaveOne() {
return null != saveOne;
}
@SuppressWarnings({ "unchecked" })
@Override
public <S extends Object> Iterable<S> save(Iterable<S> entities) {
return (Iterable<S>) invokeMethod(saveSome.getMethod(), repository, entities);
}
public boolean hasSaveSome() {
return null != saveSome;
}
@Override
public Object findOne(Serializable serializable) {
return invokeMethod(findOne.getMethod(), repository, serializable);
}
public boolean hasFindOne() {
return null != findOne;
}
@Override
public boolean exists(Serializable serializable) {
return (Boolean) invokeMethod(exists.getMethod(), repository, serializable);
}
public boolean hasExists() {
return null != exists;
}
@SuppressWarnings({ "unchecked" })
@Override
public Iterable<Object> findAll() {
return (Iterable<Object>) invokeMethod(findAll.getMethod(), repository);
}
public boolean hasFindAll() {
return null != findAll;
}
@SuppressWarnings({ "unchecked" })
@Override
public Iterable<Object> findAll(Iterable<Serializable> serializables) {
return (Iterable<Object>) invokeMethod(findSome.getMethod(), repository, serializables);
}
public boolean hasFindSome() {
return null != findSome;
}
@SuppressWarnings({ "unchecked" })
@Override
public Iterable<Object> findAll(Sort sort) {
return (Iterable<Object>) invokeMethod(findAllSorted.getMethod(), repository, sort);
}
public boolean hasFindAllSorted() {
return null != findAllSorted;
}
@SuppressWarnings({ "unchecked" })
@Override
public Page<Object> findAll(Pageable pageable) {
return (Page<Object>) invokeMethod(findAllPaged.getMethod(), repository, pageable);
}
public boolean hasFindAllPageable() {
return null != findAllPaged;
}
@Override
public void delete(Serializable serializable) {
invokeMethod(deleteOneById.getMethod(), repository, serializable);
}
public boolean hasDeleteOneById() {
return null != deleteOneById;
}
@Override
public long count() {
return (Long) invokeMethod(count.getMethod(), repository);
}
public boolean hasCount() {
return null != count;
}
@Override
public void delete(Object entity) {
invokeMethod(deleteOne.getMethod(), repository, entity);
}
public boolean hasDeleteOne() {
return null != deleteOne;
}
@Override
public void delete(Iterable<?> entities) {
invokeMethod(deleteSome.getMethod(), repository, entities);
}
public boolean hasDeleteSome() {
return null != deleteSome;
}
@Override
public void deleteAll() {
invokeMethod(deleteAll.getMethod(), repository);
}
public boolean hasDeleteAll() {
return null != deleteAll;
}
public Map<String, RepositoryMethod> getQueryMethods() {
return queryMethods;
}
public RepositoryMethod getRepositoryMethod(String name) {
return queryMethods.get(name);
}
public Object invokeQueryMethod(String name, Object... params) {
RepositoryMethod repoMethod = queryMethods.get(name);
if (null == repoMethod) {
throw new NoSuchMethodError(name);
}
return invokeMethod(repoMethod.getMethod(), repository, params);
}
public Object invokeQueryMethod(RepositoryMethod method, Object... params) {
return invokeMethod(method.getMethod(), repository, params);
}
public Object invokeQueryMethod(RepositoryMethod method, Pageable pageable, Map<String, String[]> rawParameters) {
return invokeQueryMethod(method, foo(method, pageable, rawParameters));
}
private Object[] foo(RepositoryMethod repoMethod, Pageable pageable, Map<String, String[]> rawParameters) {
List<MethodParameter> methodParams = repoMethod.getParameters();
if (methodParams.isEmpty()) {
return new Object[0];
}
Object[] paramValues = new Object[methodParams.size()];
for (int i = 0; i < paramValues.length; i++) {
MethodParameter param = methodParams.get(i);
Class<?> targetType = param.getParameterType();
if (Pageable.class.isAssignableFrom(targetType)) {
paramValues[i] = pageable;
} else if (Sort.class.isAssignableFrom(targetType)) {
paramValues[i] = pageable.getSort();
} else {
String paramName = repoMethod.getParameterNames().get(i);
String[] queryParamVals = rawParameters.get(paramName);
if (null == queryParamVals) {
if (paramName.startsWith("arg")) {
throw new IllegalArgumentException("No @Param annotation found on query method "
+ repoMethod.getMethod().getName() + " for parameter " + param.getParameterName());
} else {
throw new IllegalArgumentException("No query parameter specified for " + repoMethod.getMethod().getName()
+ " param '" + paramName + "'");
}
}
paramValues[i] = conversionService.convert(queryParamVals, targetType);
}
}
return paramValues;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.RepositoryTestsConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link ReflectionRepositoryInvoker}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RepositoryTestsConfig.class)
public class ReflectionRepositoryInvokerIntegrationTests {
@Autowired Repositories repositories;
@Autowired ConversionService conversionService;
Object repository;
RepositoryInformation information;
RepositoryInvoker invoker;
@Before
public void setUp() {
information = repositories.getRepositoryInformationFor(Person.class);
repository = repositories.getRepositoryFor(Person.class);
invoker = new ReflectionRepositoryInvoker(repository, information, conversionService);
}
@Test
public void invokesFindOneWithStringIdCorrectly() {
Object result = invoker.invokeFindOne("1");
assertThat(result, is(instanceOf(Person.class)));
}
@Test
public void invokesFindAllWithoutPageableCorrectly() {
Iterable<Object> result = invoker.invokeFindAll((Pageable) null);
assertThat(result, is(instanceOf(List.class)));
}
@Test
public void invokesFindAllWithPageableCorrectly() {
Iterable<Object> result = invoker.invokeFindAll(new PageRequest(0, 10));
assertThat(result, is(instanceOf(Page.class)));
}
}

View File

@@ -197,7 +197,7 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
Link selfLink = resource.getLink("self");
String rel = repoMapping.getSingleResourceRel();

View File

@@ -17,7 +17,6 @@ package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,6 +29,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
@@ -42,7 +42,7 @@ 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.RepositoryMethodInvoker;
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;
@@ -55,9 +55,6 @@ import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -80,8 +77,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
private final ConversionService conversionService;
private final DomainObjectMerger domainObjectMerger;
private final TransactionOperations txOperations;
private ApplicationEventPublisher publisher;
@Autowired
@@ -98,8 +93,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
this.converter = converter;
this.conversionService = conversionService;
this.domainObjectMerger = domainObjectMerger;
this.txOperations = null;
}
/*
@@ -114,33 +107,28 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable)
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable, Sort sort)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
Iterable<?> results;
RepositoryMethodInvoker repoMethodInvoker = request.getRepositoryMethodInvoker();
RepositoryInvoker repoMethodInvoker = request.getRepositoryInvoker();
if (null == repoMethodInvoker) {
throw new ResourceNotFoundException();
}
if (repoMethodInvoker.hasFindAllPageable()) {
results = repoMethodInvoker.findAll(pageable);
} else if (repoMethodInvoker.hasFindAllSorted()) {
results = repoMethodInvoker.findAll(pageable.getSort());
} else if (repoMethodInvoker.hasFindAll()) {
results = repoMethodInvoker.findAll();
if (pageable != null) {
results = repoMethodInvoker.invokeFindAll(pageable);
} else {
throw new ResourceNotFoundException();
results = repoMethodInvoker.invokeFindAll(sort);
}
ResourceMetadata repoMapping = request.getRepositoryResourceMapping();
SearchResourceMappings searchMappings = repoMapping.getSearchResourceMappings();
ResourceMetadata metadata = request.getResourceMetadata();
SearchResourceMappings searchMappings = metadata.getSearchResourceMappings();
if (searchMappings.isExported()) {
links.add(entityLinks.linkFor(repoMapping.getDomainType()).slash(searchMappings.getPath())
links.add(entityLinks.linkFor(metadata.getDomainType()).slash(searchMappings.getPath())
.withRel(searchMappings.getRel()));
}
@@ -149,13 +137,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
return resources;
}
@ResponseBody
@SuppressWarnings({ "unchecked" })
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable)
throws ResourceNotFoundException {
Resources<?> resources = listEntities(repoRequest, pageable);
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable, Sort sort) {
Resources<?> resources = listEntities(repoRequest, pageable, sort);
List<Link> links = new ArrayList<Link>(resources.getLinks());
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
@@ -169,18 +157,19 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
}
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" }, produces = {
"application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> createNewEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<?> incoming) {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne()) {
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesSave()) {
throw new NoSuchMethodError();
}
publisher.publishEvent(new BeforeCreateEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(incoming.getContent());
Object obj = invoker.invokeSave(incoming.getContent());
publisher.publishEvent(new AfterCreateEvent(obj));
Link selfLink = perAssembler.getSelfLinkFor(obj);
@@ -202,59 +191,58 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
* @return
* @throws ResourceNotFoundException
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resource<?> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id)
throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasFindOne()) {
throw new ResourceNotFoundException();
public ResponseEntity<Resource<?>> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id) {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
}
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
Object domainObj = repoMethodInvoker.invokeFindOne(id);
if (null == domainObj) {
throw new ResourceNotFoundException();
if (domainObj == null) {
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
}
return perAssembler.toResource(domainObj);
return new ResponseEntity<Resource<?>>(perAssembler.toResource(domainObj), HttpStatus.OK);
}
/**
* {@code PUT / repository}/{id}} - Updates an existing entity or creates one at exactly that place.
*
* @param repoRequest
* @param request
* @param incoming
* @param id
* @return
* @throws ResourceNotFoundException
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT, consumes = { "application/json" },
produces = { "application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<Object> incoming, @PathVariable String id) throws ResourceNotFoundException {
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest request,
PersistentEntityResource<Object> incoming, @PathVariable String id) {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne() || !repoMethodInvoker.hasFindOne()) {
throw new NoSuchMethodError();
RepositoryInvoker invoker = request.getRepositoryInvoker();
if (!invoker.exposesSave() || !invoker.exposesFindOne()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
TypeDescriptor.valueOf(request.getPersistentEntity().getType()));
if (null == domainObj) {
BeanWrapper<?, Object> incomingWrapper = BeanWrapper.create(incoming.getContent(), conversionService);
PersistentProperty<?> idProp = incoming.getPersistentEntity().getIdProperty();
incomingWrapper.setProperty(idProp, conversionService.convert(id, idProp.getType()));
return createNewEntity(repoRequest, incoming);
return createNewEntity(request, incoming);
}
domainObjectMerger.merge(incoming.getContent(), domainObj);
publisher.publishEvent(new BeforeSaveEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(domainObj);
Object obj = invoker.invokeSave(domainObj);
publisher.publishEvent(new AfterSaveEvent(obj));
if (config.isReturnBodyOnUpdate()) {
@@ -268,10 +256,11 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
public ResponseEntity<?> deleteEntity(final RepositoryRestRequest repoRequest, @PathVariable final String id)
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasFindOne()
&& !(repoMethodInvoker.hasDeleteOne() || repoMethodInvoker.hasDeleteOneById())) {
throw new HttpRequestMethodNotSupportedException("DELETE");
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesDelete() || !invoker.exposesFindOne()) {
throw new HttpRequestMethodNotSupportedException(RequestMethod.DELETE.toString());
}
// TODO: re-enable not exposing delete method if hidden
@@ -281,36 +270,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
// throw new HttpRequestMethodNotSupportedException("DELETE");
// }
final Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if (null == domainObj) {
throw new ResourceNotFoundException();
}
Object domainObj = invoker.invokeFindOne(id);
publisher.publishEvent(new BeforeDeleteEvent(domainObj));
TransactionCallbackWithoutResult callback = new TransactionCallbackWithoutResult() {
@Override
@SuppressWarnings({ "unchecked" })
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (repoMethodInvoker.hasDeleteOneById()) {
Class<? extends Serializable> idType = (Class<? extends Serializable>) repoRequest.getPersistentEntity()
.getIdProperty().getType();
final Serializable idVal = conversionService.convert(id, idType);
repoMethodInvoker.delete(idVal);
} else if (repoMethodInvoker.hasDeleteOne()) {
repoMethodInvoker.delete(domainObj);
}
}
};
// FIXME
if (txOperations != null) {
txOperations.execute(callback);
} else {
callback.doInTransaction(null);
}
invoker.invokeDelete(id);
publisher.publishEvent(new AfterDeleteEvent(domainObj));
return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);

View File

@@ -43,12 +43,13 @@ 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.RepositoryMethodInvoker;
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.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -149,9 +150,11 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
public ResponseEntity<Resource<?>> deletePropertyReference(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException,
HttpRequestMethodNotSupportedException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasDeleteOne()) {
throw new NoSuchMethodException();
final RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesDelete()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@@ -169,7 +172,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = repoMethodInvoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
@@ -245,7 +248,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return response;
}
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(property);
Class<?> propType = persistentProp.isCollectionLike() || persistentProp.isMap() ? persistentProp.getComponentType()
@@ -281,17 +284,19 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@ResponseBody
public ResponseEntity<Resource<?>> createPropertyReference(final RepositoryRestRequest repoRequest,
final @RequestBody Resource<Object> incoming, @PathVariable String id, @PathVariable String property)
throws ResourceNotFoundException, NoSuchMethodException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasSaveOne()) {
throw new NoSuchMethodException();
throws NoSuchMethodException {
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesSave()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
if (prop.property.isCollectionLike()) {
Collection<Object> coll = new ArrayList<Object>();
if ("POST".equals(repoRequest.getRequest().getMethod())) {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
coll.addAll((Collection<Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
@@ -301,7 +306,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
prop.wrapper.setProperty(prop.property, coll);
} else if (prop.property.isMap()) {
Map<String, Object> m = new HashMap<String, Object>();
if ("POST".equals(repoRequest.getRequest().getMethod())) {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
m.putAll((Map<String, Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
@@ -310,7 +315,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
prop.wrapper.setProperty(prop.property, m);
} else {
if ("POST".equals(repoRequest.getRequest().getMethod())) {
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.");
}
@@ -323,12 +328,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkSaveEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkSaveEvent(result, prop.propertyValue));
return null;
}
};
doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.CREATED);
}
@@ -336,10 +343,12 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@ResponseBody
public ResponseEntity<Resource<?>> deletePropertyReferenceId(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
throws ResourceNotFoundException, NoSuchMethodException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasDeleteOne()) {
throw new NoSuchMethodException();
throws NoSuchMethodException {
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesDelete()) {
throw new NoSuchMethodError();
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@@ -373,11 +382,12 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
@@ -395,9 +405,11 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
private Resource<?> doWithReferencedProperty(RepositoryRestRequest repoRequest, String id, String propertyPath,
Function<ReferencedProperty, Resource<?>> handler) throws ResourceNotFoundException, NoSuchMethodException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasFindOne()) {
Function<ReferencedProperty, Resource<?>> handler) throws NoSuchMethodException {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
throw new NoSuchMethodException();
}

View File

@@ -15,19 +15,15 @@
*/
package org.springframework.data.rest.webmvc;
import java.io.Serializable;
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.CrudRepository;
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.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.request.NativeWebRequest;
/**
* @author Jon Brisbin
@@ -35,48 +31,47 @@ import org.springframework.data.rest.repository.mapping.ResourceMetadata;
*/
class RepositoryRestRequest {
private final HttpServletRequest request;
private final NativeWebRequest request;
private final URI baseUri;
private final ResourceMetadata resourceMetadata;
private final RepositoryMethodInvoker repoMethodInvoker;
private final RepositoryInvoker repoInvoker;
private final PersistentEntity<?, ?> persistentEntity;
public RepositoryRestRequest(RepositoryRestConfiguration config, Repositories repositories,
HttpServletRequest request, URI baseUri, ResourceMetadata repoInfo, ConversionService conversionService) {
public RepositoryRestRequest(PersistentEntity<?, ?> entity, NativeWebRequest request, URI baseUri,
ResourceMetadata repoInfo, RepositoryInvoker invoker) {
this.request = request;
this.baseUri = baseUri;
this.resourceMetadata = repoInfo;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
this.repoMethodInvoker = null;
this.repoInvoker = null;
this.persistentEntity = null;
} else {
Class<?> domainType = repoInfo.getDomainType();
CrudRepository<Object, Serializable> repositoryFor = repositories.getRepositoryFor(domainType);
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
this.repoMethodInvoker = new RepositoryMethodInvoker(repositoryFor, information, conversionService);
this.persistentEntity = repositories.getPersistentEntity(domainType);
this.repoInvoker = invoker;
this.persistentEntity = entity;
}
}
HttpServletRequest getRequest() {
NativeWebRequest getRequest() {
return request;
}
HttpMethod getRequestMethod() {
return HttpMethod.valueOf(request.getNativeRequest(HttpServletRequest.class).getMethod());
}
URI getBaseUri() {
return baseUri;
}
ResourceMetadata getRepositoryResourceMapping() {
ResourceMetadata getResourceMetadata() {
return resourceMetadata;
}
RepositoryMethodInvoker getRepositoryMethodInvoker() {
return repoMethodInvoker;
RepositoryInvoker getRepositoryInvoker() {
return repoInvoker;
}
PersistentEntity<?, ?> getPersistentEntity() {

View File

@@ -17,14 +17,14 @@ 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.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
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.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -36,15 +36,29 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public class RepositoryRestRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConversionService conversionService;
private final Repositories repositories;
private final RepositoryInvokerFactory invokerFactory;
private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
private final BaseUriMethodArgumentResolver baseUriResolver;
@Autowired private RepositoryRestConfiguration config;
@Autowired private Repositories repositories;
@Autowired private ResourceMetadataHandlerMethodArgumentResolver repoInfoResolver;
@Autowired private BaseUriMethodArgumentResolver baseUriResolver;
/**
* Creates a new {@link RepositoryRestRequestHandlerMethodArgumentResolver} using the given {@link Repositories} and
* {@link ConversionService}.
*
* @param repositories must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public RepositoryRestRequestHandlerMethodArgumentResolver(Repositories repositories,
ConversionService conversionService, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver,
BaseUriMethodArgumentResolver baseUriResolver) {
public RepositoryRestRequestHandlerMethodArgumentResolver(ConversionService conversionService) {
this.conversionService = conversionService;
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.repositories = repositories;
this.invokerFactory = new RepositoryInvokerFactory(repositories, conversionService);
this.resourceMetadataResolver = resourceMetadataResolver;
this.baseUriResolver = baseUriResolver;
}
/*
@@ -65,11 +79,14 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = resourceMetadataResolver.resolveArgument(parameter, mavContainer, webRequest,
binderFactory);
RepositoryInvoker repositoryInvoker = invokerFactory.getInvokerFor(repoInfo.getDomainType());
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
// TODO reject if ResourceMetadata cannot be resolved
return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class),
baseUri, repoInfo, conversionService);
return new RepositoryRestRequest(persistentEntity, webRequest, baseUri, repoInfo, repositoryInvoker);
}
}

View File

@@ -26,8 +26,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.repository.invoke.RepositoryMethod;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
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;
@@ -39,6 +38,8 @@ import org.springframework.hateoas.LinkBuilder;
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.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -98,40 +99,39 @@ class RepositorySearchController extends AbstractRepositoryRestController {
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public ResourceSupport query(final RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
public ResponseEntity<ResourceSupport> query(final RepositoryRestRequest repoRequest,
@PathVariable String repository, @PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
ResourceMetadata metadata = repoRequest.getResourceMetadata();
SearchResourceMappings searchMapping = metadata.getSearchResourceMappings();
if (repoMethodInvoker.getQueryMethods().isEmpty()) {
throw new ResourceNotFoundException();
if (searchMapping.isExported()) {
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
SearchResourceMappings searchResourceMappings = repoMapping.getSearchResourceMappings();
Method mappedMethod = searchResourceMappings.getMappedMethod(method);
Method mappedMethod = searchMapping.getMappedMethod(method);
if (mappedMethod == null) {
throw new ResourceNotFoundException();
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
RepositoryMethod repositoryMethod = new RepositoryMethod(mappedMethod);
Map<String, String[]> parameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(mappedMethod, parameters, pageable, null);
Map<String, String[]> rawParameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(repositoryMethod, pageable, rawParameters);
return resultToResources(result);
return new ResponseEntity<ResourceSupport>(resultToResources(result), HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET,
produces = { "application/x-spring-data-compact+json" })
@ResponseBody
public ResourceSupport queryCompact(RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
@PathVariable String method, Pageable pageable) {
List<Link> links = new ArrayList<Link>();
ResourceSupport resource = query(repoRequest, repository, method, pageable);
ResponseEntity<ResourceSupport> entity = query(repoRequest, repository, method, pageable);
ResourceSupport resource = entity.getBody();
links.addAll(resource.getLinks());
if (resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {
@@ -148,5 +148,4 @@ class RepositorySearchController extends AbstractRepositoryRestController {
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
}
}

View File

@@ -213,7 +213,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
return new RepositoryRestRequestHandlerMethodArgumentResolver(defaultConversionService());
return new RepositoryRestRequestHandlerMethodArgumentResolver(repositories(), defaultConversionService(),
resourceMetadataHandlerMethodArgumentResolver(), baseUriMethodArgumentResolver());
}
@Bean