diff --git a/src/main/java/org/springframework/data/repository/support/AnnotationAttribute.java b/src/main/java/org/springframework/data/repository/support/AnnotationAttribute.java new file mode 100644 index 000000000..656817389 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/support/AnnotationAttribute.java @@ -0,0 +1,106 @@ +/* + * 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.repository.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; + +/** + * Simply helper to reference a dedicated attribute of an {@link Annotation}. + * + * @author Oliver Gierke + * @since 1.10 + */ +class AnnotationAttribute { + + private final Class annotationType; + private final String attributeName; + + /** + * Creates a new {@link AnnotationAttribute} to the {@code value} attribute of the given {@link Annotation} type. + * + * @param annotationType must not be {@literal null}. + */ + public AnnotationAttribute(Class annotationType) { + this(annotationType, null); + } + + /** + * Creates a new {@link AnnotationAttribute} for the given {@link Annotation} type and annotation attribute name. + * + * @param annotationType must not be {@literal null}. + * @param attributeName can be {@literal null}, defaults to {@code value}. + */ + public AnnotationAttribute(Class annotationType, String attributeName) { + + Assert.notNull(annotationType); + + this.annotationType = annotationType; + this.attributeName = attributeName; + } + + /** + * Returns the annotation type. + * + * @return the annotationType + */ + public Class getAnnotationType() { + return annotationType; + } + + /** + * Reads the {@link Annotation} attribute's value from the given {@link MethodParameter}. + * + * @param parameter must not be {@literal null}. + * @return + */ + public Object getValueFrom(MethodParameter parameter) { + + Assert.notNull(parameter, "MethodParameter must not be null!"); + Annotation annotation = parameter.getParameterAnnotation(annotationType); + return annotation == null ? null : getValueFrom(annotation); + } + + /** + * Reads the {@link Annotation} attribute's value from the given {@link AnnotatedElement}. + * + * @param annotatedElement must not be {@literal null}. + * @return + */ + public Object getValueFrom(AnnotatedElement annotatedElement) { + + Assert.notNull(annotatedElement, "Annotated element must not be null!"); + Annotation annotation = annotatedElement.getAnnotation(annotationType); + return annotation == null ? null : getValueFrom(annotation); + } + + /** + * Returns the {@link Annotation} attribute's value from the given {@link Annotation}. + * + * @param annotation must not be {@literal null}. + * @return + */ + public Object getValueFrom(Annotation annotation) { + + Assert.notNull(annotation, "Annotation must not be null!"); + return (String) (attributeName == null ? AnnotationUtils.getValue(annotation) : AnnotationUtils.getValue( + annotation, attributeName)); + } +} diff --git a/src/main/java/org/springframework/data/repository/support/MethodParameters.java b/src/main/java/org/springframework/data/repository/support/MethodParameters.java new file mode 100644 index 000000000..fbd8524e1 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/support/MethodParameters.java @@ -0,0 +1,187 @@ +/* + * 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.repository.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.util.Assert; + +/** + * Value object to represent {@link MethodParameters} to allow to easily find the ones with a given annotation. + * + * @author Oliver Gierke + * @since 1.10 + */ +class MethodParameters { + + private static final ParameterNameDiscoverer DISCOVERER = new DefaultParameterNameDiscoverer(); + + private final List parameters; + + /** + * Creates a new {@link MethodParameters} from the given {@link Method}. + * + * @param method must not be {@literal null}. + */ + public MethodParameters(Method method) { + this(method, null); + } + + /** + * Creates a new {@link MethodParameters} for the given {@link Method} and {@link AnnotationAttribute}. If the latter + * is given, method parameter names will be looked up from the annotation attribute if present. + * + * @param method must not be {@literal null}. + * @param namingAnnotation can be {@literal null}. + */ + public MethodParameters(Method method, AnnotationAttribute namingAnnotation) { + + Assert.notNull(method); + this.parameters = new ArrayList(); + + for (int i = 0; i < method.getParameterTypes().length; i++) { + + MethodParameter parameter = new AnnotationNamingMethodParameter(method, i, namingAnnotation); + parameter.initParameterNameDiscovery(DISCOVERER); + parameters.add(parameter); + } + } + + /** + * Returns all {@link MethodParameter}s. + * + * @return + */ + public List getParameters() { + return parameters; + } + + /** + * Returns the {@link MethodParameter} with the given name or {@literal null} if none found. + * + * @param name must not be {@literal null} or empty. + * @return + */ + public MethodParameter getParameter(String name) { + + Assert.hasText(name, "Parameter name must not be null!"); + + for (MethodParameter parameter : parameters) { + if (name.equals(parameter.getParameterName())) { + return parameter; + } + } + + return null; + } + + /** + * Returns all parameters of the given type. + * + * @param type must not be {@literal null}. + * @return + * @since 0.9 + */ + public List getParametersOfType(Class type) { + + Assert.notNull(type, "Type must not be null!"); + List result = new ArrayList(); + + for (MethodParameter parameter : getParameters()) { + if (parameter.getParameterType().equals(type)) { + result.add(parameter); + } + } + + return result; + } + + /** + * Returns all {@link MethodParameter}s annotated with the given annotation type. + * + * @param annotation must not be {@literal null}. + * @return + */ + public List getParametersWith(Class annotation) { + + Assert.notNull(annotation); + List result = new ArrayList(); + + for (MethodParameter parameter : getParameters()) { + if (parameter.hasParameterAnnotation(annotation)) { + result.add(parameter); + } + } + + return result; + } + + /** + * Custom {@link MethodParameter} extension that will favor the name configured in the {@link AnnotationAttribute} if + * set over discovering it. + * + * @author Oliver Gierke + */ + private static class AnnotationNamingMethodParameter extends MethodParameter { + + private final AnnotationAttribute attribute; + private String name; + + /** + * Creates a new {@link AnnotationNamingMethodParameter} for the given {@link Method}'s parameter with the given + * index. + * + * @param method must not be {@literal null}. + * @param parameterIndex + * @param attribute can be {@literal null} + */ + public AnnotationNamingMethodParameter(Method method, int parameterIndex, AnnotationAttribute attribute) { + + super(method, parameterIndex); + this.attribute = attribute; + + } + + /* + * (non-Javadoc) + * @see org.springframework.core.MethodParameter#getParameterName() + */ + @Override + public String getParameterName() { + + if (name != null) { + return name; + } + + if (attribute != null) { + Object foundName = attribute.getValueFrom(this); + if (foundName != null) { + name = foundName.toString(); + return name; + } + } + + name = super.getParameterName(); + return name; + } + } +} diff --git a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java index f2a341758..c46fdf8e6 100644 --- a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java +++ b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java @@ -29,8 +29,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.repository.core.CrudMethods; 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; diff --git a/src/test/java/org/springframework/data/repository/support/AnnotationAttributeUnitTests.java b/src/test/java/org/springframework/data/repository/support/AnnotationAttributeUnitTests.java new file mode 100644 index 000000000..b11c1ef88 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/support/AnnotationAttributeUnitTests.java @@ -0,0 +1,61 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.stereotype.Component; + +/** + * Unit tests for {@link AnnotationAttribute}. + * + * @author Oliver Gierke + */ +public class AnnotationAttributeUnitTests { + + /** + * @see DATACMNS-607 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullAnnotationType() { + new AnnotationAttribute(null); + } + + /** + * @see DATACMNS-607 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullAnnotationTypeForAnnotationAndAttributeName() { + new AnnotationAttribute(null, "name"); + } + + /** + * @see DATACMNS-607 + */ + @Test + public void looksUpAttributeFromAnnotatedElement() { + + AnnotationAttribute attribute = new AnnotationAttribute(Component.class); + assertThat(attribute.getValueFrom(Sample.class), is((Object) "foo")); + } + + @Component("foo") + static class Sample { + + } +} diff --git a/src/test/java/org/springframework/data/repository/support/MethodParametersUnitTests.java b/src/test/java/org/springframework/data/repository/support/MethodParametersUnitTests.java new file mode 100644 index 000000000..3f264a003 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/support/MethodParametersUnitTests.java @@ -0,0 +1,64 @@ +/* + * 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.support; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.MethodParameter; + +/** + * Unit tests for {@link MethodParameters}. + * + * @author Oliver Gierke + */ +public class MethodParametersUnitTests { + + @Test + public void prefersAnnotatedParameterOverDiscovered() throws Exception { + + Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); + MethodParameters parameters = new MethodParameters(method, new AnnotationAttribute(Qualifier.class)); + + assertThat(parameters.getParameter("param"), is(notNullValue())); + assertThat(parameters.getParameter("foo"), is(notNullValue())); + assertThat(parameters.getParameter("another"), is(nullValue())); + } + + /** + * @see #138 + */ + @Test + public void returnsParametersOfAGivenType() throws Exception { + + Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); + MethodParameters methodParameters = new MethodParameters(method); + + List objectParameters = methodParameters.getParametersOfType(Object.class); + assertThat(objectParameters, hasSize(1)); + assertThat(objectParameters.get(0).getParameterIndex(), is(2)); + } + + static class Sample { + + public void method(String param, @Qualifier("foo") String another, Object object) {} + } +}