de.zalando.sprocwrapper.proxy.StoredProcedure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zalando-sprocwrapper Show documentation
Show all versions of zalando-sprocwrapper Show documentation
Library to make PostgreSQL stored procedures available through simple Java "*SProcService" interfaces
including automatic object serialization and deserialization (using typemapper and
convention-over-configuration). Supports sharding, advisory locking, statement timeouts and PostgreSQL types
such as enums and hstore.
package de.zalando.sprocwrapper.proxy;
import java.lang.reflect.ParameterizedType;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.RowMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import de.zalando.sprocwrapper.SProcCall.AdvisoryLock;
import de.zalando.sprocwrapper.SProcService.WriteTransaction;
import de.zalando.sprocwrapper.dsprovider.DataSourceProvider;
import de.zalando.sprocwrapper.dsprovider.SameConnectionDatasource;
import de.zalando.sprocwrapper.globalvaluetransformer.GlobalValueTransformerLoader;
import de.zalando.sprocwrapper.proxy.executors.Executor;
import de.zalando.sprocwrapper.proxy.executors.ExecutorWrapper;
import de.zalando.sprocwrapper.proxy.executors.GlobalTransformerExecutorWrapper;
import de.zalando.sprocwrapper.proxy.executors.MultiRowSimpleTypeExecutor;
import de.zalando.sprocwrapper.proxy.executors.MultiRowTypeMapperExecutor;
import de.zalando.sprocwrapper.proxy.executors.SingleRowCustomMapperExecutor;
import de.zalando.sprocwrapper.proxy.executors.SingleRowSimpleTypeExecutor;
import de.zalando.sprocwrapper.proxy.executors.SingleRowTypeMapperExecutor;
import de.zalando.sprocwrapper.proxy.executors.ValidationExecutorWrapper;
import de.zalando.sprocwrapper.sharding.ShardedDataAccessException;
import de.zalando.sprocwrapper.sharding.ShardedObject;
import de.zalando.sprocwrapper.sharding.VirtualShardKeyStrategy;
import de.zalando.typemapper.core.ValueTransformer;
/**
* @author jmussler
*/
class StoredProcedure {
private static final int TRUNCATE_DEBUG_PARAMS_MAX_LENGTH = 1024;
private static final String TRUNCATE_DEBUG_PARAMS_ELLIPSIS = " ...";
private static final Logger LOG = LoggerFactory.getLogger(StoredProcedure.class);
private final List params = new ArrayList();
private final String name;
private String query = null;
private Class> returnType = null;
// whether the result type is a collection (List)
private boolean collectionResult = false;
private final boolean runOnAllShards;
private final boolean searchShards;
private boolean autoPartition;
private final boolean parallel;
private final boolean readOnly;
private final WriteTransaction writeTransaction;
private Executor executor = null;
private VirtualShardKeyStrategy shardStrategy;
private List shardKeyParameters = null;
private final RowMapper> resultMapper;
private int[] types = null;
private static final Executor MULTI_ROW_SIMPLE_TYPE_EXECUTOR = new MultiRowSimpleTypeExecutor();
private static final Executor MULTI_ROW_TYPE_MAPPER_EXECUTOR = new MultiRowTypeMapperExecutor();
private static final Executor SINGLE_ROW_SIMPLE_TYPE_EXECUTOR = new SingleRowSimpleTypeExecutor();
private static final Executor SINGLE_ROW_TYPE_MAPPER_EXECUTOR = new SingleRowTypeMapperExecutor();
private final long timeout;
private final AdvisoryLock adivsoryLock;
public StoredProcedure(final String name, final java.lang.reflect.Type genericType,
final VirtualShardKeyStrategy sStrategy, final boolean runOnAllShards, final boolean searchShards,
final boolean parallel, final RowMapper> resultMapper, final long timeout,
final AdvisoryLock advisoryLock, final boolean useValidation, final boolean readOnly,
final WriteTransaction writeTransaction) throws InstantiationException, IllegalAccessException {
this.name = name;
this.runOnAllShards = runOnAllShards;
this.searchShards = searchShards;
this.parallel = parallel;
this.readOnly = readOnly;
this.resultMapper = resultMapper;
this.writeTransaction = writeTransaction;
this.adivsoryLock = advisoryLock;
this.timeout = timeout;
shardStrategy = sStrategy;
ValueTransformer, ?> valueTransformerForClass = null;
if (genericType instanceof ParameterizedType) {
final ParameterizedType pType = (ParameterizedType) genericType;
if (java.util.List.class.isAssignableFrom((Class>) pType.getRawType())
&& pType.getActualTypeArguments().length > 0) {
returnType = (Class>) pType.getActualTypeArguments()[0];
// check if we have a value transformer (and initialize the registry):
valueTransformerForClass = GlobalValueTransformerLoader.getValueTransformerForClass(returnType);
if (valueTransformerForClass != null
|| SingleRowSimpleTypeExecutor.SIMPLE_TYPES.containsKey(returnType)) {
executor = MULTI_ROW_SIMPLE_TYPE_EXECUTOR;
} else {
executor = MULTI_ROW_TYPE_MAPPER_EXECUTOR;
}
collectionResult = true;
} else {
executor = SINGLE_ROW_TYPE_MAPPER_EXECUTOR;
returnType = (Class>) pType.getRawType();
}
} else {
returnType = (Class>) genericType;
// check if we have a value transformer (and initialize the registry):
valueTransformerForClass = GlobalValueTransformerLoader.getValueTransformerForClass(returnType);
if (valueTransformerForClass != null || SingleRowSimpleTypeExecutor.SIMPLE_TYPES.containsKey(returnType)) {
executor = SINGLE_ROW_SIMPLE_TYPE_EXECUTOR;
} else {
if (resultMapper != null) {
executor = new SingleRowCustomMapperExecutor(resultMapper);
} else {
executor = SINGLE_ROW_TYPE_MAPPER_EXECUTOR;
}
}
}
if (this.timeout > 0 || (this.adivsoryLock != null && !(this.adivsoryLock.equals(AdvisoryLock.NoLock.LOCK)))) {
// Wrapper provides locking and changing of session settings functionality
this.executor = new ExecutorWrapper(executor, this.timeout, this.adivsoryLock);
}
if (useValidation) {
this.executor = new ValidationExecutorWrapper(this.executor);
}
if (valueTransformerForClass != null) {
// we need to transform the return value by the global value transformer.
// add the transformation to the as a transformerExecutor
this.executor = new GlobalTransformerExecutorWrapper(this.executor);
}
}
public void addParam(final StoredProcedureParameter p) {
params.add(p);
}
public void setVirtualShardKeyStrategy(final VirtualShardKeyStrategy s) {
shardStrategy = s;
}
public void addShardKeyParameter(final int jp, final Class> clazz) {
if (shardKeyParameters == null) {
shardKeyParameters = new ArrayList(1);
}
if (List.class.isAssignableFrom(clazz)) {
autoPartition = true;
}
shardKeyParameters.add(new ShardKeyParameter(jp));
}
public String getName() {
return name;
}
public Object[] getParams(final Object[] origParams, final Connection connection) {
final Object[] ps = new Object[params.size()];
int i = 0;
for (final StoredProcedureParameter p : params) {
try {
ps[i] = p.mapParam(origParams[p.getJavaPos()], connection);
} catch (final Exception e) {
final String errorMessage = "Could not map input parameter for stored procedure " + name + " of type "
+ p.getType() + " at position " + p.getJavaPos() + ": "
+ (p.isSensitive() ? "" : origParams[p.getJavaPos()]);
LOG.error(errorMessage, e);
throw new IllegalArgumentException(errorMessage, e);
}
i++;
}
return ps;
}
public int[] getTypes() {
if (types == null) {
types = new int[params.size()];
int i = 0;
for (final StoredProcedureParameter p : params) {
types[i++] = p.getType();
}
}
return types;
}
public int getShardId(final Object[] objs) {
if (shardKeyParameters == null) {
return shardStrategy.getShardId(null);
}
final Object[] keys = new Object[shardKeyParameters.size()];
int i = 0;
Object obj;
for (final ShardKeyParameter p : shardKeyParameters) {
obj = objs[p.javaPos];
if (obj instanceof ShardedObject) {
obj = ((ShardedObject) obj).getShardKey();
}
keys[i] = obj;
i++;
}
return shardStrategy.getShardId(keys);
}
public String getSqlParameterList() {
String s = "";
boolean first = true;
for (int i = 1; i <= params.size(); ++i) {
if (!first) {
s += ",";
}
first = false;
s += "?";
}
return s;
}
public void setQuery(final String sql) {
query = sql;
}
public String getQuery() {
if (query == null) {
query = "SELECT * FROM " + name + " ( " + getSqlParameterList() + " )";
}
return query;
}
/**
* build execution string like create_or_update_multiple_objects({"(a,b)","(c,d)" }).
*
* @param args
*
* @return
*/
private String getDebugLog(final Object[] args) {
final StringBuilder sb = new StringBuilder(name);
sb.append('(');
int i = 0;
for (final Object param : args) {
if (i > 0) {
sb.append(',');
}
if (param == null) {
sb.append("NULL");
} else if (params.get(i).isSensitive()) {
sb.append("");
} else {
sb.append(param);
}
i++;
if (sb.length() > TRUNCATE_DEBUG_PARAMS_MAX_LENGTH) {
break;
}
}
if (sb.length() > TRUNCATE_DEBUG_PARAMS_MAX_LENGTH) {
// Truncate params for debug output
return sb.substring(0, TRUNCATE_DEBUG_PARAMS_MAX_LENGTH) + TRUNCATE_DEBUG_PARAMS_ELLIPSIS + ")";
} else {
sb.append(')');
return sb.toString();
}
}
/**
* split arguments by shard.
*
* @param dataSourceProvider
* @param args the original argument list
*
* @return map of virtual shard ID to argument list (TreeMap with ordered keys: sorted by shard ID)
*/
@SuppressWarnings("unchecked")
private Map partitionArguments(final DataSourceProvider dataSourceProvider,
final Object[] args) {
// use TreeMap here to maintain ordering by shard ID
final Map argumentsByShardId = Maps.newTreeMap();
// we need to partition by datasource instead of virtual shard ID (different virtual shard IDs are mapped to
// the same datasource e.g. by VirtualShardMd5Strategy)
final Map shardIdByDataSource = Maps.newHashMap();
// TODO: currently only implemented for single shardKey argument as first argument!
final List