/*
* 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 com.amazonaws.services.rds.model.DBInstanceNotFoundException;
import com.amazonaws.services.rds.model.DescribeDBInstancesRequest;
import com.amazonaws.services.rds.model.DescribeDBInstancesResult;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.jdbc.datasource.DataSourceFactory;
import org.springframework.cloud.aws.jdbc.datasource.DataSourceInformation;
import org.springframework.cloud.aws.jdbc.datasource.TomcatJdbcDataSourceFactory;
import org.springframework.cloud.aws.jdbc.datasource.support.DatabaseType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.text.MessageFormat;
/**
* {@link org.springframework.beans.factory.FactoryBean} implementation that creates a datasource backed by an Amazon
* Relational Database service instance. This factory bean retrieves all the metadata from the AWS RDS service in
* order to create and configure a datasource. This class uses the {@link AmazonRDS} service to retrieve the metadata
* and the {@link DataSourceFactory} to actually create the datasource.
*
* @author Agim Emruli
* @since 1.0
*/
public class AmazonRdsDataSourceFactoryBean extends AbstractFactoryBean<DataSource> {
private final AmazonRDS amazonRds;
private final String dbInstanceIdentifier;
private final String password;
private DataSourceFactory dataSourceFactory = new TomcatJdbcDataSourceFactory();
private String username;
private String databaseName;
private ResourceIdResolver resourceIdResolver;
/**
* 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
* metadata (in contrast to the user) so it must be provided in order to connect to the database with JDBC.
*/
public AmazonRdsDataSourceFactoryBean(AmazonRDS amazonRds, String dbInstanceIdentifier, String password) {
this.amazonRds = amazonRds;
this.dbInstanceIdentifier = dbInstanceIdentifier;
this.password = password;
}
/**
* Allows to configure a different DataSourceFactory in order to use a different DataSource implementation. Uses
* the
* {@link TomcatJdbcDataSourceFactory} by default if not configured.
*
* @param dataSourceFactory
* - A fully configured DataSourceFactory instance, will be used to actually create the
* datasource.
*/
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
}
/**
* Allows to set a different user then the master user name in order to connect to the database. In contrast to the
* password, the master user name is available in the metadata to connect to the database so this username is only
* used when configured.
*
* @param username
* - The username to connect to the database, every value provided (even empty ones) are used to connect to the
* database.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Configures an own database name to be used if the default database (that is configured in the meta-data) should not
* be used.
*
* @param databaseName
* - the database name to be used while connecting
*/
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
/**
* Configures an optional {@link org.springframework.cloud.aws.core.env.ResourceIdResolver} used to resolve a logical name to a
* physical one.
*
* @param resourceIdResolver
* - the resourceIdResolver instance, might be null or not called at all
*/
public void setResourceIdResolver(ResourceIdResolver resourceIdResolver) {
this.resourceIdResolver = resourceIdResolver;
}
@Override
public Class<DataSource> getObjectType() {
return DataSource.class;
}
@Override
protected DataSource createInstance() throws Exception {
return createDataSourceInstance(getDbInstanceIdentifier());
}
@Override
protected void destroyInstance(DataSource instance) throws Exception {
this.dataSourceFactory.closeDataSource(instance);
}
/**
* Creates a data source based in the instance name. The physical information for the data source is retrieved by
* the name passed as identifier. This method does distinguish between regular amazon rds instances and
* read-replicas because both meta-data is retrieved on the same way.
*
* @param identifier
* - the database identifier for the data source configured in amazon rds
* @return a fully configured and initialized {@link javax.sql.DataSource}
* @throws java.lang.IllegalStateException
* if no database has been found
* @throws java.lang.Exception
* in case of underlying exceptions
*/
protected DataSource createDataSourceInstance(String identifier) throws Exception {
DBInstance instance = getDbInstance(identifier);
return this.dataSourceFactory.createDataSource(fromRdsInstance(instance));
}
/**
* Retrieves the {@link com.amazonaws.services.rds.model.DBInstance} information
*
* @param identifier
* - the database identifier used
* @return - the db instance
* @throws IllegalStateException
* if the db instance is not found
*/
protected DBInstance getDbInstance(String identifier) throws IllegalStateException {
DBInstance instance;
try {
DescribeDBInstancesResult describeDBInstancesResult = this.amazonRds.describeDBInstances(new DescribeDBInstancesRequest().withDBInstanceIdentifier(identifier));
instance = describeDBInstancesResult.getDBInstances().get(0);
} catch (DBInstanceNotFoundException e) {
throw new IllegalStateException(MessageFormat.format("No database instance with id:''{0}'' found. Please specify a valid db instance",
identifier));
}
return instance;
}
protected String getDbInstanceIdentifier() {
return this.resourceIdResolver != null ? this.resourceIdResolver.resolveToPhysicalResourceId(this.dbInstanceIdentifier) : this.dbInstanceIdentifier;
}
private DataSourceInformation fromRdsInstance(DBInstance dbInstance) {
Assert.notNull(dbInstance, "DbInstance must not be null");
Assert.notNull(dbInstance.getEndpoint(), "The database instance has no endpoint available!");
return new DataSourceInformation(DatabaseType.fromEngine(dbInstance.getEngine()),
dbInstance.getEndpoint().getAddress(), dbInstance.getEndpoint().getPort(),
StringUtils.hasText(this.databaseName) ? this.databaseName : dbInstance.getDBName(),
StringUtils.hasText(this.username) ? this.username : dbInstance.getMasterUsername(), this.password);
}
}