/*
* Copyright 1999-2015 dangdang.com.
* <p>
* 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.
* </p>
*/
package com.dangdang.ddframe.rdb.sharding.config.common.api;
import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.MultipleKeysDatabaseShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.MultipleKeysTableShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.config.common.api.config.AutoIncrementColumnConfig;
import com.dangdang.ddframe.rdb.sharding.config.common.api.config.BindingTableRuleConfig;
import com.dangdang.ddframe.rdb.sharding.config.common.api.config.ShardingRuleConfig;
import com.dangdang.ddframe.rdb.sharding.config.common.api.config.StrategyConfig;
import com.dangdang.ddframe.rdb.sharding.config.common.api.config.TableRuleConfig;
import com.dangdang.ddframe.rdb.sharding.config.common.internal.algorithm.ClosureDatabaseShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.config.common.internal.algorithm.ClosureTableShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.config.common.internal.parser.InlineParser;
import com.dangdang.ddframe.rdb.sharding.id.generator.IdGenerator;
import com.dangdang.ddframe.rdb.sharding.router.strategy.MultipleKeysShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.router.strategy.ShardingAlgorithm;
import com.dangdang.ddframe.rdb.sharding.router.strategy.ShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.router.strategy.SingleKeyShardingAlgorithm;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.MapUtils;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 分片规则构建器.
*
* @author gaohongtao
*/
@AllArgsConstructor
public final class ShardingRuleBuilder {
private final String logRoot;
private final Map<String, DataSource> externalDataSourceMap;
private final ShardingRuleConfig shardingRuleConfig;
public ShardingRuleBuilder(final ShardingRuleConfig shardingRuleConfig) {
this("default", shardingRuleConfig);
}
public ShardingRuleBuilder(final String logRoot, final ShardingRuleConfig shardingRuleConfig) {
this(logRoot, Collections.<String, DataSource>emptyMap(), shardingRuleConfig);
}
/**
* 构建分片规则.
*
* @return 分片规则对象
*/
public ShardingRule build() {
DataSourceRule dataSourceRule = buildDataSourceRule();
Collection<TableRule> tableRules = buildTableRules(dataSourceRule);
com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule.ShardingRuleBuilder shardingRuleBuilder = ShardingRule.builder().dataSourceRule(dataSourceRule);
if (!Strings.isNullOrEmpty(shardingRuleConfig.getIdGeneratorClass())) {
shardingRuleBuilder.idGenerator(loadClass(shardingRuleConfig.getIdGeneratorClass(), IdGenerator.class));
}
return shardingRuleBuilder.tableRules(tableRules).bindingTableRules(buildBindingTableRules(tableRules))
.databaseShardingStrategy(buildShardingStrategy(shardingRuleConfig.getDefaultDatabaseStrategy(), DatabaseShardingStrategy.class))
.tableShardingStrategy(buildShardingStrategy(shardingRuleConfig.getDefaultTableStrategy(), TableShardingStrategy.class)).build();
}
private DataSourceRule buildDataSourceRule() {
Preconditions.checkArgument(!shardingRuleConfig.getDataSource().isEmpty() || MapUtils.isNotEmpty(externalDataSourceMap), "Sharding JDBC: No data source config");
return shardingRuleConfig.getDataSource().isEmpty() ? new DataSourceRule(externalDataSourceMap, shardingRuleConfig.getDefaultDataSourceName())
: new DataSourceRule(shardingRuleConfig.getDataSource(), shardingRuleConfig.getDefaultDataSourceName());
}
private Collection<TableRule> buildTableRules(final DataSourceRule dataSourceRule) {
Collection<TableRule> result = new ArrayList<>(shardingRuleConfig.getTables().size());
for (Entry<String, TableRuleConfig> each : shardingRuleConfig.getTables().entrySet()) {
String logicTable = each.getKey();
TableRuleConfig tableRuleConfig = each.getValue();
TableRule.TableRuleBuilder tableRuleBuilder = TableRule.builder(logicTable).dataSourceRule(dataSourceRule)
.dynamic(tableRuleConfig.isDynamic())
.databaseShardingStrategy(buildShardingStrategy(tableRuleConfig.getDatabaseStrategy(), DatabaseShardingStrategy.class))
.tableShardingStrategy(buildShardingStrategy(tableRuleConfig.getTableStrategy(), TableShardingStrategy.class));
if (null != tableRuleConfig.getActualTables()) {
tableRuleBuilder.actualTables(new InlineParser(tableRuleConfig.getActualTables()).evaluate());
}
if (!Strings.isNullOrEmpty(tableRuleConfig.getDataSourceNames())) {
tableRuleBuilder.dataSourceNames(new InlineParser(tableRuleConfig.getDataSourceNames()).evaluate());
}
buildAutoIncrementColumn(tableRuleBuilder, tableRuleConfig);
result.add(tableRuleBuilder.build());
}
return result;
}
private void buildAutoIncrementColumn(final TableRule.TableRuleBuilder tableRuleBuilder, final TableRuleConfig tableRuleConfig) {
for (AutoIncrementColumnConfig each : tableRuleConfig.getAutoIncrementColumns()) {
if (Strings.isNullOrEmpty(each.getColumnIdGeneratorClass())) {
tableRuleBuilder.autoIncrementColumns(each.getColumnName());
} else {
tableRuleBuilder.autoIncrementColumns(each.getColumnName(), loadClass(each.getColumnIdGeneratorClass(), IdGenerator.class));
}
}
}
private Collection<BindingTableRule> buildBindingTableRules(final Collection<TableRule> tableRules) {
Collection<BindingTableRule> result = new ArrayList<>(shardingRuleConfig.getBindingTables().size());
for (BindingTableRuleConfig each : shardingRuleConfig.getBindingTables()) {
result.add(new BindingTableRule(Lists.transform(new InlineParser(each.getTableNames()).split(), new Function<String, TableRule>() {
@Override
public TableRule apply(final String input) {
return findTableRuleByLogicTableName(tableRules, input);
}
})));
}
return result;
}
private TableRule findTableRuleByLogicTableName(final Collection<TableRule> tableRules, final String logicTableName) {
for (TableRule each : tableRules) {
if (logicTableName.equals(each.getLogicTable())) {
return each;
}
}
throw new IllegalArgumentException(String.format("Sharding JDBC: Binding table `%s` is not an available Table rule", logicTableName));
}
private <T extends ShardingStrategy> T buildShardingStrategy(final StrategyConfig config, final Class<T> returnClass) {
if (null == config) {
return null;
}
Preconditions.checkArgument(Strings.isNullOrEmpty(config.getAlgorithmExpression()) && !Strings.isNullOrEmpty(config.getAlgorithmClassName())
|| !Strings.isNullOrEmpty(config.getAlgorithmExpression()) && Strings.isNullOrEmpty(config.getAlgorithmClassName()));
Preconditions.checkState(returnClass.isAssignableFrom(DatabaseShardingStrategy.class) || returnClass.isAssignableFrom(TableShardingStrategy.class), "Sharding-JDBC: returnClass is illegal");
List<String> shardingColumns = new InlineParser(config.getShardingColumns()).split();
if (Strings.isNullOrEmpty(config.getAlgorithmClassName())) {
return buildShardingAlgorithmExpression(shardingColumns, config.getAlgorithmExpression(), returnClass);
}
return buildShardingAlgorithmClassName(shardingColumns, config.getAlgorithmClassName(), returnClass);
}
@SuppressWarnings("unchecked")
private <T extends ShardingStrategy> T buildShardingAlgorithmExpression(final List<String> shardingColumns, final String algorithmExpression, final Class<T> returnClass) {
return returnClass.isAssignableFrom(DatabaseShardingStrategy.class) ? (T) new DatabaseShardingStrategy(shardingColumns, new ClosureDatabaseShardingAlgorithm(algorithmExpression, logRoot))
: (T) new TableShardingStrategy(shardingColumns, new ClosureTableShardingAlgorithm(algorithmExpression, logRoot));
}
@SuppressWarnings("unchecked")
private <T extends ShardingStrategy> T buildShardingAlgorithmClassName(final List<String> shardingColumns, final String algorithmClassName, final Class<T> returnClass) {
ShardingAlgorithm shardingAlgorithm;
try {
shardingAlgorithm = (ShardingAlgorithm) Class.forName(algorithmClassName).newInstance();
} catch (final InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new IllegalArgumentException(ex);
}
Preconditions.checkState(shardingAlgorithm instanceof SingleKeyShardingAlgorithm || shardingAlgorithm instanceof MultipleKeysShardingAlgorithm, "Sharding-JDBC: algorithmClassName is illegal");
if (shardingAlgorithm instanceof SingleKeyShardingAlgorithm) {
Preconditions.checkArgument(1 == shardingColumns.size(), "Sharding-JDBC: SingleKeyShardingAlgorithm must have only ONE sharding column");
return returnClass.isAssignableFrom(DatabaseShardingStrategy.class) ? (T) new DatabaseShardingStrategy(shardingColumns.get(0), (SingleKeyDatabaseShardingAlgorithm<?>) shardingAlgorithm)
: (T) new TableShardingStrategy(shardingColumns.get(0), (SingleKeyTableShardingAlgorithm<?>) shardingAlgorithm);
}
return returnClass.isAssignableFrom(DatabaseShardingStrategy.class) ? (T) new DatabaseShardingStrategy(shardingColumns, (MultipleKeysDatabaseShardingAlgorithm) shardingAlgorithm)
: (T) new TableShardingStrategy(shardingColumns, (MultipleKeysTableShardingAlgorithm) shardingAlgorithm);
}
@SuppressWarnings("unchecked")
private <T> Class<? extends T> loadClass(final String className, final Class<T> superClass) {
try {
return (Class<? extends T>) superClass.getClassLoader().loadClass(className);
} catch (final ClassNotFoundException ex) {
throw new IllegalArgumentException(ex);
}
}
}