package org.teiid.test.framework.datasource;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.core.util.StringUtil;
import org.teiid.test.framework.ConfigPropertyLoader;
import org.teiid.test.framework.ConfigPropertyNames;
import org.teiid.test.framework.TestLogger;
import org.teiid.test.framework.exception.QueryTestFailedException;
import org.teiid.test.framework.exception.TransactionRuntimeException;
/**
* The DataSourceFactory is responsible for managing the datasources used during a single test.
* It ensures the same data source is used in both the connector binding and during validation to ensure validation is performed
* against the same datasource the test was performed on.
*
* The following are the available options for controlling which datasources are used during a test:
* <li>Control which datasources are used and to which model they are assigned</li>
*
* <p>
* Use the {@link ConfigPropertyNames#USE_DATASOURCES_PROP} property to specify a comma delimited ordered list to control which data sources to use.
*
* <br><br>
* This will enable integration testing to be setup to use well known combinations, to ensure coverage and makes it easier to replicate issues.
*
* This indicates to use only the specified datasources for this test. This option can be used in test cases where only
* specific sources are to be used and tested against.
*
* This ordered list will map to the model:order mapping in the config.properties file.
* <br><br>
* Example: in the config.properties file, the models in the test and their order can be specified:
* <li> pm1=1 </li>
* <li> pm2=2 </li>
* <br>
*
* Then set property: ConfigPropertyNames.USE_DATASOURCES_PROP=oracle,sqlserver
*
* This will use oracle and sqlserver datasources in the test, and when a datasource is requested for model "pm1", the oracle datasource will be returned. And when a data source
* for "pm2" is requested, the data source mapped to 2nd ordered datasource of sqlserver will be returned.
*
* </p>
* <li>Control which datasources of a specific database type to exclude</li>
* <p>
*
* Use the {@link ConfigPropertyNames#EXCLUDE_DATASBASE_TYPES_PROP} property to specify a comma delimited list of {@link DataBaseTypes} to exclude.
* <br><br>
* This will remove all datasources of a specific database type from the list of available datasources. This option will be applied after
* {@link ConfigPropertyNames#USE_DATASOURCES_PROP} option. This is done because there are some test that will not work with certain database
* types, and therefore, if the required datasources are not met, the test will be bypassed for execution.
*</p>
* <li>Control for a specfic model, which database type of datasource to be assigned</li>
* <p>
* This option gives the developer even more fine grain control of how datasources are assigned. There are cases where a specific model must be assigned
* to a datasource of a specific database type.
*
* To use this option,
*
* </p>
* @author vanhalbert
*
*/
@SuppressWarnings("nls")
public class DataSourceFactory {
/**
* These types match the "ddl" directories for supported database types
* and it also found in the datasources connection.properties defined by the DB_TYPE property
* These are also the values used to specify the required database types.
*
*/
public static interface DataBaseTypes{
public static String MYSQL = "mysql";
public static String ORACLE = "oracle";
public static String POSTRES = "postgres";
public static String SQLSERVER = "sqlserver";
public static String DB2 = "db2";
public static String SYBASE = "sybase";
public static String DERBY = "derby";
public static String ANY = "any";
}
// the DO_NO_USE_DEFAULT will be passed in when the test are run from maven and no property is passed in for UseDataSources
private static final String DO_NOT_USE_DEFAULT="${" + ConfigPropertyNames.USE_DATASOURCES_PROP + "}";
private Properties configprops;
// contains the names of the datasources when the -Dusedatasources option is used
private Map<String, String> useDS = null;
// contains all the datasources available to be used
private Map<String, DataSource> availDS = null;
// contains any dbtype preconditions on a model, which requires a model to be assigned a certain database type
private Map<String, String> requiredDataBaseTypes = null; // key=modelname value=dbtype
// this set is use to track datasources that have already been assigned
private Set<String> assignedDataSources = new HashSet<String>();
private int lastassigned = 0;
// indicates if the datasource requirements have been, it will be false in the case
// a specific dbtype is required and that type was not one of the available types defined
private boolean metDBRequiredTypes = true;
// indicates that there are required dbtypes to consider
private boolean hasRequiredDBTypes = false;
public DataSourceFactory(ConfigPropertyLoader config) {
this.configprops = PropertiesUtils.clone(config.getProperties(), null, true);
this.requiredDataBaseTypes = config.getModelAssignedDatabaseTypes();
}
public Properties getConfigProperties() {
return this.configprops;
}
/**
* config is called at the start / setup of the {@link
* TransactionContainer#} test. This is to ensure any exclusions /
* inclusions are considered for the next executed set of test.
*
*
* 1st, check for the usedatasource property, if exist, then only add those specific datasources
* to the useDataSources, otherwise add all available.
* 2nd, if the exclude option is used, then remove any excluded datasources from the useDataSources.
*
* @since
*/
protected void config(DataSourceMgr dsmgr) {
TestLogger.logDebug("Configure Datasource Factory ");
dsmgr = DataSourceMgr.getInstance();
Map<String, DataSource> availDatasources = dsmgr.getDataSources();
availDS = new HashMap<String, DataSource>(availDatasources.size());
String usedstypeprop = configprops
.getProperty(ConfigPropertyNames.USE_DATASOURCE_TYPES_PROP);
Set<String> useDBTypes = null;
if (usedstypeprop != null && usedstypeprop.length() > 0) {
List<String> eprops = StringUtil.split(usedstypeprop, ",");
useDBTypes = new HashSet<String>(eprops.size());
useDBTypes.addAll(eprops);
System.out.println("EXCLUDE datasources: " + usedstypeprop);
} else {
useDBTypes = Collections.EMPTY_SET;
}
String excludeprop = configprops
.getProperty(ConfigPropertyNames.EXCLUDE_DATASBASE_TYPES_PROP);
Set<String> excludedDBTypes = null;
if (excludeprop != null && excludeprop.length() > 0) {
List<String> eprops = StringUtil.split(excludeprop, ",");
excludedDBTypes = new HashSet<String>(eprops.size());
excludedDBTypes.addAll(eprops);
System.out.println("EXCLUDE datasources: " + excludeprop);
} else {
excludedDBTypes = Collections.EMPTY_SET;
}
String limitdsprop = configprops
.getProperty(ConfigPropertyNames.USE_DATASOURCES_PROP);
if (limitdsprop != null && limitdsprop.length() > 0 && ! limitdsprop.equalsIgnoreCase(DO_NOT_USE_DEFAULT)) {
TestLogger.log("Use ONLY datasources: " + limitdsprop);
List<String> dss = StringUtil.split(limitdsprop, ",");
useDS = new HashMap<String, String>(dss.size());
DataSource ds = null;
int i = 1;
for (Iterator<String> it = dss.iterator(); it.hasNext(); i++) {
String dssName = it.next();
ds = availDatasources.get(dssName);
if (ds != null && !excludedDBTypes.contains(ds.getDBType())) {
useDS.put(String.valueOf(i), dssName);
availDS.put(dssName, ds);
TestLogger.logInfo("Using ds: " + dssName);
}
}
} else {
for (Iterator<DataSource> it = availDatasources.values().iterator(); it.hasNext(); ) {
DataSource ds = it.next();
// if the datasource type is not excluded, then consider for usages
if (!excludedDBTypes.contains(ds.getDBType())) {
// if use a specific db type is specified, then it must match,
// otherwise add it to the available list
if (useDBTypes.size() > 0) {
if ( usedstypeprop.contains(ds.getDBType())) {
availDS.put(ds.getName(), ds);
}
} else {
availDS.put(ds.getName(), ds);
}
}
}
}
if (requiredDataBaseTypes != null && requiredDataBaseTypes.size() > 0) {
this.hasRequiredDBTypes = true;
Iterator<String> rit = this.requiredDataBaseTypes.keySet().iterator();
// go thru all the datasources and remove those that are excluded
while (rit.hasNext()) {
String modelName = rit.next();
String rdbtype = this.requiredDataBaseTypes.get(modelName);
Iterator<DataSource> ait = availDS.values().iterator();
metDBRequiredTypes = false;
// go thru all the datasources and find the matching datasource of the correct dbtype
while (ait.hasNext()) {
DataSource ds = ait.next();
if (ds.getDBType().equalsIgnoreCase(rdbtype)) {
assignedDataSources.add(ds.getName());
dsmgr.setDataSource(modelName, ds);
// modelToDatasourceMap.put(modelName, ds);
metDBRequiredTypes = true;
}
}
if (!metDBRequiredTypes) {
// did find a required dbtype, no need going any further
break;
}
}
}
}
public int getNumberAvailableDataSources() {
return (metDBRequiredTypes ? this.availDS.size() :0);
}
public synchronized DataSource getDatasource(String modelName) throws QueryTestFailedException {
DataSource ds = null;
// map the datasource to the model and datasourceid
// this is so the next time this combination is requested,
// the same datasource is returned to ensure when consecutive calls
// during the process
// corresponds to the same datasource
String key = null;
key = modelName;
//+ "_" + datasourceid;
// if the datasourceid represents a group name, then use the group name
// so that all future request using that group name for a specified
// model
// will use the same datasource
if (this.hasRequiredDBTypes) {
if (this.requiredDataBaseTypes.containsKey(modelName)) {
String dbtype = this.requiredDataBaseTypes.get(modelName);
Iterator<DataSource> it = availDS.values().iterator();
// need to go thru all the datasources to know if any has already been
// assigned
// because the datasourceid passed in was a group name
while (it.hasNext()) {
DataSource checkit = it.next();
if (dbtype.equalsIgnoreCase(checkit.getDBType())) {
ds = checkit;
break;
}
}
}
} else if (useDS != null) {
String dsname = useDS.get(modelName);
if (dsname != null) {
ds = availDS.get(dsname);
if (ds == null) {
throw new QueryTestFailedException("Datasource name "
+ dsname
+ " was not found in the allDatasources map");
}
}
} else {
Iterator<DataSource> it = availDS.values().iterator();
// need to go thru all the datasources to know if any has already been
// assigned
// because the datasourceid passed in was a group name
while (it.hasNext()) {
DataSource checkit = it.next();
if (!assignedDataSources.contains(checkit.getName())) {
ds = checkit;
break;
}
}
}
if (ds == null) {
int cnt = 0;
Iterator<String> itds = assignedDataSources.iterator();
// when all the datasources have been assigned, but a new model
// datasource id is
// passed in, need to reassign a previously assigned datasource
// This case will happen when more models are defined than there are
// defined datasources.
while (itds.hasNext()) {
String dsname = itds.next();
if (cnt == this.lastassigned) {
ds = availDS.get(dsname);
this.lastassigned++;
if (lastassigned >= assignedDataSources.size()) {
this.lastassigned = 0;
}
break;
}
}
}
if (ds != null) {
assignedDataSources.add(ds.getName());
}
return ds;
}
public void cleanup() {
assignedDataSources.clear();
// requiredDataBaseTypes.clear();
if (useDS != null) useDS.clear();
// if (availDS != null) availDS.clear();
}
public static void main(String[] args) {
//NOTE: to run this test to validate the DataSourceMgr, do the following:
// --- need 3 datasources, Oracle, SqlServer and 1 other
ConfigPropertyLoader config = ConfigPropertyLoader.getInstance();
DataSourceFactory factory = new DataSourceFactory(config);
try {
if (factory.getDatasource("model1") == null) {
throw new TransactionRuntimeException("No datasource was not found");
}
} catch (QueryTestFailedException e) {
e.printStackTrace();
}
factory.cleanup();
ConfigPropertyLoader.reset();
// the following verifies that order of "use" datasources is applied to request for datasources.
config = ConfigPropertyLoader.getInstance();
config.setProperty(ConfigPropertyNames.USE_DATASOURCES_PROP, "oracle,sqlserver");
factory = new DataSourceFactory(config);
try {
DataSource dsfind = factory.getDatasource( "model2");
if (dsfind == null) {
throw new TransactionRuntimeException("No datasource was not found as the 2nd datasource");
}
if (dsfind.getConnectorType() == null) {
throw new TransactionRuntimeException("Connector types was not defined");
}
if (!dsfind.getName().equalsIgnoreCase("sqlserver")) {
throw new TransactionRuntimeException("Sqlserver was not found as the 2nd datasource");
}
dsfind = factory.getDatasource( "model1");
if (dsfind == null) {
throw new TransactionRuntimeException("No datasource was not found as the 2nd datasource");
}
if (!dsfind.getName().equalsIgnoreCase("oracle")) {
throw new TransactionRuntimeException("Oracle was not found as the 2nd datasource");
}
System.out.println("Datasource :" + dsfind.getName() + " was found");
// the following test verifies that a sqlserver datasource is not
// returned (excluded)
factory.cleanup();
ConfigPropertyLoader.reset();
config = ConfigPropertyLoader.getInstance();
config.setProperty(ConfigPropertyNames.EXCLUDE_DATASBASE_TYPES_PROP, "sqlserver");
factory = new DataSourceFactory(config);
int n = factory.getNumberAvailableDataSources();
TestLogger.log("Num avail datasources: " + n);
for (int i=0; i<n; i++) {
String k = String.valueOf(i);
DataSource ds1 = factory.getDatasource( "model" + k);
if (ds1 == null) {
throw new TransactionRuntimeException("No datasource was found for: model:" + k);
} if (ds1.getDBType().equalsIgnoreCase(DataSourceFactory.DataBaseTypes.SQLSERVER)) {
throw new TransactionRuntimeException("sqlserver dbtype should have been excluded");
}
}
DataSource reuse = factory.getDatasource( "model1");
if (reuse != null) {
} else {
throw new TransactionRuntimeException("The process was not able to reassign an already used datasource");
}
factory.cleanup();
ConfigPropertyLoader.reset();
// test required database types
// test 1 source
config = ConfigPropertyLoader.getInstance();
config.setModelAssignedToDatabaseType("pm1", DataSourceFactory.DataBaseTypes.ORACLE);
factory = new DataSourceFactory(config);
DataSource ds1 = factory.getDatasource("pm1");
if (!ds1.getDBType().equalsIgnoreCase(DataSourceFactory.DataBaseTypes.ORACLE)) {
throw new TransactionRuntimeException("Required DB Type of oracle for model pm1 is :" + ds1.getDBType());
}
TestLogger.log("Test1 Required DS1 " + ds1.getDBType());
factory.cleanup();
ConfigPropertyLoader.reset();
// test required database types
// test 2 sources, 1 required and other ANY
config = ConfigPropertyLoader.getInstance();
config.setModelAssignedToDatabaseType("pm2", DataSourceFactory.DataBaseTypes.SQLSERVER);
config.setModelAssignedToDatabaseType("pm1", DataSourceFactory.DataBaseTypes.ANY);
factory = new DataSourceFactory(config);
DataSource ds2 = factory.getDatasource("pm2");
if (!ds2.getDBType().equalsIgnoreCase(DataSourceFactory.DataBaseTypes.SQLSERVER)) {
throw new TransactionRuntimeException("Required DB Type of sqlserver for model pm2 is :" + ds2.getDBType());
}
TestLogger.log("Test2 Required DS2 " + ds2.getDBType());
factory.cleanup();
ConfigPropertyLoader.reset();
// test required database types
// test 2 sources, 2 required
config = ConfigPropertyLoader.getInstance();
config.setModelAssignedToDatabaseType("pm2", DataSourceFactory.DataBaseTypes.SQLSERVER);
config.setModelAssignedToDatabaseType("pm1", DataSourceFactory.DataBaseTypes.ORACLE);
factory = new DataSourceFactory(config);
DataSource ds3a = factory.getDatasource("pm2");
if (!ds3a.getDBType().equalsIgnoreCase(DataSourceFactory.DataBaseTypes.SQLSERVER)) {
throw new TransactionRuntimeException("Required DB Type of sqlserver for model pm12 is :" + ds3a.getDBType());
}
DataSource ds3b = factory.getDatasource("pm1");
if (!ds3b.getDBType().equalsIgnoreCase(DataSourceFactory.DataBaseTypes.ORACLE)) {
throw new TransactionRuntimeException("Required DB Type of oracle for model pm1 is :" + ds3b.getDBType());
}
TestLogger.log("Test3 Required DS3a " + ds3a.getDBType());
TestLogger.log("Test3 Required DS3b " + ds3b.getDBType());
factory.cleanup();
} catch (QueryTestFailedException e) {
e.printStackTrace();
}
}
}