/* * Copyright 2014-2015 the original author or authors * * 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. */ // Created on 2015年4月19日 // $Id$ package com.wplatform.ddal.shards; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.sql.DataSource; import com.wplatform.ddal.message.DbException; import com.wplatform.ddal.util.New; import com.wplatform.ddal.util.StringUtils; /** * @author <a href="mailto:jorgie.mail@gmail.com">jorgie li</a> */ public class SmartDataSource implements DataSource, Failover { private final String shardName; private final DataSourceRepository database; private final List<DataSourceMarker> menbers; private final Set<DataSourceMarker> readable = New.copyOnWriteArraySet(); private final Set<DataSourceMarker> writable = New.copyOnWriteArraySet(); private volatile LoadBalancingStrategy writableLoadBalance; private volatile LoadBalancingStrategy readableLoadBalance; private PrintWriter out = null; private int seconds = 0; /** * @param uid * @param writable * @param readable * @param datasource */ public SmartDataSource(DataSourceRepository database, String shardName, List<DataSourceMarker> menbers) { if (database == null) { throw new IllegalArgumentException("No dataSource repository specified"); } if (StringUtils.isNullOrEmpty(shardName)) { throw new IllegalArgumentException("No shardName specified"); } this.database = database; this.shardName = shardName; this.menbers = menbers; List<DataSourceMarker> writable = New.arrayList(); List<DataSourceMarker> readable = New.arrayList(); for (DataSourceMarker item : menbers) { if (!item.isReadOnly() && item.getwWeight() > 0) { writable.add(item); } if (item.getrWeight() > 0) { readable.add(item); } } if (writable.size() < 1) { throw new IllegalStateException(); } if (readable.size() < 1) { throw new IllegalStateException(); } this.writable.addAll(writable); this.readable.addAll(readable); this.writableLoadBalance = new ConsistentHashing(writable, false); this.readableLoadBalance = new ConsistentHashing(readable, true); } @Override public PrintWriter getLogWriter() throws SQLException { return out; } @Override public void setLogWriter(PrintWriter out) throws SQLException { this.out = out; } @Override public void setLoginTimeout(int seconds) throws SQLException { this.seconds = seconds; } @Override public int getLoginTimeout() throws SQLException { return seconds; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } /** * Return an object of this class if possible. * * @param iface the class * @return this */ @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (isWrapperFor(iface)) { return (T) this; } throw DbException.getInvalidValueException("iface", iface); } /** * Checks if unwrap can return an object of this class. * * @param iface the class * @return whether or not the interface is assignable from this class */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface != null && iface.isAssignableFrom(getClass()); } @Override public Connection getConnection() throws SQLException { return SmartConnection.newInstance(database, this); } @Override public Connection getConnection(String username, String password) throws SQLException { return SmartConnection.newInstance(database, this, username, password); } public DataSourceMarker doRoute(boolean readOnly) { DataSourceMarker next; if (!readOnly) { next = writableLoadBalance.next(); } else { next = readableLoadBalance.next(); } return next; } public DataSourceMarker doRoute(boolean readOnly, List<DataSourceMarker> exclusive) { for (DataSourceMarker marker : menbers) { if (exclusive.contains(marker)) { continue; } if (!readOnly && marker.isReadOnly()) { continue; } return marker; } return null; } @Override public void doHandleAbnormal(DataSourceMarker source) { if (!menbers.contains(source)) { throw new IllegalStateException(shardName + "datasource not matched. " + source); } if (!source.isReadOnly() && writable.remove(source)) { this.writableLoadBalance = new ConsistentHashing(writable, false); } if (readable.remove(source)) { readableLoadBalance = new ConsistentHashing(readable, true); } } @Override public void doHandleWakeup(DataSourceMarker source) { if (!menbers.contains(source)) { throw new IllegalStateException(shardName + " datasource not matched. " + source); } if (!source.isReadOnly() && source.getwWeight() > 0 && writable.add(source)) { this.writableLoadBalance = new ConsistentHashing(writable, false); } if (source.getrWeight() > 0 && readable.add(source)) { this.readableLoadBalance = new ConsistentHashing(readable, true); } } @Override public String toString() { return "RoutingDataSource [shardName=" + shardName + ", menbers=" + menbers + "]"; } }