/*
* Copyright 2014-2017 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;
import static org.assertj.core.api.Assertions.*;
import lombok.RequiredArgsConstructor;
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;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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;
/**
* Unit tests {@link ExtensionAwareEvaluationContextProvider}.
*
* @author Oliver Gierke
* @author Thomas Darimont.
*/
public class ExtensionAwareEvaluationContextProviderUnitTests {
Method method;
EvaluationContextProvider provider;
@Before
public void setUp() throws Exception {
this.method = SampleRepo.class.getMethod("findByFirstname", String.class);
this.provider = new ExtensionAwareEvaluationContextProvider(Collections.emptyList());
}
@Test // DATACMNS-533
public void usesPropertyDefinedByExtension() {
this.provider = new ExtensionAwareEvaluationContextProvider(
Collections.singletonList(new DummyExtension("_first", "first")));
assertThat(evaluateExpression("key")).isEqualTo("first");
}
@Test // DATACMNS-533
public void secondExtensionOverridesFirstOne() {
List<EvaluationContextExtension> extensions = new ArrayList<>();
extensions.add(new DummyExtension("_first", "first"));
extensions.add(new DummyExtension("_second", "second"));
this.provider = new ExtensionAwareEvaluationContextProvider(extensions);
assertThat(evaluateExpression("key")).isEqualTo("second");
}
@Test // DATACMNS-533
public void allowsDirectAccessToExtensionViaKey() {
List<EvaluationContextExtension> extensions = new ArrayList<>();
extensions.add(new DummyExtension("_first", "first"));
extensions.add(new DummyExtension("_second", "second"));
this.provider = new ExtensionAwareEvaluationContextProvider(extensions);
assertThat(evaluateExpression("_first.key")).isEqualTo("first");
}
@Test // DATACMNS-533
public void exposesParametersAsVariables() {
assertThat(evaluateExpression("#firstname")).isEqualTo("parameterValue");
}
@Test // DATACMNS-533
public void exposesMethodDefinedByExtension() {
this.provider = new ExtensionAwareEvaluationContextProvider(
Collections.singletonList(new DummyExtension("_first", "first")));
assertThat(evaluateExpression("aliasedMethod()")).isEqualTo("methodResult");
assertThat(evaluateExpression("extensionMethod()")).isEqualTo("methodResult");
assertThat(evaluateExpression("_first.extensionMethod()")).isEqualTo("methodResult");
assertThat(evaluateExpression("_first.aliasedMethod()")).isEqualTo("methodResult");
}
@Test // DATACMNS-533
public void exposesPropertiesDefinedByExtension() {
this.provider = new ExtensionAwareEvaluationContextProvider(
Collections.singletonList(new DummyExtension("_first", "first")));
assertThat(evaluateExpression("DUMMY_KEY")).isEqualTo("dummy");
assertThat(evaluateExpression("_first.DUMMY_KEY")).isEqualTo("dummy");
}
@Test // DATACMNS-533
public void exposesPageableParameter() throws Exception {
this.method = SampleRepo.class.getMethod("findByFirstname", String.class, Pageable.class);
PageRequest pageable = PageRequest.of(2, 3, Sort.by(Direction.DESC, "lastname"));
assertThat(evaluateExpression("#pageable.offset", new Object[] { "test", pageable })).isEqualTo(6L);
assertThat(evaluateExpression("#pageable.pageSize", new Object[] { "test", pageable })).isEqualTo(3);
assertThat(evaluateExpression("#pageable.sort.toString()", new Object[] { "test", pageable }))
.isEqualTo("lastname: DESC");
}
@Test // DATACMNS-533
public void exposesSortParameter() throws Exception {
this.method = SampleRepo.class.getMethod("findByFirstname", String.class, Sort.class);
Sort sort = Sort.by(Direction.DESC, "lastname");
assertThat(evaluateExpression("#sort.toString()", new Object[] { "test", sort })).isEqualTo("lastname: DESC");
}
@Test // DATACMNS-533
public void exposesSpecialParameterEvenIfItsNull() throws Exception {
this.method = SampleRepo.class.getMethod("findByFirstname", String.class, Sort.class);
assertThat(evaluateExpression("#sort?.toString()", new Object[] { "test", null })).isNull();
}
@Test // DATACMNS-533
public void shouldBeAbleToAccessCustomRootObjectPropertiesAndFunctions() {
this.provider = new ExtensionAwareEvaluationContextProvider(Collections.singletonList( //
new DummyExtension("_first", "first") {
@Override
public CustomExtensionRootObject1 getRootObject() {
return new CustomExtensionRootObject1();
}
}));
assertThat(evaluateExpression("rootObjectInstanceField1")).isEqualTo("rootObjectInstanceF1");
assertThat(evaluateExpression("rootObjectInstanceMethod1()")).isEqualTo(true);
assertThat(evaluateExpression("getStringProperty()")).isEqualTo("stringProperty");
assertThat(evaluateExpression("stringProperty")).isEqualTo("stringProperty");
assertThat(evaluateExpression("_first.rootObjectInstanceField1")).isEqualTo("rootObjectInstanceF1");
assertThat(evaluateExpression("_first.rootObjectInstanceMethod1()")).isEqualTo(true);
assertThat(evaluateExpression("_first.getStringProperty()")).isEqualTo("stringProperty");
assertThat(evaluateExpression("_first.stringProperty")).isEqualTo("stringProperty");
}
@Test // DATACMNS-533
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")).isEqualTo("rootObjectInstanceF1");
assertThat(evaluateExpression("rootObjectInstanceMethod1()")).isEqualTo(true);
assertThat(evaluateExpression("rootObjectInstanceField2")).isEqualTo(42);
assertThat(evaluateExpression("rootObjectInstanceMethod2()")).isEqualTo("rootObjectInstanceMethod2");
assertThat(evaluateExpression("[0]")).isEqualTo("parameterValue");
}
@Test // DATACMNS-533
public void shouldBeAbleToAccessCustomRootObjectPropertiesAndFunctionsFromDynamicTargetSource() {
final AtomicInteger counter = new AtomicInteger();
this.provider = new ExtensionAwareEvaluationContextProvider(Collections.singletonList( //
new DummyExtension("_first", "first") {
@Override
public CustomExtensionRootObject1 getRootObject() {
counter.incrementAndGet();
return new CustomExtensionRootObject1();
}
}) //
);
// inc counter / property access
assertThat(evaluateExpression("rootObjectInstanceField1")).isEqualTo("rootObjectInstanceF1");
// inc counter / function invocation
assertThat(evaluateExpression("rootObjectInstanceMethod1()")).isEqualTo(true);
assertThat(counter.get()).isEqualTo(2);
}
@RequiredArgsConstructor
public static class DummyExtension extends EvaluationContextExtensionSupport {
public static String DUMMY_KEY = "dummy";
private final String key, value;
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.spi.EvaluationContextExtension#getExtensionId()
*/
@Override
public String getExtensionId() {
return key;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.EvaluationContextExtensionAdapter#getProperties()
*/
@Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>(super.getProperties());
properties.put("key", value);
return properties;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport#getFunctions()
*/
@Override
public Map<String, Function> getFunctions() {
Map<String, Function> functions = new HashMap<>(super.getFunctions());
try {
functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
return functions;
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
public static String extensionMethod() {
return "methodResult";
}
}
private Object evaluateExpression(String expression) {
return evaluateExpression(expression, new Object[] { "parameterValue" });
}
private Object evaluateExpression(String expression, Object[] args) {
DefaultParameters parameters = new DefaultParameters(method);
EvaluationContext evaluationContext = provider.getEvaluationContext(parameters, args);
return new SpelExpressionParser().parseExpression(expression).getValue(evaluationContext);
}
interface SampleRepo {
List<Object> findByFirstname(@Param("firstname") String firstname);
List<Object> findByFirstname(@Param("firstname") String firstname, Pageable pageable);
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";
}
}
}