/*
* Copyright 2002-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.jdbc;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.util.AbstractExpressionEvaluator;
import org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
/**
* An implementation of {@link SqlParameterSourceFactory} which creates an {@link SqlParameterSource} that evaluates
* Spring EL expressions. In addition the user can supply static parameters that always take precedence.
*
* @author Dave Syer
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpressionEvaluator implements
SqlParameterSourceFactory {
private final static Log logger = LogFactory.getLog(ExpressionEvaluatingSqlParameterSourceFactory.class);
private static final ExpressionParser PARSER = new SpelExpressionParser();
private static final Object ERROR = new Object();
private volatile Map<String, ?> staticParameters;
/**
* The {@link Map} of parameters with expressions.
* {@code key} - parameter name; {@code value} - array of two {@link Expression}s:
* first element - direct {@link Expression}, second - collection projection {@link Expression}.
* Used in case of root object of evaluation is {@link Collection}.
*/
private volatile Map<String, Expression[]> parameterExpressions;
public ExpressionEvaluatingSqlParameterSourceFactory() {
this.staticParameters = Collections.unmodifiableMap(new HashMap<String, Object>());
this.parameterExpressions = new HashMap<String, Expression[]>();
}
/**
* Define some static parameter values. These take precedence over those defined as expressions in the
* {@link #setParameterExpressions(Map) parameterExpressions}, so a parameter in the query will be filled from here
* first, and then from the expressions.
*
* @param staticParameters the static parameters to set
*/
public void setStaticParameters(Map<String, ?> staticParameters) {
this.staticParameters = staticParameters;
}
/**
* Optionally maps parameter names to explicit expressions. The named parameter support in Spring is limited to
* simple parameter names with no special characters, so this feature allows you to specify a simple name in the SQL
* query and then have it translated into an expression at runtime. The target of the expression depends on the
* context: generally in an outbound setting it is a Message, and in an inbound setting it is a result set row (a
* Map or a domain object if a RowMapper has been provided). The {@link #setStaticParameters(Map) static parameters}
* can be referred to in an expression using the variable <code>#staticParameters</code>, for example:
* <p>
* <table>
* <caption>Parameter Expressions Samples</caption>
* <tr>
* <th><b>Key</b></th>
* <th><b>Value (Expression)</b></th>
* <th><b>Example SQL</b></th>
* </tr>
* <tr>
* <td>id</td>
* <td>{@code payload.businessKey}</td>
* <td>{@code select * from items where id=:id}</td>
* </tr>
* <tr>
* <td>date</td>
* <td>{@code headers['timestamp']}</td>
* <td>{@code select * from items where created>:date}</td>
* </tr>
* <tr>
* <td>key</td>
* <td>{@code #staticParameters['foo'].toUpperCase()}</td>
* <td>{@code select * from items where name=:key}</td>
* </tr>
* </table>
* <p>
*
* @param parameterExpressions the parameter expressions to set
*/
public void setParameterExpressions(Map<String, String> parameterExpressions) {
Map<String, Expression[]> paramExpressions = new HashMap<String, Expression[]>(parameterExpressions.size());
for (Map.Entry<String, String> entry : parameterExpressions.entrySet()) {
String key = entry.getKey();
String expression = entry.getValue();
Expression[] expressions = new Expression[] {
PARSER.parseExpression(expression),
PARSER.parseExpression("#root.![" + expression + "]")
};
paramExpressions.put(key, expressions);
}
this.parameterExpressions = paramExpressions;
}
@Override
public SqlParameterSource createParameterSource(final Object input) {
return new ExpressionEvaluatingSqlParameterSource(input, this.staticParameters, this.parameterExpressions, true);
}
/**
* Create an expression evaluating {@link SqlParameterSource} that does not cache it's results. Useful for cases
* where the source is used multiple times, for example in a {@code <int-jdbc:inbound-channel-adapter/>} for the
* {@code select-sql-parameter-source} attribute.
* @param input The root object for the evaluation.
* @return The parameter source.
*/
public SqlParameterSource createParameterSourceNoCache(final Object input) {
return new ExpressionEvaluatingSqlParameterSource(input, this.staticParameters, this.parameterExpressions, false);
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
this.getEvaluationContext().setVariable("staticParameters", this.staticParameters);
}
private final class ExpressionEvaluatingSqlParameterSource extends AbstractSqlParameterSource {
private final Object input;
private final Map<String, Object> values = new HashMap<String, Object>();
private final Map<String, Expression[]> parameterExpressions;
private final boolean cache;
ExpressionEvaluatingSqlParameterSource(Object input, Map<String, ?> staticParameters,
Map<String, Expression[]> parameterExpressions, boolean cache) {
this.input = input;
this.parameterExpressions = parameterExpressions;
this.values.putAll(staticParameters);
this.cache = cache;
}
@Override
public Object getValue(String paramName) throws IllegalArgumentException {
return this.doGetValue(paramName, false);
}
public Object doGetValue(String paramName, boolean calledFromHasValue) throws IllegalArgumentException {
if (this.values.containsKey(paramName)) {
Object cachedByHasValue = this.values.get(paramName);
if (!this.cache) {
this.values.remove(paramName);
}
return cachedByHasValue;
}
if (!this.parameterExpressions.containsKey(paramName)) {
Expression[] expressions = new Expression[] {
PARSER.parseExpression(paramName),
PARSER.parseExpression("#root.![" + paramName + "]")
};
ExpressionEvaluatingSqlParameterSourceFactory.this.parameterExpressions.put(paramName, expressions);
this.parameterExpressions.put(paramName, expressions);
}
Expression expression = null;
if (this.input instanceof Collection<?>) {
expression = this.parameterExpressions.get(paramName)[1];
}
else {
expression = this.parameterExpressions.get(paramName)[0];
}
Object value = evaluateExpression(expression, this.input);
if (this.cache || calledFromHasValue) {
this.values.put(paramName, value);
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved expression " + expression + " to " + value);
}
return value;
}
@Override
public boolean hasValue(String paramName) {
try {
Object value = doGetValue(paramName, true);
if (value == ERROR) {
return false;
}
}
catch (ExpressionException e) {
if (logger.isDebugEnabled()) {
logger.debug("Could not evaluate expression", e);
}
if (this.cache) {
this.values.put(paramName, ERROR);
}
return false;
}
return true;
}
}
}