Browse Source

DATACMNS-533 - Added test cases for root object integration in SpEL extension API.

Changed SPI to expose a Function type rather than plain methods as well as an arbitrary root object.

Related pull request: spring-projects/spring-data-jpa#101
Related ticket: DATAJPA-564
pull/82/merge
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
1e489bbd6d
  1. 12
      src/main/java/org/springframework/data/repository/query/spi/EvaluationContextExtension.java
  2. 118
      src/main/java/org/springframework/data/repository/query/spi/Function.java
  3. 114
      src/test/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProviderUnitTests.java

12
src/main/java/org/springframework/data/repository/query/spi/EvaluationContextExtension.java

@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
*/
package org.springframework.data.repository.query.spi;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider;
@ -51,5 +50,14 @@ public interface EvaluationContextExtension { @@ -51,5 +50,14 @@ public interface EvaluationContextExtension {
*
* @return the functions
*/
Map<String, Method> getFunctions();
Map<String, Function> getFunctions();
/**
* Returns the root object to be exposed by the extension. It's strongly recommended to declare the most concrete type
* possible as return type of the implementation method. This will allow us to obtain the necessary metadata once and
* not for every evaluation.
*
* @return
*/
Object getRootObject();
}

118
src/main/java/org/springframework/data/repository/query/spi/Function.java

@ -0,0 +1,118 @@ @@ -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.query.spi;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.TypeUtils;
/**
* Value object to represent a function. Can either be backed by a static {@link Method} invocation (see
* {@link #Function(Method)}) or a method invocation on an instance (see {@link #Function(Method, Object)}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @since 1.9
*/
public class Function {
private final Method method;
private final Object target;
/**
* Creates a new {@link Function} to statically invoke the given {@link Method}.
*
* @param method
*/
public Function(Method method) {
this(method, null);
Assert.isTrue(Modifier.isStatic(method.getModifiers()), "Method must be static!");
}
/**
* Creates a new {@link Function} for the given method on the given target instance.
*
* @param method must not be {@literal null}.
* @param target can be {@literal null}, if so, the method
*/
public Function(Method method, Object target) {
Assert.notNull(method, "Method must not be null!");
Assert.isTrue(target != null || Modifier.isStatic(method.getModifiers()),
"Method must either be static or a non-static one with a target object!");
this.method = method;
this.target = target;
}
/**
* Invokes the function with the given arguments.
*
* @param arguments must not be {@literal null}.
* @return
* @throws Exception
*/
public Object invoke(Object[] arguments) throws Exception {
return method.invoke(target, arguments);
}
/**
* Returns the name of the function.
*
* @return
*/
public String getName() {
return method.getName();
}
/**
* Returns the type declaring the {@link Function}.
*
* @return
*/
public Class<?> getDeclaringClass() {
return method.getDeclaringClass();
}
/**
* Returns {@literal true} if the function can be called with the given {@code argumentTypes}.
*
* @param argumentTypes
* @return
*/
public boolean supports(List<TypeDescriptor> argumentTypes) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != argumentTypes.size()) {
return false;
}
for (int i = 0; i < parameterTypes.length; i++) {
if (!TypeUtils.isAssignable(parameterTypes[i], argumentTypes.get(i).getType())) {
return false;
}
}
return true;
}
}

114
src/test/java/org/springframework/data/repository/query/ExtensionAwareEvaluationContextProviderUnitTests.java

@ -20,10 +20,12 @@ import static org.junit.Assert.*; @@ -20,10 +20,12 @@ import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
@ -33,6 +35,7 @@ import org.springframework.data.domain.Sort; @@ -33,6 +35,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
import org.springframework.data.repository.query.spi.Function;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@ -169,6 +172,88 @@ public class ExtensionAwareEvaluationContextProviderUnitTests { @@ -169,6 +172,88 @@ public class ExtensionAwareEvaluationContextProviderUnitTests {
assertThat(evaluateExpression("#sort?.toString()", new Object[] { "test", null }), is(nullValue()));
}
/**
* @see DATACMNS-533
*/
@Test
public void shouldBeAbleToAccessCustomRootObjectPropertiesAndFunctions() {
this.provider = new ExtensionAwareEvaluationContextProvider(Collections.singletonList( //
new DummyExtension("_first", "first") {
@Override
public CustomExtensionRootObject1 getRootObject() {
return new CustomExtensionRootObject1();
}
}));
assertThat(evaluateExpression("rootObjectInstanceField1"), is((Object) "rootObjectInstanceF1"));
assertThat(evaluateExpression("rootObjectInstanceMethod1()"), is((Object) true));
assertThat(evaluateExpression("getStringProperty()"), is((Object) "stringProperty"));
assertThat(evaluateExpression("stringProperty"), is((Object) "stringProperty"));
assertThat(evaluateExpression("_first.rootObjectInstanceField1"), is((Object) "rootObjectInstanceF1"));
assertThat(evaluateExpression("_first.rootObjectInstanceMethod1()"), is((Object) true));
assertThat(evaluateExpression("_first.getStringProperty()"), is((Object) "stringProperty"));
assertThat(evaluateExpression("_first.stringProperty"), is((Object) "stringProperty"));
}
/**
* @see DATACMNS-533
*/
@Test
public void shouldBeAbleToAccessCustomRootObjectPropertiesAndFunctionsInMultipleExtensions() {
this.provider = new ExtensionAwareEvaluationContextProvider(Arrays.asList( //
new DummyExtension("_first", "first") {
@Override
public CustomExtensionRootObject1 getRootObject() {
return new CustomExtensionRootObject1();
}
}, //
new DummyExtension("_second", "second") {
@Override
public CustomExtensionRootObject2 getRootObject() {
return new CustomExtensionRootObject2();
}
}));
assertThat(evaluateExpression("rootObjectInstanceField1"), is((Object) "rootObjectInstanceF1"));
assertThat(evaluateExpression("rootObjectInstanceMethod1()"), is((Object) true));
assertThat(evaluateExpression("rootObjectInstanceField2"), is((Object) 42));
assertThat(evaluateExpression("rootObjectInstanceMethod2()"), is((Object) "rootObjectInstanceMethod2"));
assertThat(evaluateExpression("[0]"), is((Object) "parameterValue"));
}
/**
* @see DATACMNS-533
*/
@Test
public void shouldBeAbleToAccessCustomRootObjectPropertiesAndFunctionsFromDynamicTargetSource() {
final AtomicInteger counter = new AtomicInteger();
this.provider = new ExtensionAwareEvaluationContextProvider(Arrays.asList( //
new DummyExtension("_first", "first") {
@Override
public CustomExtensionRootObject1 getRootObject() {
counter.incrementAndGet();
return new CustomExtensionRootObject1();
}
}) //
);
// inc counter / property access
assertThat(evaluateExpression("rootObjectInstanceField1"), is((Object) "rootObjectInstanceF1"));
// inc counter / function invocation
assertThat(evaluateExpression("rootObjectInstanceMethod1()"), is((Object) true));
assertThat(counter.get(), is(2));
}
public static class DummyExtension extends EvaluationContextExtensionSupport {
public static String DUMMY_KEY = "dummy";
@ -177,7 +262,6 @@ public class ExtensionAwareEvaluationContextProviderUnitTests { @@ -177,7 +262,6 @@ public class ExtensionAwareEvaluationContextProviderUnitTests {
private final String value;
public DummyExtension(String key, String value) {
this.key = key;
this.value = value;
}
@ -210,12 +294,12 @@ public class ExtensionAwareEvaluationContextProviderUnitTests { @@ -210,12 +294,12 @@ public class ExtensionAwareEvaluationContextProviderUnitTests {
* @see org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport#getFunctions()
*/
@Override
public Map<String, Method> getFunctions() {
public Map<String, Function> getFunctions() {
Map<String, Method> functions = new HashMap<String, Method>(super.getFunctions());
Map<String, Function> functions = new HashMap<String, Function>(super.getFunctions());
try {
functions.put("aliasedMethod", getClass().getMethod("extensionMethod"));
functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
return functions;
} catch (Exception o_O) {
throw new RuntimeException(o_O);
@ -246,4 +330,26 @@ public class ExtensionAwareEvaluationContextProviderUnitTests { @@ -246,4 +330,26 @@ public class ExtensionAwareEvaluationContextProviderUnitTests {
List<Object> findByFirstname(@Param("firstname") String firstname, Sort sort);
}
public static class CustomExtensionRootObject1 {
public String rootObjectInstanceField1 = "rootObjectInstanceF1";
public boolean rootObjectInstanceMethod1() {
return true;
}
public String getStringProperty() {
return "stringProperty";
}
}
public static class CustomExtensionRootObject2 {
public Integer rootObjectInstanceField2 = 42;
public String rootObjectInstanceMethod2() {
return "rootObjectInstanceMethod2";
}
}
}

Loading…
Cancel
Save