DATACMNS-589 - Integrated RepositoryInvoker abstraction from Spring Data REST.

Replaced CrudInvoker with the more sophisticated and flexible RepositoryInvoker originating from Spring Data REST. Slightly refined the API and added a lot more unit tests. Refactored usage of CrudInvoker to use a DefaultRepositoryInvokerFactory and create RepositoryInvokers for the domain types.
This commit is contained in:
Oliver Gierke
2014-11-13 15:22:11 +01:00
parent ac73720668
commit eac6fcaa8e
32 changed files with 1777 additions and 470 deletions

View File

@@ -1,46 +0,0 @@
/*
* 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.repository.core;
import java.io.Serializable;
import org.springframework.data.repository.CrudRepository;
/**
* Interface for components that can invoke simple CRUD operations on repositories. Useful to be able to abstract being
* backed by a {@link CrudRepository} implementation or a raw repository declaration with signature compatible methods
* for {@link CrudRepository#findOne(Serializable)} and {@link CrudRepository#save(Object)}.
*
* @author Oliver Gierke
* @since 1.6
*/
public interface CrudInvoker<T> {
/**
* Invokes the method equivalent to {@link CrudRepository#save(Object)}.
*
* @param object must not be {@literal null}.
*/
T invokeSave(T object);
/**
* Invokes the method equivalent to {@link CrudRepository#findOne(Serializable)}.
*
* @param id must not be {@literal null}.
* @return
*/
T invokeFindOne(Serializable id);
}

View File

@@ -26,7 +26,9 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.util.Assert;
@@ -107,6 +109,8 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A
*/
public void populate(Repositories repositories) {
RepositoryInvokerFactory invokerFactory = new DefaultRepositoryInvokerFactory(repositories);
for (Resource resource : resources) {
LOGGER.info(String.format("Reading resource: %s", resource));
@@ -116,13 +120,13 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A
if (result instanceof Collection) {
for (Object element : (Collection<?>) result) {
if (element != null) {
persist(element, repositories);
persist(element, invokerFactory);
} else {
LOGGER.info("Skipping null element found in unmarshal result!");
}
}
} else {
persist(result, repositories);
persist(result, invokerFactory);
}
}
@@ -151,12 +155,10 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A
* @param object must not be {@literal null}.
* @param repositories must not be {@literal null}.
*/
@SuppressWarnings({ "unchecked" })
private void persist(Object object, Repositories repositories) {
private void persist(Object object, RepositoryInvokerFactory invokerFactory) {
CrudInvoker<Object> invoker = (CrudInvoker<Object>) repositories.getCrudInvoker(object.getClass());
RepositoryInvoker invoker = invokerFactory.getInvokerFor(object.getClass());
LOGGER.debug(String.format("Persisting %s using repository %s", object, invoker));
invoker.invokeSave(object);
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.repository.invoker;
import java.io.Serializable;
import java.lang.reflect.Method;
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.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
/**
* {@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 extends ReflectionRepositoryInvoker {
private final CrudRepository<Object, Serializable> repository;
private final CrudMethods crudMethods;
private final boolean customSaveMethod;
private final boolean customFindOneMethod;
private final boolean customFindAllMethod;
private final boolean customDeleteMethod;
/**
* Creates a new {@link CrudRepositoryInvoker} for the given {@link CrudRepository}, {@link RepositoryInformation} and
* {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public CrudRepositoryInvoker(CrudRepository<Object, Serializable> repository, RepositoryMetadata metadata,
ConversionService conversionService) {
super(repository, metadata, conversionService);
this.repository = repository;
this.crudMethods = metadata.getCrudMethods();
this.customSaveMethod = isRedeclaredMethod(crudMethods.getSaveMethod());
this.customFindOneMethod = isRedeclaredMethod(crudMethods.getFindOneMethod());
this.customDeleteMethod = isRedeclaredMethod(crudMethods.getDeleteMethod());
this.customFindAllMethod = isRedeclaredMethod(crudMethods.getFindAllMethod());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> invokeFindAll(Sort sort) {
return customFindAllMethod ? super.invokeFindAll(sort) : repository.findAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
public Iterable<Object> invokeFindAll(Pageable pageable) {
return customFindAllMethod ? super.invokeFindAll(pageable) : repository.findAll();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
*/
@Override
@SuppressWarnings("unchecked")
public <T> T invokeFindOne(Serializable id) {
return customFindOneMethod ? super.<T> invokeFindOne(id) : (T) repository.findOne(convertId(id));
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object)
*/
@Override
public <T> T invokeSave(T entity) {
return customSaveMethod ? super.invokeSave(entity) : repository.save(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
*/
@Override
public void invokeDelete(Serializable id) {
if (customDeleteMethod) {
super.invokeDelete(id);
} else {
repository.delete(convertId(id));
}
}
private boolean isRedeclaredMethod(Method method) {
return !method.getDeclaringClass().equals(CrudRepository.class);
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.repository.invoker;
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;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
/**
* Default implementation of {@link RepositoryInvokerFactory} to inspect the requested repository type and create a
* matching {@link RepositoryInvoker} that suits the repository best. That means, the more concrete the base interface
* of the repository is, the more concrete will the actual invoker become - which means it will favor concrete method
* invocations over reflection ones.
*
* @author Oliver Gierke
*/
public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory {
private final Repositories repositories;
private final ConversionService conversionService;
private final Map<Class<?>, RepositoryInvoker> invokers;
/**
* Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories}.
*
* @param repositories must not be {@literal null}.
*/
public DefaultRepositoryInvokerFactory(Repositories repositories) {
this(repositories, new DefaultFormattingConversionService());
}
/**
* Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories} and
* {@link ConversionService}.
*
* @param repositories must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public DefaultRepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.repositories = repositories;
this.conversionService = conversionService;
this.invokers = new HashMap<Class<?>, RepositoryInvoker>();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvokerFactory#getInvokerFor(java.lang.Class)
*/
@Override
public RepositoryInvoker getInvokerFor(Class<?> domainType) {
RepositoryInvoker invoker = invokers.get(domainType);
if (invoker != null) {
return invoker;
}
invoker = prepareInvokers(domainType);
invokers.put(domainType, invoker);
return invoker;
}
/**
* Creates a {@link RepositoryInvoker} for the repository managing the given domain type.
*
* @param domainType
* @return
*/
@SuppressWarnings("unchecked")
private RepositoryInvoker prepareInvokers(Class<?> domainType) {
Object repository = repositories.getRepositoryFor(domainType);
Assert.notNull(repository, String.format("No repository found for domain type: %s", domainType));
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
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);
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.repository.invoker;
import java.io.Serializable;
import java.lang.reflect.Method;
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.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
/**
* 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 {
private final PagingAndSortingRepository<Object, Serializable> repository;
private final boolean customFindAll;
/**
* Creates a new {@link PagingAndSortingRepositoryInvoker} using the given repository, {@link RepositoryInformation}
* and {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Serializable> repository,
RepositoryMetadata metadata, ConversionService conversionService) {
super(repository, metadata, conversionService);
CrudMethods crudMethods = metadata.getCrudMethods();
this.repository = repository;
this.customFindAll = isRedeclaredMethod(crudMethods.getFindAllMethod());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> invokeFindAll(Sort sort) {
return customFindAll ? invokeFindAllReflectively(sort) : repository.findAll(sort);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
public Iterable<Object> invokeFindAll(Pageable pageable) {
return customFindAll ? invokeFindAllReflectively(pageable) : repository.findAll(pageable);
}
private boolean isRedeclaredMethod(Method method) {
return !method.getDeclaringClass().equals(PagingAndSortingRepository.class);
}
}

View File

@@ -0,0 +1,280 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.repository.invoker;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
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.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Param;
import org.springframework.hateoas.core.AnnotationAttribute;
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories.
*
* @author Oliver Gierke
*/
class ReflectionRepositoryInvoker implements RepositoryInvoker {
private static final AnnotationAttribute PARAM_ANNOTATION = new AnnotationAttribute(Param.class);
private final Object repository;
private final CrudMethods methods;
private final Class<? extends Serializable> idType;
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 metadata must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public ReflectionRepositoryInvoker(Object repository, RepositoryMetadata metadata, ConversionService conversionService) {
Assert.notNull(repository, "Repository must not be null!");
Assert.notNull(metadata, "RepositoryMetadata must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.repository = repository;
this.methods = metadata.getCrudMethods();
this.idType = metadata.getIdType();
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindAllMethod()
*/
@Override
public boolean hasFindAllMethod() {
return methods.hasFindAllMethod();
}
/* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<Object> invokeFindAll(Sort sort) {
return invokeFindAllReflectively(sort);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
*/
@Override
public Iterable<Object> invokeFindAll(Pageable pageable) {
return invokeFindAllReflectively(pageable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasSaveMethod()
*/
@Override
public boolean hasSaveMethod() {
return methods.hasSaveMethod();
}
/* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeSave(java.lang.Object)
*/
@Override
public <T> T invokeSave(T object) {
Assert.state(hasSaveMethod(), "Repository doesn't have a save-method declared!");
return invoke(methods.getSaveMethod(), object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindOneMethod()
*/
@Override
public boolean hasFindOneMethod() {
return methods.hasFindOneMethod();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
*/
@Override
public <T> T invokeFindOne(Serializable id) {
Assert.state(hasFindOneMethod(), "Repository doesn't have a find-one-method declared!");
return invoke(methods.getFindOneMethod(), convertId(id));
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasDeleteMethod()
*/
@Override
public boolean hasDeleteMethod() {
return methods.hasDelete();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
*/
@Override
@SuppressWarnings("unchecked")
public void invokeDelete(Serializable id) {
Assert.notNull(id, "Identifier must not be null!");
Assert.state(hasDeleteMethod(), "Repository doesn't have a delete-method declared!");
Method method = methods.getDeleteMethod();
Class<?> parameterType = method.getParameterTypes()[0];
List<Class<? extends Serializable>> idTypes = Arrays.asList(idType, Serializable.class);
if (idTypes.contains(parameterType)) {
invoke(method, convertId(id));
} else {
invoke(method, invokeFindOne(id));
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.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) {
Assert.notNull(method, "Method must not be null!");
Assert.notNull(parameters, "Parameters must not be null!");
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, PARAM_ANNOTATION).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();
if (!StringUtils.hasText(parameterName)) {
throw new IllegalArgumentException("No @Param annotation found on query method " + method.getName()
+ " for parameter " + parameterName);
}
String[] parameterValue = rawParameters.get(parameterName);
Object value = parameterValue == null ? null : parameterValue.length == 1 ? parameterValue[0] : parameterValue;
result[i] = conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(param));
}
}
return result;
}
/**
* Invokes the given method with the given arguments on the backing repository.
*
* @param method
* @param arguments
* @return
*/
@SuppressWarnings("unchecked")
private <T> T invoke(Method method, Object... arguments) {
ReflectionUtils.makeAccessible(method);
return (T) ReflectionUtils.invokeMethod(method, repository, arguments);
}
/**
* 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, idType);
}
protected Iterable<Object> invokeFindAllReflectively(Pageable pageable) {
Assert.state(hasFindAllMethod(), "Repository doesn't have a find-all-method declared!");
Method method = methods.getFindAllMethod();
Class<?>[] types = method.getParameterTypes();
if (types.length == 0) {
return invoke(method);
}
if (Pageable.class.isAssignableFrom(types[0])) {
return invoke(method, pageable);
}
Sort sort = pageable == null ? null : pageable.getSort();
return invokeFindAll(sort);
}
protected Iterable<Object> invokeFindAllReflectively(Sort sort) {
Assert.state(hasFindAllMethod(), "Repository doesn't have a find-all-method declared!");
Method method = methods.getFindAllMethod();
Class<?>[] types = method.getParameterTypes();
if (types.length == 0) {
return invoke(method);
}
return invoke(method, sort);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.repository.invoker;
/**
* Meta-information about the methods a repository exposes.
*
* @author Oliver Gierke
*/
public interface RepositoryInvocationInformation {
/**
* Returns whether the repository has a method to save objects.
*
* @return
*/
boolean hasSaveMethod();
/**
* Returns whether the repository has a method to delete objects.
*
* @return
*/
boolean hasDeleteMethod();
/**
* Returns whether the repository has a method to find a single object.
*
* @return
*/
boolean hasFindOneMethod();
/**
* Returns whether the repository has a method to find all objects.
*
* @return
*/
boolean hasFindAllMethod();
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.repository.invoker;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* API to invoke (CRUD) methods on Spring Data repository instances independently of the base interface they expose.
* Clients should check the availability of the methods before invoking them by using the methods of
* {@link RepositoryInvocationInformation}.
*
* @author Oliver Gierke
*/
public interface RepositoryInvoker extends RepositoryInvocationInformation {
/**
* Invokes the method equivalent to {@link CrudRepository#save(Object)} on the repository.
*
* @param object
* @return the result of the invocation of the save method
* @throws IllegalStateException if the repository does not expose a save method.
*/
<T> T invokeSave(T object);
/**
* Invokes the method equivalent to {@link CrudRepository#findOne(Serializable)}.
*
* @param id must not be {@literal null}.
* @return the entity with the given id.
* @throws IllegalStateException if the repository does not expose a find-one-method.
*/
<T> T invokeFindOne(Serializable id);
/**
* Invokes the find-all method of the underlying repository using the method taking a {@link Pageable} as parameter if
* available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Pageable)}), using the method taking a
* {@link Sort} if available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Sort)} by extracting
* the {@link Sort} contained in the given {@link Pageable}) or the plain equivalent to
* {@link CrudRepository#findAll()}.
*
* @param pageable can be {@literal null}.
* @return the result of the invocation of the find-all method.
* @throws IllegalStateException if the repository does not expose a find-all-method.
*/
Iterable<Object> invokeFindAll(Pageable pageable);
/**
* Invokes the find-all method of the underlying repository using the method taking a {@link Sort} as parameter if
* available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Sort)}) or the plain equivalent to
* {@link CrudRepository#findAll()}.
*
* @param pageable can be {@literal null}.
* @return the result of the invocation of the find-all method.
* @throws IllegalStateException if the repository does not expose a find-all-method.
*/
Iterable<Object> invokeFindAll(Sort sort);
/**
* Invokes the method equivalent to {@link CrudRepository#delete(Serializable)}. The given id is assumed to be of a
* type convertable into the actual identifier type of the backing repository.
*
* @param id must not be {@literal null}. throws {@link IllegalStateException} if the repository does not expose a
* delete-method.
*/
void invokeDelete(Serializable id);
/**
* Invokes the query method backed by the given {@link Method} using the given parameters, {@link Pageable} and
* {@link Sort}.
*
* @param method must not be {@literal null}.
* @param parameters must not be {@literal null}.
* @param pageable can be {@literal null}.
* @param sort can be {@literal null}.
* @return the result of the invoked query method.
*/
Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort);
}

View File

@@ -0,0 +1,33 @@
/*
* 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.repository.invoker;
/**
* Interface for a factory to create {@link RepositoryInvoker} instances for repositories managing a particular domain
* type.
*
* @author Oliver Gierke
*/
public interface RepositoryInvokerFactory {
/**
* Returns the {@link RepositoryInvoker} for a repository managing the given domain type.
*
* @param domainType must not be {@literal null}.
* @return
*/
RepositoryInvoker getInvokerFor(Class<?> domainType);
}

View File

@@ -1,61 +0,0 @@
/*
* 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.repository.support;
import java.io.Serializable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.util.Assert;
/**
* {@link CrudRepository} based {@link CrudInvoker} calling methods on {@link CrudRepository} directly.
*
* @author Oliver Gierke
* @since 1.6
*/
class CrudRepositoryInvoker<T> implements CrudInvoker<T> {
private final CrudRepository<T, Serializable> repository;
/**
* Creates a new {@link CrudRepositoryInvoker} using the given {@link CrudRepository}.
*
* @param repository must not be {@literal null}.
*/
public CrudRepositoryInvoker(CrudRepository<T, Serializable> repository) {
Assert.notNull(repository, "Repository must not be null!");
this.repository = repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.CrudInvoker#findOne(java.io.Serializable)
*/
@Override
public T invokeFindOne(Serializable id) {
return repository.findOne(id);
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.CrudInvoker#save(java.lang.Object)
*/
@Override
public T invokeSave(T object) {
return repository.save(object);
}
}

View File

@@ -25,8 +25,10 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.util.StringUtils;
/**
@@ -43,6 +45,7 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
private final T conversionService;
private Repositories repositories = Repositories.NONE;
private RepositoryInvokerFactory repositoryInvokerFactory;
public DomainClassConverter(T conversionService) {
this.conversionService = conversionService;
@@ -73,7 +76,7 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
Class<?> domainType = targetType.getType();
RepositoryInformation info = repositories.getRepositoryInformationFor(domainType);
CrudInvoker<?> invoker = repositories.getCrudInvoker(domainType);
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType);
return invoker.invokeFindOne(conversionService.convert(source, info.getIdType()));
}
@@ -104,5 +107,6 @@ public class DomainClassConverter<T extends ConversionService & ConverterRegistr
this.repositories = new Repositories(context);
this.conversionService.addConverter(this);
this.repositoryInvokerFactory = new DefaultRepositoryInvokerFactory(repositories, conversionService);
}
}

View File

@@ -22,8 +22,8 @@ import java.io.Serializable;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -34,7 +34,7 @@ import org.springframework.util.StringUtils;
*/
public class DomainClassPropertyEditor<T, ID extends Serializable> extends PropertyEditorSupport {
private final CrudInvoker<?> invoker;
private final RepositoryInvoker invoker;
private final EntityInformation<T, ID> information;
private final PropertyEditorRegistry registry;
@@ -46,7 +46,7 @@ public class DomainClassPropertyEditor<T, ID extends Serializable> extends Prope
* @param information must not be {@literal null}.
* @param registry must not be {@literal null}.
*/
public DomainClassPropertyEditor(CrudInvoker<?> invoker, EntityInformation<T, ID> information,
public DomainClassPropertyEditor(RepositoryInvoker invoker, EntityInformation<T, ID> information,
PropertyEditorRegistry registry) {
Assert.notNull(invoker);

View File

@@ -21,8 +21,10 @@ import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.web.servlet.DispatcherServlet;
/**
@@ -44,10 +46,13 @@ import org.springframework.web.servlet.DispatcherServlet;
* {@link org.springframework.web.context.WebApplicationContext}.
*
* @author Oliver Gierke
* @deprecated use {@link DomainClassConverter} instead, will be removed in 1.10
*/
@Deprecated
public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistrar, ApplicationContextAware {
private Repositories repositories = Repositories.NONE;
private RepositoryInvokerFactory repositoryInvokerFactory;
/*
* (non-Javadoc)
@@ -58,7 +63,7 @@ public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistr
for (Class<?> domainClass : repositories) {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainClass);
CrudInvoker<?> invoker = repositories.getCrudInvoker(domainClass);
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainClass);
DomainClassPropertyEditor<Object, Serializable> editor = new DomainClassPropertyEditor<Object, Serializable>(
invoker, repositories.getEntityInformationFor(repositoryInformation.getDomainType()), registry);
@@ -72,6 +77,8 @@ public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistr
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext context) {
this.repositories = new Repositories(context);
this.repositoryInvokerFactory = new DefaultRepositoryInvokerFactory(repositories);
}
}

View File

@@ -1,78 +0,0 @@
/*
* 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.repository.support;
import static java.lang.String.*;
import java.io.Serializable;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link CrudInvoker} that uses reflection to invoke repository methods based on the {@link CrudMethods} meta
* information.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @since 1.6
*/
class ReflectionRepositoryInvoker<T> implements CrudInvoker<T> {
private final Object repository;
private final CrudMethods methods;
/**
* Creates a new {@link ReflectionRepositoryInvoker} using the given repository and {@link CrudMethods}.
*
* @param repository must not be {@literal null}.
* @param methods must not be {@literal null}.
*/
public ReflectionRepositoryInvoker(Object repository, CrudMethods methods) {
Assert.notNull(repository, "Repository must not be null!");
Assert.notNull(methods, "CrudMethods must not be null!");
Class<?> type = repository.getClass();
Assert.isTrue(methods.hasFindOneMethod(), format("Repository %s does not expose a findOne(…) method!", type));
Assert.isTrue(methods.hasSaveMethod(), format("Repository %s does not expose a save(…) method!", type));
this.repository = repository;
this.methods = methods;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.CrudInvoker#save(java.lang.Object)
*/
@Override
@SuppressWarnings("unchecked")
public T invokeSave(T object) {
return (T) ReflectionUtils.invokeMethod(methods.getSaveMethod(), repository, object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.CrudInvoker#findOne(java.io.Serializable)
*/
@Override
@SuppressWarnings("unchecked")
public T invokeFindOne(Serializable id) {
return (T) ReflectionUtils.invokeMethod(methods.getFindOneMethod(), repository, id);
}
}

View File

@@ -27,8 +27,6 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
@@ -193,20 +191,6 @@ public class Repositories implements Iterable<Class<?>> {
return getRepositoryFactoryInfoFor(domainClass).getQueryMethods();
}
@SuppressWarnings("unchecked")
public <T> CrudInvoker<T> getCrudInvoker(Class<T> domainClass) {
Object repository = getRepositoryFor(domainClass);
Assert.notNull(repository, String.format("No repository found for domain class: %s", domainClass));
if (repository instanceof CrudRepository) {
return new CrudRepositoryInvoker<T>((CrudRepository<T, Serializable>) repository);
} else {
return new ReflectionRepositoryInvoker<T>(repository, getRepositoryInformationFor(domainClass).getCrudMethods());
}
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()

View File

@@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -46,8 +48,8 @@ public class RepositoryComponentProviderUnitTests {
RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections.<TypeFilter> emptyList());
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository.sample");
assertThat(components.size(), is(1));
assertThat(components.iterator().next().getBeanClassName(), is(SampleAnnotatedRepository.class.getName()));
assertThat(components.size(), is(3));
assertThat(components, hasItem(BeanDefinitionOfTypeMatcher.beanDefinitionOfType(SampleAnnotatedRepository.class)));
}
@Test
@@ -59,7 +61,7 @@ public class RepositoryComponentProviderUnitTests {
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository");
assertThat(components.size(), is(1));
assertThat(components.iterator().next().getBeanClassName(), is(MyOtherRepository.class.getName()));
assertThat(components, hasItem(BeanDefinitionOfTypeMatcher.beanDefinitionOfType(MyOtherRepository.class)));
}
/**
@@ -79,5 +81,40 @@ public class RepositoryComponentProviderUnitTests {
Matchers.<BeanDefinition> hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName))));
}
static class BeanDefinitionOfTypeMatcher extends BaseMatcher<BeanDefinition> {
private final Class<?> expectedType;
private BeanDefinitionOfTypeMatcher(Class<?> expectedType) {
this.expectedType = expectedType;
}
public static BeanDefinitionOfTypeMatcher beanDefinitionOfType(Class<?> expectedType) {
return new BeanDefinitionOfTypeMatcher(expectedType);
}
/*
* (non-Javadoc)
* @see org.hamcrest.Matcher#matches(java.lang.Object)
*/
@Override
public boolean matches(Object item) {
if (!(item instanceof BeanDefinition)) {
return false;
}
BeanDefinition definition = (BeanDefinition) item;
return definition.getBeanClassName().equals(expectedType.getName());
}
/*
* (non-Javadoc)
* @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
*/
@Override
public void describeTo(Description description) {}
}
public interface MyNestedRepository extends Repository<Person, Long> {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,50 +21,62 @@ import static org.mockito.Mockito.*;
import java.util.Collection;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.sample.Product;
import org.springframework.data.repository.sample.ProductRepository;
import org.springframework.data.repository.sample.SampleConfiguration;
import org.springframework.data.repository.sample.User;
import org.springframework.data.repository.support.Repositories;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Unit tests for {@link UnmarshallingRepositoryInitializer}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SampleConfiguration.class)
public class ResourceReaderRepositoryInitializerUnitTests {
@Mock ResourceReader reader;
@Mock Repositories repositories;
@Mock Resource resource;
@Mock CrudInvoker<Object> invoker;
@Autowired ProductRepository productRepository;
@Autowired Repositories repositories;
@Mock ApplicationEventPublisher publisher;
ApplicationEventPublisher publisher;
ResourceReader reader;
Resource resource;
@Before
public void setUp() {
this.reader = mock(ResourceReader.class);
this.publisher = mock(ApplicationEventPublisher.class);
this.resource = mock(Resource.class);
}
@Test
public void storesSingleObjectCorrectly() throws Exception {
Object reference = new Object();
Product reference = new Product();
setUpReferenceAndInititalize(reference);
verify(invoker, times(1)).invokeSave(reference);
verify(productRepository).save(reference);
}
@Test
public void storesCollectionOfObjectsCorrectly() throws Exception {
Object object = new Object();
Collection<Object> reference = Collections.singletonList(object);
Product product = new Product();
Collection<Product> reference = Collections.singletonList(product);
setUpReferenceAndInititalize(reference);
verify(invoker, times(1)).invokeSave(object);
verify(productRepository, times(1)).save(product);
}
/**
@@ -73,7 +85,7 @@ public class ResourceReaderRepositoryInitializerUnitTests {
@Test
public void emitsRepositoriesPopulatedEventIfPublisherConfigured() throws Exception {
RepositoryPopulator populator = setUpReferenceAndInititalize(new Object(), publisher);
RepositoryPopulator populator = setUpReferenceAndInititalize(new User(), publisher);
ApplicationEvent event = new RepositoriesPopulatedEvent(populator, repositories);
verify(publisher, times(1)).publishEvent(event);
@@ -83,7 +95,6 @@ public class ResourceReaderRepositoryInitializerUnitTests {
throws Exception {
when(reader.readFrom(any(Resource.class), any(ClassLoader.class))).thenReturn(reference);
when(repositories.getCrudInvoker(Object.class)).thenReturn(invoker);
ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader);
populator.setResources(resource);

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.invoker;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor;
import org.springframework.data.repository.query.Param;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Unit tests for {@link CrudRepositoryInvoker}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class CrudRepositoryInvokerUnitTests {
@Mock PersonRepository personRepository;
@Mock OrderRepository orderRepository;
/**
* @see DATACMNS-589, DATAREST-216
*/
@Test
public void invokesRedeclaredSave() {
getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeSave(new Order());
}
/**
* @see DATACMNS-589, DATAREST-216
*/
@Test
public void invokesRedeclaredFindOne() {
getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeFindOne(1L);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesRedeclaredDelete() throws Exception {
getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeDelete(1L);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesSaveOnCrudRepository() throws Exception {
Method method = CrudRepository.class.getMethod("save", Object.class);
getInvokerFor(personRepository, expectInvocationOf(method)).invokeSave(new Person());
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindOneOnCrudRepository() throws Exception {
Method method = CrudRepository.class.getMethod("findOne", Serializable.class);
getInvokerFor(personRepository, expectInvocationOf(method)).invokeFindOne(1L);
}
/**
* @see DATACMNS-589, DATAREST-216
*/
@Test
public void invokesDeleteOnCrudRepository() throws Exception {
Method method = CrudRepository.class.getMethod("delete", Serializable.class);
getInvokerFor(personRepository, expectInvocationOf(method)).invokeDelete(1L);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllOnCrudRepository() throws Exception {
Method method = CrudRepository.class.getMethod("findAll");
getInvokerFor(orderRepository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
getInvokerFor(orderRepository, expectInvocationOf(method)).invokeFindAll((Sort) null);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesCustomFindAllTakingASort() throws Exception {
CrudWithFindAllWithSort repository = mock(CrudWithFindAllWithSort.class);
Method findAllWithSort = CrudWithFindAllWithSort.class.getMethod("findAll", Sort.class);
getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll((Pageable) null);
getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll(new PageRequest(0, 10));
getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll((Sort) null);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesCustomFindAllTakingAPageable() throws Exception {
CrudWithFindAllWithPageable repository = mock(CrudWithFindAllWithPageable.class);
Method findAllWithPageable = CrudWithFindAllWithPageable.class.getMethod("findAll", Pageable.class);
getInvokerFor(repository, expectInvocationOf(findAllWithPageable)).invokeFindAll((Pageable) null);
getInvokerFor(repository, expectInvocationOf(findAllWithPageable)).invokeFindAll(new PageRequest(0, 10));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) {
Object proxy = getVerifyingRepositoryProxy(repository, interceptor);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]);
GenericConversionService conversionService = new DefaultFormattingConversionService();
return new CrudRepositoryInvoker((CrudRepository) proxy, metadata, conversionService);
}
static class Order {}
interface OrderRepository extends CrudRepository<Order, Long> {
@Override
<S extends Order> S save(S entity);
@Override
Order findOne(Long id);
@Override
void delete(Long id);
}
static class Person {}
interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
Pageable pageable);
}
interface CrudWithFindAllWithSort extends CrudRepository<Order, Long> {
List<Order> findAll(Sort sort);
}
interface CrudWithFindAllWithPageable extends CrudRepository<Order, Long> {
List<Order> findAll(Pageable sort);
}
interface CrudWithRedeclaredDelete extends CrudRepository<Order, Long> {
void delete(Long id);
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.invoker;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.sample.Product;
import org.springframework.data.repository.sample.ProductRepository;
import org.springframework.data.repository.sample.SampleConfiguration;
import org.springframework.data.repository.sample.User;
import org.springframework.data.repository.support.Repositories;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link DefaultRepositoryInvokerFactory}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SampleConfiguration.class)
public class DefaultRepositoryInvokerFactoryIntegrationTests {
@Autowired ProductRepository productRepository;
@Autowired Repositories repositories;
RepositoryInvokerFactory factory;
@Rule public ExpectedException exception = ExpectedException.none();
@Before
public void setUp() {
this.factory = new DefaultRepositoryInvokerFactory(repositories);
}
/**
* @see DATACMNS-410, DATACMNS-589
*/
@Test
public void findOneShouldDelegateToAppropriateRepository() {
Mockito.reset(productRepository);
Product product = new Product();
when(productRepository.findOne(4711L)).thenReturn(product);
assertThat(factory.getInvokerFor(Product.class).invokeFindOne(4711L), is((Object) product));
}
/**
* @see DATACMNS-374, DATACMNS-589
*/
@Test
public void shouldThrowMeaningfulExceptionWhenTheRepositoryForAGivenDomainClassCannotBeFound() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("No repository found for domain type: ");
exception.expectMessage(Object.class.getName());
factory.getInvokerFor(Object.class);
}
/**
* @see DATACMNS-589
*/
@Test
public void returnsSameInvokerInstanceForSubsequentCalls() {
RepositoryInvoker invoker = factory.getInvokerFor(Product.class);
assertThat(factory.getInvokerFor(Product.class), is(invoker));
}
/**
* @see DATACMNS-589
*/
@Test
public void createsReflectionRepositoryInvokerForRepositoryNotExtendingADedicatedBaseRepository() {
RepositoryInvoker invoker = factory.getInvokerFor(Product.class);
assertThat(invoker, is(instanceOf(ReflectionRepositoryInvoker.class)));
assertThat(invoker, is(not(instanceOf(CrudRepositoryInvoker.class))));
}
/**
* @see DATACMNS-589
*/
@Test
public void createsCrudRepositoryInvokerForRepositoryExtendingCrudRepository() {
RepositoryInvoker invoker = factory.getInvokerFor(User.class);
assertThat(invoker, is(instanceOf(CrudRepositoryInvoker.class)));
assertThat(invoker, is(not(instanceOf(PagingAndSortingRepositoryInvoker.class))));
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.invoker;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Unit tests for {@link PagingAndSortingRepositoryInvoker}.
*
* @author Oliver Gierke
*/
public class PaginginAndSortingRepositoryInvokerUnitTests {
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllWithPageableByDefault() throws Exception {
Repository repository = mock(Repository.class);
Method method = PagingAndSortingRepository.class.getMethod("findAll", Pageable.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllWithSortByDefault() throws Exception {
Repository repository = mock(Repository.class);
Method method = PagingAndSortingRepository.class.getMethod("findAll", Sort.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo"));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesRedeclaredFindAllWithPageable() throws Exception {
RepositoryWithRedeclaredFindAllWithPageable repository = mock(RepositoryWithRedeclaredFindAllWithPageable.class);
Method method = RepositoryWithRedeclaredFindAllWithPageable.class.getMethod("findAll", Pageable.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesRedeclaredFindAllWithSort() throws Exception {
RepositoryWithRedeclaredFindAllWithSort repository = mock(RepositoryWithRedeclaredFindAllWithSort.class);
Method method = RepositoryWithRedeclaredFindAllWithSort.class.getMethod("findAll", Sort.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo"));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static RepositoryInvoker getInvokerFor(Object repository) {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]);
GenericConversionService conversionService = new DefaultFormattingConversionService();
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository, metadata, conversionService);
}
private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) {
return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor));
}
interface Repository extends PagingAndSortingRepository<Object, Long> {}
interface RepositoryWithRedeclaredFindAllWithPageable extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Pageable pageable);
}
interface RepositoryWithRedeclaredFindAllWithSort extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Sort sort);
}
}

View File

@@ -0,0 +1,293 @@
/*
* 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.repository.invoker;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.invoker.CrudRepositoryInvokerUnitTests.Person;
import org.springframework.data.repository.invoker.CrudRepositoryInvokerUnitTests.PersonRepository;
import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Integration tests for {@link ReflectionRepositoryInvoker}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectionRepositoryInvokerUnitTests {
static final Page<Person> EMPTY_PAGE = new PageImpl<Person>(Collections.<Person> emptyList());
ConversionService conversionService;
@Before
public void setUp() {
this.conversionService = new DefaultFormattingConversionService();
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesSaveMethodCorrectly() throws Exception {
ManualCrudRepository repository = mock(ManualCrudRepository.class);
Method method = ManualCrudRepository.class.getMethod("save", Domain.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeSave(new Domain());
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindOneCorrectly() throws Exception {
ManualCrudRepository repository = mock(ManualCrudRepository.class);
Method method = ManualCrudRepository.class.getMethod("findOne", Long.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindOne("1");
getInvokerFor(repository, expectInvocationOf(method)).invokeFindOne(1L);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesDeleteWithDomainCorrectly() throws Exception {
RepoWithDomainDeleteAndFindOne repository = mock(RepoWithDomainDeleteAndFindOne.class);
when(repository.findOne(1L)).thenReturn(new Domain());
Method findOneMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("findOne", Long.class);
Method deleteMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("delete", Domain.class);
getInvokerFor(repository, expectInvocationOf(findOneMethod, deleteMethod)).invokeDelete(1L);
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllWithoutParameterCorrectly() throws Exception {
Method method = ManualCrudRepository.class.getMethod("findAll");
ManualCrudRepository repository = mock(ManualCrudRepository.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo"));
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllWithSortCorrectly() throws Exception {
Method method = RepoWithFindAllWithSort.class.getMethod("findAll", Sort.class);
RepoWithFindAllWithSort repository = mock(RepoWithFindAllWithSort.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo"));
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesFindAllWithPageableCorrectly() throws Exception {
Method method = RepoWithFindAllWithPageable.class.getMethod("findAll", Pageable.class);
RepoWithFindAllWithPageable repository = mock(RepoWithFindAllWithPageable.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10));
}
/**
* @see DATACMNS-589
*/
@Test
public void invokesQueryMethod() throws Exception {
HashMap<String, String[]> parameters = new HashMap<String, String[]>();
parameters.put("firstName", new String[] { "John" });
Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class);
PersonRepository repository = mock(PersonRepository.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, null, null);
}
/**
* @see DATACMNS-589
*/
@Test
public void considersFormattingAnnotationsOnQueryMethodParameters() throws Exception {
HashMap<String, String[]> parameters = new HashMap<String, String[]>();
parameters.put("date", new String[] { "2013-07-18T10:49:00.000+02:00" });
Method method = PersonRepository.class.getMethod("findByCreatedUsingISO8601Date", Date.class, Pageable.class);
PersonRepository repository = mock(PersonRepository.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, null, null);
}
/**
* @see DATAREST-335, DATAREST-346, DATACMNS-589
*/
@Test
public void invokesOverriddenDeleteMethodCorrectly() throws Exception {
MyRepo repository = mock(MyRepo.class);
Method method = CustomRepo.class.getMethod("delete", Long.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeDelete("1");
}
/**
* @see DATACMNS-589
*/
@Test(expected = IllegalStateException.class)
public void rejectsInvocationOfMissingDeleteMethod() {
RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class));
assertThat(invoker.hasDeleteMethod(), is(false));
invoker.invokeDelete(1L);
}
/**
* @see DATACMNS-589
*/
@Test(expected = IllegalStateException.class)
public void rejectsInvocationOfMissingFindOneMethod() {
RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class));
assertThat(invoker.hasFindOneMethod(), is(false));
invoker.invokeFindOne(1L);
}
/**
* @see DATACMNS-589
*/
@Test(expected = IllegalStateException.class)
public void rejectsInvocationOfMissingFindAllMethod() {
RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class));
assertThat(invoker.hasFindAllMethod(), is(false));
invoker.invokeFindAll((Pageable) null);
}
/**
* @see DATACMNS-589
*/
@Test(expected = IllegalStateException.class)
public void rejectsInvocationOfMissingSaveMethod() {
RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class));
assertThat(invoker.hasSaveMethod(), is(false));
invoker.invokeSave(new Object());
}
private static RepositoryInvoker getInvokerFor(Object repository) {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]);
GenericConversionService conversionService = new DefaultFormattingConversionService();
return new ReflectionRepositoryInvoker(repository, metadata, conversionService);
}
private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) {
return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor));
}
interface MyRepo extends CustomRepo, CrudRepository<Domain, Long> {}
class Domain {}
interface CustomRepo {
void delete(Long id);
}
interface EmptyRepository extends Repository<Domain, Long> {}
interface ManualCrudRepository extends Repository<Domain, Long> {
Domain findOne(Long id);
Iterable<Domain> findAll();
<T extends Domain> T save(T entity);
void delete(Long id);
}
interface RepoWithFindAllWithoutParameters extends Repository<Domain, Long> {
List<Domain> findAll();
}
interface RepoWithFindAllWithPageable extends Repository<Domain, Long> {
Page<Domain> findAll(Pageable pageable);
}
interface RepoWithFindAllWithSort extends Repository<Domain, Long> {
Page<Domain> findAll(Sort sort);
}
interface RepoWithDomainDeleteAndFindOne extends Repository<Domain, Long> {
Domain findOne(Long id);
void delete(Domain entity);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.invoker;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
/**
* Utility methods to create {@link RepositoryInvoker} instances that get a verifying proxy attached so that the
* invocation of a given target methods or type can be verified.
*
* @author Oliver Gierke
*/
class RepositoryInvocationTestUtils {
@SuppressWarnings("unchecked")
public static <T> T getVerifyingRepositoryProxy(T target, VerifyingMethodInterceptor interceptor) {
ProxyFactory factory = new ProxyFactory();
factory.setInterfaces(target.getClass().getInterfaces());
factory.setTarget(target);
factory.addAdvice(interceptor);
return (T) factory.getProxy();
}
public static VerifyingMethodInterceptor expectInvocationOnType(Class<?> type) {
return new VerifyingMethodInterceptor(type, new Method[0]);
}
public static VerifyingMethodInterceptor expectInvocationOf(Method... methods) {
return new VerifyingMethodInterceptor(null, methods);
}
/**
* {@link MethodInterceptor} to verifiy the invocation was triggered on the given type.
*
* @author Oliver Gierke
*/
@SuppressWarnings("rawtypes")
public static final class VerifyingMethodInterceptor implements MethodInterceptor {
private final Class expectedInvocationTarget;
private final List<Method> methods;
private VerifyingMethodInterceptor(Class<?> expectedInvocationTarget, Method... methods) {
this.expectedInvocationTarget = expectedInvocationTarget;
this.methods = Arrays.asList(methods);
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (!methods.isEmpty()) {
assertThat(methods, hasItem(invocation.getMethod()));
} else {
Class<?> type = invocation.getMethod().getDeclaringClass();
assertThat("Expected methods invocation on " + expectedInvocationTarget + " but was invoked on " + type + "!",
type, is(equalTo(expectedInvocationTarget)));
}
return invocation.proceed();
}
}
}

View File

@@ -0,0 +1,3 @@
package org.springframework.data.repository.sample;
public class Product {}

View File

@@ -0,0 +1,10 @@
package org.springframework.data.repository.sample;
import org.springframework.data.repository.Repository;
public interface ProductRepository extends Repository<Product, Long> {
Product findOne(Long id);
Product save(Product product);
}

View File

@@ -0,0 +1,48 @@
package org.springframework.data.repository.sample;
import static org.mockito.Mockito.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.support.Repositories;
@Configuration
public class SampleConfiguration {
@Autowired ApplicationContext context;
@Bean
public Repositories repositories() {
return new Repositories(context);
}
@Bean
public RepositoryFactoryBeanSupport<Repository<User, Long>, User, Long> userRepositoryFactory() {
DummyRepositoryFactoryBean<Repository<User, Long>, User, Long> factory = new DummyRepositoryFactoryBean<Repository<User, Long>, User, Long>();
factory.setRepositoryInterface(UserRepository.class);
return factory;
}
@Bean
public RepositoryFactoryBeanSupport<Repository<Product, Long>, Product, Long> productRepositoryFactory(
ProductRepository productRepository) {
DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long> factory = new DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long>();
factory.setRepositoryInterface(ProductRepository.class);
factory.setCustomImplementation(productRepository);
return factory;
}
@Bean
public ProductRepository productRepository() {
return mock(ProductRepository.class);
}
}

View File

@@ -0,0 +1,3 @@
package org.springframework.data.repository.sample;
public class User {}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.repository.sample;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
}

View File

@@ -38,6 +38,7 @@ import org.springframework.data.repository.core.support.DummyRepositoryFactoryBe
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("deprecation")
public class DomainClassPropertyEditorRegistrarUnitTests {
@Mock PropertyEditorRegistry registry;

View File

@@ -28,8 +28,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.data.domain.Persistable;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.invoker.RepositoryInvoker;
/**
* Unit test for {@link DomainClassPropertyEditor}.
@@ -42,14 +42,14 @@ public class DomainClassPropertyEditorUnitTests {
DomainClassPropertyEditor<User, Integer> editor;
@Mock PropertyEditorRegistry registry;
@Mock CrudInvoker<User> userRepository;
@Mock RepositoryInvoker invoker;
@Mock EntityInformation<User, Integer> information;
@Before
public void setUp() {
when(information.getIdType()).thenReturn(Integer.class);
editor = new DomainClassPropertyEditor<User, Integer>(userRepository, information, registry);
editor = new DomainClassPropertyEditor<User, Integer>(invoker, information, registry);
}
@Test
@@ -57,11 +57,11 @@ public class DomainClassPropertyEditorUnitTests {
User user = new User(1);
when(information.getId(user)).thenReturn(user.getId());
when(userRepository.invokeFindOne(1)).thenReturn(user);
when(invoker.invokeFindOne(1)).thenReturn(user);
editor.setAsText("1");
verify(userRepository, times(1)).invokeFindOne(1);
verify(invoker, times(1)).invokeFindOne(1);
}
@Test

View File

@@ -1,109 +0,0 @@
/*
* 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.repository.support;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.CrudMethods;
/**
* Unit tests for {@link ReflectionRepositoryInvoker}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectionRepositoryInvokerUnitTests {
@Mock CrudRepository<Object, Serializable> repo;
@Mock CrudMethods methods;
@Test
public void createsInvokerForRepositoryExposingBothFindAllAndSaveMethod() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(repo, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullRepository() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(null, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsRepositoryIfItDoesntExposeAFindOneMethod() {
when(methods.hasFindOneMethod()).thenReturn(false);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(repo, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsRepositoryIfItDoesntExposeASaveMethod() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(false);
new ReflectionRepositoryInvoker<Object>(repo, methods);
}
/**
* @see DATACMNS-410
*/
@Test
public void invokesFindOneCorrectly() throws Exception {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
when(methods.getFindOneMethod()).thenReturn(CrudRepository.class.getMethod("findOne", Serializable.class));
CrudInvoker<Object> invoker = new ReflectionRepositoryInvoker<Object>(repo, methods);
invoker.invokeFindOne(1L);
verify(repo, times(1)).findOne(1L);
}
/**
* @see DATACMNS-410
*/
@Test
public void invokesSaveCorrectly() throws Exception {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
when(methods.getSaveMethod()).thenReturn(CrudRepository.class.getMethod("save", Object.class));
CrudInvoker<Object> invoker = new ReflectionRepositoryInvoker<Object>(repo, methods);
Object object = new Object();
invoker.invokeSave(object);
verify(repo, times(1)).save(object);
}
}

View File

@@ -21,16 +21,11 @@ import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.sample.Product;
import org.springframework.data.repository.sample.ProductRepository;
import org.springframework.data.repository.sample.SampleConfiguration;
import org.springframework.data.repository.sample.User;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -41,45 +36,9 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@ContextConfiguration(classes = SampleConfiguration.class)
public class RepositoriesIntegrationTests {
@Configuration
static class Config {
@Autowired ApplicationContext context;
@Bean
public Repositories repositories() {
return new Repositories(context);
}
@Bean
public RepositoryFactoryBeanSupport<Repository<User, Long>, User, Long> userRepositoryFactory() {
DummyRepositoryFactoryBean<Repository<User, Long>, User, Long> factory = new DummyRepositoryFactoryBean<Repository<User, Long>, User, Long>();
factory.setRepositoryInterface(UserRepository.class);
return factory;
}
@Bean
public RepositoryFactoryBeanSupport<Repository<Product, Long>, Product, Long> productRepositoryFactory(
ProductRepository productRepository) {
DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long> factory = new DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long>();
factory.setRepositoryInterface(ProductRepository.class);
factory.setCustomImplementation(productRepository);
return factory;
}
@Bean
public ProductRepository productRepository() {
return mock(ProductRepository.class);
}
}
@Autowired Repositories repositories;
@Autowired ProductRepository productRepository;
@@ -91,14 +50,6 @@ public class RepositoriesIntegrationTests {
assertThat(repositories.hasRepositoryFor(Product.class), is(true));
}
@Test
public void createsCrudInvokersCorrectly() {
assertThat(repositories, is(notNullValue()));
assertThat(repositories.getCrudInvoker(User.class), is(instanceOf(CrudRepositoryInvoker.class)));
assertThat(repositories.getCrudInvoker(Product.class), is(instanceOf(ReflectionRepositoryInvoker.class)));
}
/**
* @see DATACMNS-376
*/
@@ -108,36 +59,4 @@ public class RepositoriesIntegrationTests {
User user = mock(User.class);
assertThat(repositories.getPersistentEntity(user.getClass()), is(notNullValue()));
}
/**
* @see DATACMNS-410
*/
@Test
public void findOneShouldDelegateToAppropriateRepository() {
Mockito.reset(productRepository);
Product product = new Product();
when(productRepository.findOne(4711L)).thenReturn(product);
CrudInvoker<Product> crudInvoker = repositories.getCrudInvoker(Product.class);
assertThat(crudInvoker.invokeFindOne(4711L), is(product));
}
static class User {
}
interface UserRepository extends CrudRepository<User, Long> {
}
public static class Product {}
public static interface ProductRepository extends Repository<Product, Long> {
Product findOne(Long id);
Product save(Product product);
}
}

View File

@@ -25,9 +25,7 @@ import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -59,8 +57,6 @@ public class RepositoriesUnitTests {
GenericApplicationContext context;
@Rule public ExpectedException exception = ExpectedException.none();
@Before
public void setUp() {
@@ -112,38 +108,13 @@ public class RepositoriesUnitTests {
assertThat(repositories.getPersistentEntity(Address.class), is(notNullValue()));
}
/**
* @see DATACMNS-374
*/
@Test
public void shouldThrowMeaningfulExceptionWhenTheRepositoryForAGivenDomainClassCannotBeFound() {
class Person {}
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("No repository found for domain class: "));
class Address {}
Repositories repositories = new Repositories(context);
repositories.getCrudInvoker(EntityWithoutRepository.class);
}
interface PersonRepository extends CrudRepository<Person, Long> {}
class Person {
}
class Address {
}
class EntityWithoutRepository {
}
interface PersonRepository extends CrudRepository<Person, Long> {
}
interface AddressRepository extends Repository<Address, Long> {
}
interface AddressRepository extends Repository<Address, Long> {}
static class SampleRepoFactoryInformation<T, S extends Serializable> implements RepositoryFactoryInformation<T, S> {