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()