package org.ovirt.engine.core.dal.dbbroker;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
@Singleton
public class SimpleJdbcCallsHandler {
private static final String RETURN_VALUE_PARAMETER = "RETURN_VALUE";
private final ConcurrentMap<String, SimpleJdbcCall> callsMap = new ConcurrentHashMap<>();
private final DbEngineDialect dialect;
private final JdbcTemplate jdbcTemplate;
@Inject
public SimpleJdbcCallsHandler(
DbEngineDialect dialect,
JdbcTemplate jdbcTemplate) {
Objects.requireNonNull(dialect, "DbEngineDialect cannot be null");
Objects.requireNonNull(jdbcTemplate, "jdbcTemplate cannot be null");
this.dialect = dialect;
this.jdbcTemplate = jdbcTemplate;
}
private interface CallCreator {
SimpleJdbcCall createCall();
}
/**
* Runs a set of stored procedure calls in a batch. Only useful for update procedures that return no value
*
* @param procName
* the procedure name
* @param executions
* a list of parameter maps
*/
public void executeStoredProcAsBatch(final String procName,
final List<MapSqlParameterSource> executions)
throws DataAccessException {
jdbcTemplate.execute(new BatchProcedureExecutionConnectionCallback(this, procName, executions));
}
/**
* Runs a set of stored procedure calls in a batch. Only useful for update procedures that return no value
* @param procedureName the procedure name
* @param paramValues list of objects to be converted to {@link MapSqlParameterSource}
* @param mapper mapper to use to convert the param value objects to {@link MapSqlParameterSource}
*/
public <T> void executeStoredProcAsBatch(final String procedureName,
Collection<T> paramValues,
MapSqlParameterMapper<T> mapper) {
List<MapSqlParameterSource> sqlParams = paramValues.stream().map(mapper::map).collect(Collectors.toList());
executeStoredProcAsBatch(procedureName, sqlParams);
}
public Map<String, Object> executeModification(final String procedureName, final MapSqlParameterSource paramSource) {
return executeImpl(procedureName, paramSource, createCallForModification(procedureName));
}
public int executeModificationReturnResult(final String procedureName, final MapSqlParameterSource paramSource) {
Integer procedureResult = null;
Map<String, Object> result = executeImpl(procedureName, paramSource, createCallForModification(procedureName));
if (!result.isEmpty()) {
List<?> resultArray = (List<?>) result.values().iterator().next();
if (resultArray != null && !resultArray.isEmpty()) {
Map<?, ?> resultMap = (Map<?, ?>) resultArray.get(0);
if (!resultMap.isEmpty()) {
procedureResult = (Integer) resultMap.values().iterator().next();
}
}
}
return (procedureResult != null) ? procedureResult : 0;
}
public <T> T executeRead(final String procedureName,
final RowMapper<T> mapper,
final MapSqlParameterSource parameterSource) {
List<T> results = executeReadList(procedureName, mapper, parameterSource);
return results.isEmpty() ? null : results.get(0);
}
@SuppressWarnings("unchecked")
public <T> List<T> executeReadList(final String procedureName,
final RowMapper<T> mapper,
final MapSqlParameterSource parameterSource) {
Map<String, Object> resultsMap = executeImpl(procedureName, parameterSource, createCallForRead(procedureName, mapper, parameterSource), mapper);
return (List<T>) resultsMap.get(RETURN_VALUE_PARAMETER);
}
private CallCreator createCallForRead(final String procedureName,
final RowMapper<?> mapper,
final MapSqlParameterSource parameterSource) {
return () -> {
SimpleJdbcCall call =
(SimpleJdbcCall) dialect.createJdbcCallForQuery(jdbcTemplate).withProcedureName(procedureName);
call.returningResultSet(RETURN_VALUE_PARAMETER, mapper);
// Pass mapper information (only parameter names) in order to supply all the needed
// metadata information for compilation.
call.getInParameterNames().addAll(
SqlParameterSourceUtils.extractCaseInsensitiveParameterNames(parameterSource).keySet());
return call;
};
}
CallCreator createCallForModification(final String procedureName) {
return () -> new SimpleJdbcCall(jdbcTemplate).withProcedureName(procedureName);
}
private Map<String, Object> executeImpl(String procedureName,
MapSqlParameterSource paramsSource, CallCreator callCreatorr) {
return executeImpl(procedureName, paramsSource, callCreatorr, null);
}
private <T> Map<String, Object> executeImpl(String procedureName,
MapSqlParameterSource paramsSource, CallCreator callCreator, RowMapper<T> mapper) {
SimpleJdbcCall call = getCall(procedureName, callCreator, mapper);
return call.execute(paramsSource);
}
/**
* Creates a call object and compiles its metadata, if not found in the map. Bare in mind the existence check if not
* atomic, so at worst case few more redundant information schema calls * will be made. The compilation is done at
* the scope of the method in order to avoid concurrency issues upon first time usage of the stored procedure.
*
* @param procedureName
* stored procedure name
* @param callCreator
* calls creator object
* @return simple JDBC call object
*/
protected SimpleJdbcCall getCall(String procedureName, CallCreator callCreator) {
return getCall(procedureName, callCreator, null);
}
protected <T> SimpleJdbcCall getCall(String procedureName, CallCreator callCreator, RowMapper<T> mapper) {
SimpleJdbcCall call = callsMap.get(procedureName);
if (call == null) {
call = callCreator.createCall();
call.compile();
callsMap.putIfAbsent(procedureName, call);
} else if (mapper != null) {
call.returningResultSet(RETURN_VALUE_PARAMETER, mapper);
}
return call;
}
public DbEngineDialect getDialect() {
return dialect;
}
}