/*
* Copyright 2013-2014 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.
*/
package org.springframework.cloud.aws.jdbc.rds;
import com.amazonaws.services.rds.AmazonRDS;
import com.amazonaws.services.rds.model.DBInstance;
import org.springframework.cloud.aws.jdbc.datasource.ReadOnlyRoutingDataSource;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
/**
* {@link org.springframework.cloud.aws.jdbc.rds.AmazonRdsDataSourceFactoryBean} sub-class that is capable to handle amazon rds
* read-replicas. This is especially useful in case of read-heavy applications to leverage read-replica instance for
* all
* read-accesses.
*
* @author Agim Emruli
*/
public class AmazonRdsReadReplicaAwareDataSourceFactoryBean extends AmazonRdsDataSourceFactoryBean {
/**
* Constructor which retrieves all mandatory objects to allow the object to be constructed. This are the minimal
* configuration options which uses defaults or no values for all optional elements.
*
* @param amazonRDS
* - The amazonRDS instance used to connect to the service. This object will be used to actually retrieve the
* datasource metadata from the Amazon RDS service.
* @param dbInstanceIdentifier
* - the unique database instance identifier in the Amazon RDS service
* @param password
* - The password used to connect to the datasource. For security reasons the password is not available in the
*/
public AmazonRdsReadReplicaAwareDataSourceFactoryBean(AmazonRDS amazonRDS, String dbInstanceIdentifier, String password) {
super(amazonRDS, dbInstanceIdentifier, password);
}
/**
* Constructs a {@link org.springframework.cloud.aws.jdbc.datasource.ReadOnlyRoutingDataSource} data source that contains the
* regular data source as a default, and all read-replicas as additional data source. The {@link
* org.springframework.cloud.aws.jdbc.datasource.ReadOnlyRoutingDataSource} is additionally wrapped with a {@link
* org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy}, because the read-only flag is only available
* after the transactional context has been established. This is only the case if the physical connection is
* requested after the transaction start and not while starting a transaction.
*
* @return a ReadOnlyRoutingDataSource that is wrapped with a LazyConnectionDataSourceProxy
* @throws Exception
* if the underlying data source setup throws any exception
*/
@Override
protected DataSource createInstance() throws Exception {
DBInstance dbInstance = getDbInstance(getDbInstanceIdentifier());
//If there is no read replica available, delegate to super class
if (dbInstance.getReadReplicaDBInstanceIdentifiers().isEmpty()) {
return super.createInstance();
}
HashMap<Object, Object> replicaMap = new HashMap<>(
dbInstance.getReadReplicaDBInstanceIdentifiers().size());
for (String replicaName : dbInstance.getReadReplicaDBInstanceIdentifiers()) {
replicaMap.put(replicaName, createDataSourceInstance(replicaName));
}
//Create the data source
ReadOnlyRoutingDataSource dataSource = new ReadOnlyRoutingDataSource();
dataSource.setTargetDataSources(replicaMap);
dataSource.setDefaultTargetDataSource(createDataSourceInstance(getDbInstanceIdentifier()));
//Initialize the class
dataSource.afterPropertiesSet();
return new LazyConnectionDataSourceProxy(dataSource);
}
@Override
protected void destroyInstance(DataSource instance) throws Exception {
if (instance instanceof LazyConnectionDataSourceProxy) {
DataSource targetDataSource = ((LazyConnectionDataSourceProxy) instance).getTargetDataSource();
if (targetDataSource instanceof ReadOnlyRoutingDataSource) {
List<Object> dataSources = ((ReadOnlyRoutingDataSource) targetDataSource).getDataSources();
for (Object candidate : dataSources) {
if (candidate instanceof DataSource) {
super.destroyInstance(instance);
}
}
}
}
}
}