DATACMNS-452 - Added some helper types to simplify the implementations in store modules.
Added AnnotatedTypeScanner to easily scan for annotated types within a set of base packages. Picks up interfaces by default which had to be customized when using ClassPathScanningCanidateComponentProvider. Added DirectFieldAccessFallbackBeanWrapper that will use direct field access for beans in case no accessor method can be found. This allows to easily implement bean property values through accessors first but using the field directly if the accessors aren't available. Introduced AnnotationDetectionMethodCallback to easily find methods equipped with a given annotation.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Scanner to find types with annotations on the classpath.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotatedTypeScanner {
|
||||
|
||||
private final Iterable<Class<? extends Annotation>> annotationTypess;
|
||||
private final boolean considerInterfaces;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
|
||||
*
|
||||
* @param annotationTypes the annotation types to scan for.
|
||||
*/
|
||||
public AnnotatedTypeScanner(Class<? extends Annotation>... annotationTypes) {
|
||||
this(true, annotationTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
|
||||
*
|
||||
* @param considerInterfaces whether to consider interfaces as well.
|
||||
* @param annotationTypes the annotations to scan for.
|
||||
*/
|
||||
public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {
|
||||
|
||||
this.annotationTypess = Arrays.asList(annotationTypes);
|
||||
this.considerInterfaces = considerInterfaces;
|
||||
}
|
||||
|
||||
public Set<Class<?>> findTypes(String... basePackages) {
|
||||
return findTypes(Arrays.asList(basePackages));
|
||||
}
|
||||
|
||||
public Set<Class<?>> findTypes(Iterable<String> basePackages) {
|
||||
|
||||
ClassPathScanningCandidateComponentProvider provider = new InterfaceAwareScanner(considerInterfaces);
|
||||
|
||||
for (Class<? extends Annotation> annotationType : annotationTypess) {
|
||||
provider.addIncludeFilter(new AnnotationTypeFilter(annotationType, true, considerInterfaces));
|
||||
}
|
||||
|
||||
Set<Class<?>> types = new HashSet<Class<?>>();
|
||||
|
||||
for (String basePackage : basePackages) {
|
||||
|
||||
for (BeanDefinition definition : provider.findCandidateComponents(basePackage)) {
|
||||
try {
|
||||
types.add(ClassUtils.forName(definition.getBeanClassName(), getClass().getClassLoader()));
|
||||
} catch (ClassNotFoundException o_O) {
|
||||
throw new IllegalStateException(o_O);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom extension of {@link ClassPathScanningCandidateComponentProvider} to make sure interfaces to not get dropped
|
||||
* from scanning results.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class InterfaceAwareScanner extends ClassPathScanningCandidateComponentProvider {
|
||||
|
||||
private final boolean considerInterfaces;
|
||||
|
||||
public InterfaceAwareScanner(boolean considerInterfaces) {
|
||||
super(false);
|
||||
this.considerInterfaces = considerInterfaces;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
return super.isCandidateComponent(beanDefinition) || considerInterfaces
|
||||
&& beanDefinition.getMetadata().isInterface();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* {@link MethodCallback} to find annotations of a given type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationDetectionMethodCallback<A extends Annotation> implements MethodCallback {
|
||||
|
||||
private static final String MULTIPLE_FOUND = "Found annotation %s both on %s and %s! Make sure only one of them is annotated with it!";
|
||||
|
||||
private final boolean enforceUniqueness;
|
||||
private final Class<A> annotationType;
|
||||
|
||||
private Method foundMethod;
|
||||
private A annotation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationDetectionMethodCallback} for the given annotation type.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationDetectionMethodCallback(Class<A> annotationType) {
|
||||
this(annotationType, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationDetectionMethodCallback} for the given annotation type.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @param enforceUniqueness whether to fail if multiple methods with the annotation are found.
|
||||
*/
|
||||
public AnnotationDetectionMethodCallback(Class<A> annotationType, boolean enforceUniqueness) {
|
||||
|
||||
Assert.notNull(annotationType, "Annotation type must not be null!");
|
||||
|
||||
this.annotationType = annotationType;
|
||||
this.enforceUniqueness = enforceUniqueness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the method
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return foundMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation
|
||||
*/
|
||||
public A getAnnotation() {
|
||||
return annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an annotation was found.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasFoundAnnotation() {
|
||||
return annotation != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.ReflectionUtils.MethodCallback#doWith(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
if (foundMethod != null && !enforceUniqueness) {
|
||||
return;
|
||||
}
|
||||
|
||||
A annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
|
||||
if (annotation != null) {
|
||||
|
||||
if (foundMethod != null && enforceUniqueness) {
|
||||
throw new IllegalStateException(String.format(MULTIPLE_FOUND, annotation.getClass().getName(), foundMethod,
|
||||
method));
|
||||
}
|
||||
|
||||
this.annotation = annotation;
|
||||
this.foundMethod = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.util;
|
||||
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.NotReadablePropertyException;
|
||||
import org.springframework.beans.NotWritablePropertyException;
|
||||
|
||||
/**
|
||||
* Custom extension of {@link BeanWrapperImpl} that falls back to direct field access in case the object or type being
|
||||
* wrapped does not use accessor methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DirectFieldAccessFallbackBeanWrapper extends BeanWrapperImpl {
|
||||
|
||||
public DirectFieldAccessFallbackBeanWrapper(Object entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.BeanWrapperImpl#getPropertyValue(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Object getPropertyValue(String propertyName) {
|
||||
|
||||
try {
|
||||
return super.getPropertyValue(propertyName);
|
||||
} catch (NotReadablePropertyException e) {
|
||||
|
||||
Field field = findField(getWrappedClass(), propertyName);
|
||||
|
||||
if (field == null) {
|
||||
throw new NotReadablePropertyException(getWrappedClass(), propertyName,
|
||||
"Could not find field for property during fallback access!");
|
||||
}
|
||||
|
||||
makeAccessible(field);
|
||||
return getField(field, getWrappedInstance());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.BeanWrapperImpl#setPropertyValue(java.lang.String, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void setPropertyValue(String propertyName, Object value) {
|
||||
|
||||
try {
|
||||
super.setPropertyValue(propertyName, value);
|
||||
} catch (NotWritablePropertyException e) {
|
||||
|
||||
Field field = findField(getWrappedClass(), propertyName);
|
||||
|
||||
if (field == null) {
|
||||
throw new NotWritablePropertyException(getWrappedClass(), propertyName,
|
||||
"Could not find field for property during fallback access!");
|
||||
}
|
||||
|
||||
makeAccessible(field);
|
||||
setField(field, getWrappedInstance(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotatedTypeScannerUnitTests}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotatedTypeScannerUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findsAnnotatedTypes() {
|
||||
|
||||
AnnotatedTypeScanner scanner = new AnnotatedTypeScanner(Persistent.class);
|
||||
Set<Class<?>> types = scanner.findTypes(AnnotatedTypeScanner.class.getPackage().getName());
|
||||
|
||||
assertThat(types, hasItem(Type.class));
|
||||
}
|
||||
|
||||
@Persistent
|
||||
static class Type {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationDetectionMethodCallback}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationDetectionMethodCallbackUnitTests {
|
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none();
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test
|
||||
public void findsMethodWithAnnotation() throws Exception {
|
||||
|
||||
AnnotationDetectionMethodCallback<Value> callback = new AnnotationDetectionMethodCallback<Value>(Value.class);
|
||||
ReflectionUtils.doWithMethods(Sample.class, callback);
|
||||
|
||||
assertThat(callback.hasFoundAnnotation(), is(true));
|
||||
assertThat(callback.getMethod(), is(Sample.class.getMethod("getValue")));
|
||||
assertThat(callback.getAnnotation(), is(notNullValue()));
|
||||
assertThat(callback.getAnnotation().value(), is("#{null}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test
|
||||
public void detectsAmbiguousAnnotations() {
|
||||
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectMessage("Value");
|
||||
exception.expectMessage("getValue");
|
||||
exception.expectMessage("getOtherValue");
|
||||
|
||||
AnnotationDetectionMethodCallback<Value> callback = new AnnotationDetectionMethodCallback<Value>(Value.class, true);
|
||||
ReflectionUtils.doWithMethods(Multiple.class, callback);
|
||||
}
|
||||
|
||||
interface Sample {
|
||||
|
||||
@Value("#{null}")
|
||||
Object getValue();
|
||||
}
|
||||
|
||||
interface Multiple {
|
||||
|
||||
@Value("#{null}")
|
||||
Object getValue();
|
||||
|
||||
@Value("#{null}")
|
||||
Object getOtherValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.NotReadablePropertyException;
|
||||
import org.springframework.beans.NotWritablePropertyException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DirectFieldAccessFallbackBeanWrapper}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DirectFieldAccessFallbackBeanWrapperUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test
|
||||
public void usesFieldAccessForReadIfNoAccessorCanBeFound() {
|
||||
|
||||
Sample sample = new Sample();
|
||||
sample.firstname = "Dave";
|
||||
|
||||
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(sample);
|
||||
|
||||
assertThat(wrapper.getPropertyValue("firstname"), is((Object) "Dave"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test
|
||||
public void usesFieldAccessForWriteIfNoAccessorCanBeFound() {
|
||||
|
||||
Sample sample = new Sample();
|
||||
|
||||
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(sample);
|
||||
wrapper.setPropertyValue("firstname", "Dave");
|
||||
|
||||
assertThat(sample.firstname, is("Dave"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test(expected = NotReadablePropertyException.class)
|
||||
public void throwsAppropriateExceptionIfNoFieldFoundForRead() {
|
||||
|
||||
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(new Sample());
|
||||
wrapper.getPropertyValue("lastname");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-452
|
||||
*/
|
||||
@Test(expected = NotWritablePropertyException.class)
|
||||
public void throwsAppropriateExceptionIfNoFieldFoundForWrite() {
|
||||
|
||||
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(new Sample());
|
||||
wrapper.setPropertyValue("lastname", "Matthews");
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
String firstname;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user