package de.zalando.sprocwrapper.proxy; import de.zalando.sprocwrapper.SProcCall; import de.zalando.sprocwrapper.SProcParam; import de.zalando.sprocwrapper.SProcService; import de.zalando.sprocwrapper.sharding.ShardKey; import de.zalando.sprocwrapper.sharding.VirtualShardKeyStrategy; import de.zalando.sprocwrapper.util.NameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.RowMapper; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; /** * @author Soroosh Sarabadani */ public class SProcCallHandler { private static final Logger LOG = LoggerFactory.getLogger(SProcCallHandler.class); private static String getSqlNameForMethod(final String methodName) { return NameUtils.camelCaseToUnderscore(methodName); } List<Method> findSProcCallAnnotatedMethods(Class c) { List<Method> foundMethods = new ArrayList<>(); for (Method method : c.getMethods()) { if (method.isAnnotationPresent(SProcCall.class)) { foundMethods.add(method); } } return foundMethods; } private RowMapper getRowMapper(SProcCall scA) { if (scA.resultMapper() != Void.class) { try { return (RowMapper<?>) scA.resultMapper().newInstance(); } catch (final InstantiationException | IllegalAccessException ex) { LOG.error("Result mapper for sproc can not be instantiated", ex); throw new IllegalArgumentException("Result mapper for sproc can not be instantiated"); } } return null; } private boolean isValidationActive(SProcCall scA, SProcServiceAnnotationHandler.HandlerResult handlerResult) { boolean result = handlerResult.isValidationActive(); // overwrite if explicitly set in SprocCall: if (scA.validate() == SProcCall.Validate.YES) { result = true; } else if (scA.validate() == SProcCall.Validate.NO) { result = false; } return result; } public static SProcService.WriteTransaction mapSprocWriteTransactionToServiceWriteTransaction(SProcCall.WriteTransaction scWiWriteTransaction, SProcServiceAnnotationHandler.HandlerResult handlerResult) { SProcService.WriteTransaction serviceWriteTransaction = handlerResult.getWriteTransaction(); if (scWiWriteTransaction == null) { throw new IllegalArgumentException("scWiWriteTransaction cannot be null"); } switch (scWiWriteTransaction) { case NONE: serviceWriteTransaction = SProcService.WriteTransaction.NONE; break; case ONE_PHASE: serviceWriteTransaction = SProcService.WriteTransaction.ONE_PHASE; break; case TWO_PHASE: serviceWriteTransaction = SProcService.WriteTransaction.TWO_PHASE; break; default: if (serviceWriteTransaction == null) { throw new IllegalArgumentException("ServiceWriteTransaction cannot be null"); } break; } return serviceWriteTransaction; } public Map<Method, StoredProcedure> handle(Class c, SProcServiceAnnotationHandler.HandlerResult handlerResult) { if (handlerResult == null) { throw new IllegalArgumentException("handlerResult should not be null"); } if (c == null) { throw new IllegalArgumentException("class should not be null"); } Map<Method, StoredProcedure> result = new HashMap<>(); List<Method> annotatedMethods = this.findSProcCallAnnotatedMethods(c); for (Method method : annotatedMethods) { final SProcCall scA = method.getAnnotation(SProcCall.class); String name = scA.name(); if ("".equals(name)) { name = getSqlNameForMethod(method.getName()); } name = handlerResult.getPrefix() + name; VirtualShardKeyStrategy sprocStrategy = handlerResult.getShardKeyStrategy(); if (scA.shardStrategy() != Void.class) { try { sprocStrategy = (VirtualShardKeyStrategy) scA.shardStrategy().newInstance(); } catch (final InstantiationException | IllegalAccessException ex) { LOG.error("Shard strategy for sproc can not be instantiated", ex); throw new IllegalArgumentException("Shard strategy for sproc can not be instantiated"); } } RowMapper<?> resultMapper = this.getRowMapper(scA); final boolean useValidation = this.isValidationActive(scA, handlerResult); final List<ShardKeyParameter> shardKeyParameters = new ArrayList<>(); final List<StoredProcedureParameter> params = new ArrayList<>(); int pos = 0; for (final Annotation[] as : method.getParameterAnnotations()) { for (final Annotation a : as) { final Class<?> clazz = method.getParameterTypes()[pos]; Type genericType = method.getGenericParameterTypes()[pos]; if (genericType instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType) genericType; if (parameterizedType.getActualTypeArguments() != null && parameterizedType.getActualTypeArguments().length > 0) { genericType = parameterizedType.getActualTypeArguments()[0]; } } if (a instanceof ShardKey) { shardKeyParameters.add(new ShardKeyParameter(pos, clazz)); } if (a instanceof SProcParam) { final SProcParam sParam = (SProcParam) a; final String dbTypeName = sParam.type(); try { params.add(StoredProcedureParameter.createParameter(clazz, genericType, method, dbTypeName, sParam.sqlType(), pos, sParam.sensitive())); } catch (final InstantiationException | IllegalAccessException e) { LOG.error("Could not instantiate StoredProcedureParameter. ABORTING.", e); throw new IllegalArgumentException("Could not instantiate StoredProcedureParameter. ABORTING."); } } } pos++; } final StoredProcedure storedProcedure = createStoredProcedure(scA, handlerResult, method, name, params, sprocStrategy, shardKeyParameters, resultMapper, useValidation); result.put(method, storedProcedure); } return result; } private StoredProcedure createStoredProcedure(SProcCall scA, SProcServiceAnnotationHandler.HandlerResult handlerResult, Method method, String name, List<StoredProcedureParameter> params, VirtualShardKeyStrategy sprocStrategy, List<ShardKeyParameter> shardKeyParameters, RowMapper<?> resultMapper, boolean useValidation) { try { SProcService.WriteTransaction writeTransaction = mapSprocWriteTransactionToServiceWriteTransaction(scA.shardedWriteTransaction(), handlerResult); String query = !"".equals(scA.sql()) ? scA.sql() : null; StoredProcedure storedProcedure = new StoredProcedure(name, query, params, method.getGenericReturnType(), sprocStrategy, shardKeyParameters, scA.runOnAllShards(), scA.searchShards(), scA.parallel(), resultMapper, scA.timeoutInMilliSeconds(), new SProcCall.AdvisoryLock(scA.adivsoryLockName(),scA.adivsoryLockId()), useValidation, scA.readOnly(), writeTransaction); return storedProcedure; } catch (final InstantiationException | IllegalAccessException e) { LOG.error("Could not instantiate StoredProcedure. ABORTING.", e); throw new IllegalArgumentException("Could not instantiate StoredProcedure. ABORTING."); } } }