package com.ctrip.platform.dal.dao.task;
import static com.ctrip.platform.dal.dao.helper.DalShardingHelper.buildShardStr;
import static com.ctrip.platform.dal.dao.helper.DalShardingHelper.getDatabaseSet;
import static com.ctrip.platform.dal.dao.helper.DalShardingHelper.isTableShardingEnabled;
import static com.ctrip.platform.dal.dao.helper.DalShardingHelper.locateTableShardId;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.ctrip.platform.dal.common.enums.DatabaseCategory;
import com.ctrip.platform.dal.dao.DalClient;
import com.ctrip.platform.dal.dao.DalClientFactory;
import com.ctrip.platform.dal.dao.DalHintEnum;
import com.ctrip.platform.dal.dao.DalHints;
import com.ctrip.platform.dal.dao.DalParser;
import com.ctrip.platform.dal.dao.DalQueryDao;
import com.ctrip.platform.dal.dao.StatementParameters;
import com.ctrip.platform.dal.dao.UpdatableEntity;
public class TaskAdapter<T> implements DaoTask<T> {
public static final String GENERATED_KEY = "GENERATED_KEY";
//public static final String TMPL_SQL_FIND_BY = "SELECT * FROM %s WHERE %s";
protected static final String COLUMN_SEPARATOR = ", ";
protected static final String PLACE_HOLDER = "?";
protected static final String TMPL_SET_VALUE = "%s=?";
protected static final String AND = " AND ";
protected static final String OR = " OR ";
protected static final String TMPL_CALL = "{call %s(%s)}";
public static String findtmp = "SELECT * FROM %s WHERE %s";
protected DalClient client;
protected DalQueryDao queryDao;
protected DalParser<T> parser;
protected String logicDbName;
protected DatabaseCategory dbCategory;
protected String pkSql;
protected Set<String> pkColumns;
protected Set<String> sensitiveColumns;
protected Map<String, Integer> columnTypes = new HashMap<String, Integer>();
protected String updateCriteriaTmpl;
protected String setValueTmpl;
protected String setVersionValueTmpl;
protected boolean hasVersion;
protected boolean isVersionUpdatable;
protected Set<String> defaultUpdateColumnNames;
public boolean tableShardingEnabled;
protected String rawTableName;
public void initialize(DalParser<T> parser) {
this.client = DalClientFactory.getClient(parser.getDatabaseName());
this.parser = parser;
this.logicDbName = parser.getDatabaseName();
queryDao = new DalQueryDao(parser.getDatabaseName());
rawTableName = parser.getTableName();
tableShardingEnabled = isTableShardingEnabled(logicDbName, rawTableName);
initColumnTypes();
dbCategory = getDatabaseSet(logicDbName).getDatabaseCategory();
initDbSpecific();
initSensitiveColumns();
}
public void initDbSpecific() {
pkSql = initPkSql();
initUpdateColumns();
setValueTmpl = dbCategory.getNullableUpdateTpl();
initVersionColumnUpdateTemplate();
}
private void initUpdateColumns() {
defaultUpdateColumnNames = new LinkedHashSet<>(Arrays.asList(parser.getUpdatableColumnNames()));
for (String column : parser.getPrimaryKeyNames()) {
defaultUpdateColumnNames.remove(column);
}
hasVersion = parser.getVersionColumn() != null;
isVersionUpdatable = hasVersion ? defaultUpdateColumnNames.contains(parser.getVersionColumn()) : false;
// Remove Version from updatable columns
if(hasVersion)
defaultUpdateColumnNames.remove(parser.getVersionColumn());
}
/**
* If there is version column and it is updatable, the column can not be null and it will always use the update version template.
*/
private void initVersionColumnUpdateTemplate() {
String versionColumn = parser.getVersionColumn();
updateCriteriaTmpl = pkSql;
if(versionColumn == null)
return;
String quotedVersionColumn = quote(parser.getVersionColumn());
updateCriteriaTmpl += AND + String.format(TMPL_SET_VALUE, quotedVersionColumn);
if(!isVersionUpdatable)
return;
int versionType = getColumnType(versionColumn);
String valueTmpl = null;
if(versionType == Types.TIMESTAMP){
valueTmpl = dbCategory.getTimestampExp();
}else{
valueTmpl = quote(parser.getVersionColumn()) + "+1";
}
setVersionValueTmpl = quotedVersionColumn + "=" + valueTmpl;
}
public String getTableName(DalHints hints) throws SQLException {
return getTableName(hints, null, null);
}
public String getTableName(DalHints hints, StatementParameters parameters) throws SQLException {
return getTableName(hints, parameters, null);
}
public String getTableName(DalHints hints, Map<String, ?> fields) throws SQLException {
return getTableName(hints, null, fields);
}
public String getTableName(DalHints hints, StatementParameters parameters, Map<String, ?> fields) throws SQLException {
return quote(getRawTableName(hints, parameters, fields));
}
public String getRawTableName(DalHints hints) throws SQLException {
return getRawTableName(hints, null, null);
}
public String getRawTableName(DalHints hints, StatementParameters parameters) throws SQLException {
return getRawTableName(hints, parameters, null);
}
public String getRawTableName(DalHints hints, Map<String, ?> fields) throws SQLException {
return getRawTableName(hints, null, fields);
}
public String getRawTableName(DalHints hints, StatementParameters parameters, Map<String, ?> fields) throws SQLException {
if(tableShardingEnabled == false)
return rawTableName;
hints.cleanUp();
return rawTableName + buildShardStr(logicDbName, locateTableShardId(logicDbName, hints, parameters, fields));
}
/**
* Add all the entries into the parameters by index. The parameter index
* will depends on the index of the entry in the entry set, value will be
* entry value. The value can be null.
*
* @param parameters A container that holds all the necessary parameters
* @param entries Key value pairs to be added into parameters
*/
public void addParameters(StatementParameters parameters,
Map<String, ?> entries) {
int index = parameters.size() + 1;
for (Map.Entry<String, ?> entry : entries.entrySet()) {
addParameter(parameters, index++, entry.getKey(), entry.getValue());
}
}
public void addParameters(StatementParameters parameters,
Map<String, ?> entries, String[] validColumns) {
int index = parameters.size() + 1;
for(String column : validColumns){
addParameter(parameters, index++, column, entries.get(column));
}
}
public int addParameters(int start, StatementParameters parameters,
Map<String, ?> entries, List<String> validColumns) {
int count = 0;
for(String column : validColumns){
addParameter(parameters, count + start, column, entries.get(column));
count++;
}
return count;
}
/**
* Add all the entries into the parameters by name. The parameter name will
* be the entry key, value will be entry value. The value can be null. This
* method will be used to set input parameters for stored procedure.
*
* @param parameters A container that holds all the necessary parameters
* @param entries Key value pairs to be added into parameters
*/
public void addParametersByName(StatementParameters parameters,
Map<String, ?> entries) {
for (Map.Entry<String, ?> entry : entries.entrySet()) {
addParameter(parameters, entry.getKey(), entry.getValue());
}
}
/**
* Add all the entries into the parameters by name. The parameter name will
* be the entry key, value will be entry value. The value can be null. This
* method will be used to set input parameters for stored procedure.
*
* @param parameters A container that holds all the necessary parameters
* @param entries Key value pairs to be added into parameters
*/
public void addParametersByName(StatementParameters parameters,
Map<String, ?> entries, String[] validColumns) {
for(String column : validColumns){
addParameter(parameters, column, entries.get(column));
}
}
/**
* According to DBA, call SP by parameter name will invoke sp_sproc_columns to get SP metadata, this is a
* very costly operation. To avoid the cost, it is required to call sp by parameter index instead of name
*/
public void addParametersByIndex(StatementParameters parameters,
Map<String, ?> entries) {
int index = parameters.size() + 1;
for (Map.Entry<String, ?> entry : entries.entrySet()) {
addParameterByIndex(parameters, index++, entry.getKey(), entry.getValue());
}
}
/**
* According to DBA, call SP by parameter name will invoke sp_sproc_columns to get SP metadata, this is a
* very costly operation. To avoid the cost, it is required to call sp by parameter index instead of name
*/
public void addParametersByIndex(StatementParameters parameters,
Map<String, ?> entries, String[] validColumns) {
int index = parameters.size() + 1;
for(String column : validColumns){
addParameterByIndex(parameters, index++, column, entries.get(column));
}
}
public void addParameterByIndex(StatementParameters parameters, int index, String columnName, Object value) {
if(isSensitive(columnName))
parameters.setSensitive(index, getColumnType(columnName), value);
else
parameters.set(index, getColumnType(columnName), value);
}
public void addParameter(StatementParameters parameters, int index, String columnName, Object value) {
if(isSensitive(columnName))
parameters.setSensitive(index, columnName, getColumnType(columnName), value);
else
parameters.set(index, columnName, getColumnType(columnName), value);
}
private void addParameter(StatementParameters parameters, String columnName, Object value) {
if(isSensitive(columnName))
parameters.setSensitive(columnName, getColumnType(columnName), value);
else
parameters.set(columnName, getColumnType(columnName), value);
}
/**
* Get the column type defined in java.sql.Types.
*
* @param columnName The column name of the table
* @return value defined in java.sql.Types
*/
public int getColumnType(String columnName) {
return columnTypes.get(columnName);
}
/**
* Remove all the null value in the given map.
*
* @param fields
* @return the original map reference
*/
public Map<String, ?> filterNullFileds(Map<String, ?> fields) {
for (String columnName : parser.getColumnNames()) {
if (fields.get(columnName) == null)
fields.remove(columnName);
}
return fields;
}
public Set<String> getUpdatedColumns(T rawPojo) {
return rawPojo instanceof UpdatableEntity ?
((UpdatableEntity)rawPojo).getUpdatedColumns() :
(Set<String>)Collections.EMPTY_SET;
}
public Set<String> filterColumns(DalHints hints) {
Set<String> qulifiedColumns = new HashSet<>(defaultUpdateColumnNames);
if(hints.is(DalHintEnum.includedColumns))
qulifiedColumns.retainAll(hints.getIncluded());
if(hints.is(DalHintEnum.excludedColumns))
qulifiedColumns.removeAll(hints.getExcluded());
return qulifiedColumns;
}
public Map<String, ?> removeAutoIncrementPrimaryFields(Map<String, ?> fields){
// This is bug here, for My Sql, auto incremental id can be part of the joint primary key.
// But for Ctrip, a table must have a pk defined by single column as mandatory, so we don't have problem here
if(parser.isAutoIncrement())
fields.remove(parser.getPrimaryKeyNames()[0]);
return fields;
}
public String buildCallSql(String spName, int paramCount) {
return String.format(TMPL_CALL, spName,
combine(PLACE_HOLDER, paramCount, COLUMN_SEPARATOR));
}
public boolean isEmpty(List<?> daoPojos) {
return null == daoPojos || daoPojos.size() == 0;
}
public List<Map<String, ?>> getPojosFields(List<T> daoPojos) {
List<Map<String, ?>> pojoFields = new LinkedList<Map<String, ?>>();
if (null == daoPojos || daoPojos.size() < 1)
return pojoFields;
for (T pojo: daoPojos){
pojoFields.add(parser.getFields(pojo));
}
return pojoFields;
}
public Map<Integer, Map<String, ?>> getPojosFieldsMap(List<T> daoPojos) {
Map<Integer, Map<String, ?>> daoPojosMaps = new LinkedHashMap<>();
for(int i = 0; i < daoPojos.size(); i ++)
daoPojosMaps.put(i, parser.getFields(daoPojos.get(i)));
return daoPojosMaps;
}
public boolean isPrimaryKey(String fieldName){
return pkColumns.contains(fieldName);
}
public boolean isSensitive(String fieldName){
if(sensitiveColumns.isEmpty())
return false;
return sensitiveColumns.contains(fieldName);
}
public String initPkSql() {
pkColumns = new HashSet<String>();
Collections.addAll(pkColumns, parser.getPrimaryKeyNames());
// Build primary key template
String template = combine(TMPL_SET_VALUE, parser.getPrimaryKeyNames().length, AND);
return String.format(template, (Object[]) quote(parser.getPrimaryKeyNames()));
}
public void initSensitiveColumns() {
sensitiveColumns = new HashSet<String>();
if(parser.getSensitiveColumnNames() != null)
Collections.addAll(sensitiveColumns, parser.getSensitiveColumnNames());
}
// Build a lookup table
public void initColumnTypes() {
String[] cloumnNames = parser.getColumnNames();
int[] columnsTypes = parser.getColumnTypes();
for (int i = 0; i < cloumnNames.length; i++) {
columnTypes.put(cloumnNames[i], columnsTypes[i]);
}
}
public Map<String, ?> getPrimaryKeys(Map<String, ?> fields) {
Map<String, Object> pks = new LinkedHashMap<>();
for(String pkName: parser.getPrimaryKeyNames())
pks.put(pkName, fields.get(pkName));
return pks;
}
public String buildWhereClause(Map<String, ?> fields) {
return String.format(combine(TMPL_SET_VALUE, fields.size(), AND),
quote(fields.keySet()));
}
public String combineColumns(Collection<String> values, String separator) {
StringBuilder valuesSb = new StringBuilder();
int i = 0;
for (String value : values) {
quote(valuesSb, value);
if (++i < values.size())
valuesSb.append(separator);
}
return valuesSb.toString();
}
public String combine(String value, int count, String separator) {
StringBuilder valuesSb = new StringBuilder();
for (int i = 1; i <= count; i++) {
valuesSb.append(value);
if (i < count)
valuesSb.append(separator);
}
return valuesSb.toString();
}
public String quote(String column) {
return dbCategory.quote(column);
}
public StringBuilder quote(StringBuilder sb, String column) {
return sb.append(dbCategory.quote(column));
}
public Object[] quote(Set<String> columns) {
Object[] quatedColumns = columns.toArray();
for(int i = 0; i < quatedColumns.length; i++)
quatedColumns[i] = quote((String)quatedColumns[i]);
return quatedColumns;
}
public String[] quote(String[] columns) {
String[] quatedColumns = new String[columns.length];
for(int i = 0; i < columns.length; i++)
quatedColumns[i] = quote(columns[i]);
return quatedColumns;
}
}