/*
* Copyright 2013-2016 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.integration.expression;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.util.Assert;
/**
* <p>
* An immutable {@link AbstractMap} implementation that wraps a {@code Map<String, Object>},
* where values must be instances of {@link String} or {@link Expression},
* and evaluates an {@code expression} for the provided {@code key} from the underlying
* {@code original} Map.
* </p>
* <p>
* Any mutating operations ({@link #put(String, Object)}, {@link #remove(Object)} etc.)
* are not allowed on instances of this class. Mutation can be performed on underlying Map
* if it supports it.
* </p>
* <p>
* A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
* via its {@link #from(Map)} method:
* </p>
* <pre class="code">
* {@code
*ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
* .usingCallback(new EvaluationCallback() {
* Object evaluate(Expression expression) {
* // return some expression evaluation
* }
* })
* .build();
*}
* </pre>
* <p>
* Thread-safety depends on the original underlying Map.
* Objects of this class are not serializable.
* </p>
*
* @author Artem Bilan
* @since 3.0
*/
public final class ExpressionEvalMap extends AbstractMap<String, Object> {
public static final EvaluationCallback SIMPLE_CALLBACK = expression -> expression.getValue();
private final Map<String, ?> original;
private final EvaluationCallback evaluationCallback;
private ExpressionEvalMap(Map<String, ?> original, EvaluationCallback evaluationCallback) {
this.original = original;
this.evaluationCallback = evaluationCallback;
}
/**
* Gets the {@code value}({@link Expression}) for the provided {@code key}
* from {@link #original} and returns the result of evaluation using {@link #evaluationCallback}.
*/
@Override
public Object get(Object key) {
Object value = this.original.get(key);
if (value != null) {
Expression expression;
if (value instanceof Expression) {
expression = (Expression) value;
}
else if (value instanceof String) {
expression = new LiteralExpression((String) value);
}
else {
throw new IllegalArgumentException("Values must be "
+ "'java.lang.String' or 'org.springframework.expression.Expression'; the value type for key "
+ key + " is : " + value.getClass());
}
return this.evaluationCallback.evaluate(expression);
}
return null;
}
@Override
public Collection<Object> values() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
return this.original.containsKey(key);
}
@Override
public Set<String> keySet() {
return this.original.keySet();
}
@Override
public boolean isEmpty() {
return this.original.isEmpty();
}
@Override
public int size() {
return this.original.size();
}
@Override
public boolean equals(Object o) {
return this.original.equals(o);
}
@Override
public int hashCode() {
return this.original.hashCode();
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public Object put(String key, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends String, ?> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return this.original.toString();
}
public static ExpressionEvalMapBuilder from(Map<String, ?> expressions) {
Assert.notNull(expressions, "'expressions' must not be null.");
return new ExpressionEvalMapBuilder(expressions);
}
/**
* Implementations of this interface can be provided to build 'on demand {@link #get(Object)} logic'
* for {@link ExpressionEvalMap}.
*/
@FunctionalInterface
public interface EvaluationCallback {
Object evaluate(Expression expression);
}
/**
* The {@link EvaluationCallback} implementation which evaluates an expression using
* the provided {@code context}, {@code root} and {@code returnType} variables.
*/
public static class ComponentsEvaluationCallback implements EvaluationCallback {
private final EvaluationContext context;
private final Object root;
private final Class<?> returnType;
public ComponentsEvaluationCallback(EvaluationContext context, Object root, Class<?> returnType) {
this.context = context;
this.root = root;
this.returnType = returnType;
}
@Override
public Object evaluate(Expression expression) {
if (this.context != null) {
return expression.getValue(this.context, this.root, this.returnType);
}
return expression.getValue(this.root, this.returnType);
}
}
/**
* The builder class to instantiate {@link ExpressionEvalMap}.
*/
public static final class ExpressionEvalMapBuilder {
private final Map<String, ?> expressions;
private EvaluationCallback evaluationCallback;
private EvaluationContext context;
private Object root;
private Class<?> returnType;
private final ExpressionEvalMapComponentsBuilder evalMapComponentsBuilder =
new ExpressionEvalMapComponentsBuilderImpl();
private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
private ExpressionEvalMapBuilder(Map<String, ?> expressions) {
this.expressions = expressions;
}
public ExpressionEvalMapFinalBuilder usingCallback(EvaluationCallback callback) {
this.evaluationCallback = callback;
return this.finalBuilder;
}
public ExpressionEvalMapFinalBuilder usingSimpleCallback() {
return this.usingCallback(SIMPLE_CALLBACK);
}
public ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context) {
this.context = context;
return this.evalMapComponentsBuilder;
}
public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
this.root = root;
return this.evalMapComponentsBuilder;
}
public ExpressionEvalMapComponentsBuilder withReturnType(Class<?> returnType) {
this.returnType = returnType;
return this.evalMapComponentsBuilder;
}
private class ExpressionEvalMapFinalBuilderImpl implements ExpressionEvalMapFinalBuilder {
@Override
public ExpressionEvalMap build() {
if (ExpressionEvalMapBuilder.this.evaluationCallback != null) {
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions,
ExpressionEvalMapBuilder.this.evaluationCallback);
}
ComponentsEvaluationCallback evaluationCallback =
new ComponentsEvaluationCallback(ExpressionEvalMapBuilder.this.context,
ExpressionEvalMapBuilder.this.root, ExpressionEvalMapBuilder.this.returnType);
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions, evaluationCallback);
}
}
private class ExpressionEvalMapComponentsBuilderImpl extends ExpressionEvalMapFinalBuilderImpl
implements ExpressionEvalMapComponentsBuilder {
@Override
public ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context) {
return ExpressionEvalMapBuilder.this.usingEvaluationContext(context);
}
@Override
public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
return ExpressionEvalMapBuilder.this.withRoot(root);
}
@Override
public ExpressionEvalMapComponentsBuilder withReturnType(Class<?> returnType) {
return ExpressionEvalMapBuilder.this.withReturnType(returnType);
}
}
}
@FunctionalInterface
public interface ExpressionEvalMapFinalBuilder {
ExpressionEvalMap build();
}
public interface ExpressionEvalMapComponentsBuilder extends ExpressionEvalMapFinalBuilder {
ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context);
ExpressionEvalMapComponentsBuilder withRoot(Object root);
ExpressionEvalMapComponentsBuilder withReturnType(Class<?> returnType);
}
}