blob: ec4d470f61de87d4057b71b7e63c102c6aea0657 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.internal.datasource;
/**
* This class models a datasource factory.The datasource factory has funtions to create 3 types of
* datasources. 1) Basic datasource without any connection pooling. 2) Datasource with pooled
* connections. 3) Datasource with pooled connection and transaction capabilities.
*
* The invokeAllMethods was setting only some specific properties Modified the code so that any key
* value mentioned in <property>tag is attempted for setting. If the property has a key as
* serverName , then the setter method is invoked with the name setServerName & the value present in
* value attribute is passed Made the Exception handling robust
*
* Changed invokeAllMethods wrt the change in cache.xml for vendor specific properties.
*
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ManagedConnectionFactory;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import org.apache.logging.log4j.Logger;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.datasource.PooledDataSourceFactory;
import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.util.PasswordUtil;
public class DataSourceFactory {
private static final Logger logger = LogService.getLogger();
private static final String DEFAULT_CONNECTION_POOL_DS_CLASS =
"org.apache.geode.connectors.jdbc.JdbcPooledDataSourceFactory";
private static final String POOL_PREFIX = "pool.";
/** Creates a new instance of DataSourceFactory */
public DataSourceFactory() {}
/**
* This function returns the Basic datasource without any pooling.
*
* @param configMap a map containing configurations required for datasource.
* @return ??
*/
public DataSource getSimpleDataSource(Map configMap) throws DataSourceCreateException {
ConfiguredDataSourceProperties configs = createDataSourceProperties(configMap);
if (configs.getURL() == null) {
logger.error("DataSourceFactory::getSimpleDataSource:URL String to Database is null");
throw new DataSourceCreateException(
"DataSourceFactory::getSimpleDataSource:URL String to Database is null");
}
try {
return new GemFireBasicDataSource(configs);
} catch (Exception ex) {
logger.error(String.format(
"DataSourceFactory::getSimpleDataSource:Exception while creating GemfireBasicDataSource.Exception String=%s",
ex.getLocalizedMessage()),
ex);
throw new DataSourceCreateException(
String.format(
"DataSourceFactory::getSimpleDataSource:Exception while creating GemfireBasicDataSource.Exception String=%s",
ex.getLocalizedMessage()),
ex);
}
}
/**
* This function creates a data source from the ManagedConnectionFactory class using the
* connectionManager.
*
* @return javax.sql.DataSource
*/
public ClientConnectionFactoryWrapper getManagedDataSource(Map configMap,
List<ConfigProperty> props) throws DataSourceCreateException {
Object cf = null;
ManagedConnectionFactory mcf = null;
ConfiguredDataSourceProperties configs = createDataSourceProperties(configMap);
if (configs.getMCFClass() == null) {
logger.error(
"DataSourceFactory::getManagedDataSource:Managed Connection factory class is not available");
throw new DataSourceCreateException(
"DataSourceFactory::getManagedDataSource:Managed Connection factory class is not available");
} else {
try {
Class cl = ClassPathLoader.getLatest().forName(configs.getMCFClass());
mcf = (ManagedConnectionFactory) cl.newInstance();
invokeAllMethods(cl, mcf, props);
} catch (Exception ex) {
logger.error(String.format(
"DataSourceFactory::getManagedDataSource: Exception in creating managed connection factory. Exception string, %s",
ex));
throw new DataSourceCreateException(
String.format(
"DataSourceFactory::getManagedDataSource: Exception in creating managed connection factory. Exception string, %s",
ex));
}
}
// Initialize the managed connection factory class
// Instantiate a connection manager with the managed-conn-facory-class
// and generate a connection pool.
Object cm = null;
if (configs.getMCFClass().equals("org.apache.persistence.connection.internal.ConnFactory")) {
cm = new FacetsJCAConnectionManagerImpl(mcf, configs);
} else {
cm = new JCAConnectionManagerImpl(mcf, configs);
}
try {
cf = mcf.createConnectionFactory((ConnectionManager) cm);
} catch (Exception ex) {
logger.error(
"DataSourceFactory::getManagedDataSource: Exception in creating managed connection factory. Exception string, %s",
ex.toString());
throw new DataSourceCreateException(
String.format(
"DataSourceFactory::getManagedDataSource: Exception in creating managed connection factory. Exception string, %s",
ex));
}
return new ClientConnectionFactoryWrapper(cf, cm);
}
/**
* This function returns the datasource with connection pooling.
*/
public DataSource getPooledDataSource(Map configMap, List<ConfigProperty> props)
throws DataSourceCreateException {
ConfiguredDataSourceProperties configs = createDataSourceProperties(configMap);
String connpoolClassName = configs.getConnectionPoolDSClass();
if (connpoolClassName == null) {
connpoolClassName = DEFAULT_CONNECTION_POOL_DS_CLASS;
}
try {
Properties poolProperties = createPoolProperties(configMap, props);
Properties dataSourceProperties = createDataSourceProperties(props);
Class<?> cl = ClassPathLoader.getLatest().forName(connpoolClassName);
PooledDataSourceFactory factory = (PooledDataSourceFactory) cl.newInstance();
return factory.createDataSource(poolProperties, dataSourceProperties);
} catch (Exception ex) {
String exception =
String.format(
"DataSourceFactory::getPooledDataSource:Exception creating ConnectionPoolDataSource.Exception string=%s",
new Object[] {ex});
logger.error(String.format(
"DataSourceFactory::getPooledDataSource:Exception creating ConnectionPoolDataSource.Exception string=%s",
ex),
ex);
throw new DataSourceCreateException(exception, ex);
}
}
static Properties createPoolProperties(Map<String, String> configMap,
List<ConfigProperty> props) {
Properties result = new Properties();
if (props != null) {
for (ConfigProperty prop : props) {
if (prop.getName().toLowerCase().startsWith(POOL_PREFIX)) {
String poolName = prop.getName().substring(POOL_PREFIX.length());
result.setProperty(poolName, prop.getValue());
}
}
}
if (configMap != null) {
for (Map.Entry<String, String> entry : configMap.entrySet()) {
if (entry.getValue() == null || entry.getValue().equals("")) {
continue;
}
if (entry.getKey().equals("type")) {
continue;
}
if (entry.getKey().equals("jdbc-driver-class")) {
continue;
}
if (entry.getKey().equals("jndi-name")) {
continue;
}
if (entry.getKey().equals("transaction-type")) {
continue;
}
if (entry.getKey().equals("conn-pooled-datasource-class")) {
continue;
}
if (entry.getKey().equals("managed-conn-factory-class")) {
continue;
}
if (entry.getKey().equals("xa-datasource-class")) {
continue;
}
result.setProperty(entry.getKey(), entry.getValue());
}
}
return result;
}
static Properties createDataSourceProperties(List<ConfigProperty> props) {
Properties result = new Properties();
if (props != null) {
for (ConfigProperty prop : props) {
if (prop.getName().toLowerCase().startsWith(POOL_PREFIX)) {
continue;
}
result.setProperty(prop.getName(), prop.getValue());
}
}
return result;
}
/**
* This function returns the datasource with connection pooling and transaction participation
* capabilities.
*
* @param configMap a map containing configurations required for datasource.
* @return ???
*/
public DataSource getTranxDataSource(Map configMap, List<ConfigProperty> props)
throws DataSourceCreateException {
ConfiguredDataSourceProperties configs = createDataSourceProperties(configMap);
String xaClassName = configs.getXADSClass();
if (xaClassName == null) {
logger.error(
"DataSourceFactory::getTranxDataSource:XADataSource class name for the ResourceManager is not available");
throw new DataSourceCreateException(
"DataSourceFactory::getTranxDataSource:XADataSource class name for the ResourceManager is not available");
}
if (TEST_CONNECTION_HOST != null) {
props.add(new ConfigProperty("serverName", TEST_CONNECTION_HOST, "java.lang.String"));
}
if (TEST_CONNECTION_PORT != null) {
props.add(new ConfigProperty("portNumber", TEST_CONNECTION_PORT, "int"));
}
try {
Class cl = ClassPathLoader.getLatest().forName(xaClassName);
Object Obj = cl.newInstance();
invokeAllMethods(cl, Obj, props);
return new GemFireTransactionDataSource((XADataSource) Obj, configs);
} catch (Exception ex) {
String exception =
String.format(
"DataSourceFactory::getTranxDataSource:Exception in creating GemFireTransactionDataSource. Exception string=%s",
new Object[] {ex});
logger.error(String.format(
"DataSourceFactory::getTranxDataSource:Exception in creating GemFireTransactionDataSource. Exception string=%s",
ex),
ex);
throw new DataSourceCreateException(exception, ex);
}
}
/**
* Creates ConfiguredDataSourceProperties from map.
*
* @param configMap a map containing configurations required for datasource.
*/
private ConfiguredDataSourceProperties createDataSourceProperties(Map configMap) {
ConfiguredDataSourceProperties configs = new ConfiguredDataSourceProperties();
Iterator entries = configMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
String name = (String) entry.getKey();
final Object obj = entry.getValue();
if (name.equals("connection-url"))
configs.setURL((String) obj);
else if (name.equals("user-name"))
configs.setUser((String) obj);
else if (name.equals("password"))
configs.setPassword(PasswordUtil.decrypt((String) obj));
else if (name.equals("jdbc-driver-class"))
configs.setJDBCDriver((String) obj);
else if (name.equals("init-pool-size"))
configs.setInitialPoolSize(Integer.parseInt((String) (obj == null
? String.valueOf(DataSourceResources.CONNECTION_POOL_DEFAULT_INIT_LIMIT) : obj)));
else if (name.equals("max-pool-size"))
configs.setMaxPoolSize(Integer.parseInt((String) (obj == null
? String.valueOf(DataSourceResources.CONNECTION_POOL_DEFAULT_MAX_LIMIT) : obj)));
else if (name.equals("idle-timeout-seconds"))
configs.setConnectionExpirationTime(Integer.parseInt((String) (obj == null
? String.valueOf(DataSourceResources.CONNECTION_POOL_DEFAULT_EXPIRATION_TIME) : obj)));
else if (name.equals("blocking-timeout-seconds"))
configs.setConnectionTimeOut(Integer.parseInt((String) (obj == null
? String.valueOf(DataSourceResources.CONNECTION_POOL_DEFAULT_ACTIVE_TIME_OUT) : obj)));
else if (name.equals("login-timeout-seconds"))
configs.setLoginTimeOut(Integer.parseInt((String) (obj == null
? String.valueOf(DataSourceResources.CONNECTION_POOL_DEFAULT_CLIENT_TIME_OUT) : obj)));
else if (name.equals("conn-pooled-datasource-class"))
configs.setConnectionPoolDSClass((String) obj);
else if (name.equals("xa-datasource-class"))
configs.setXADSClass((String) obj);
else if (name.equals("managed-conn-factory-class"))
configs.setMCFClass((String) obj);
else if (name.equals("transaction-type"))
configs.setTransactionType((String) obj);
}
/*
* Test hook for replacing URL
*/
if (TEST_CONNECTION_URL != null) {
configs.setURL((String) TEST_CONNECTION_URL);
}
return configs;
}
/*
* Test hook for replacing URL
*/
@MutableForTesting
private static String TEST_CONNECTION_URL = null;
@MutableForTesting
private static String TEST_CONNECTION_HOST = null;
@MutableForTesting
private static String TEST_CONNECTION_PORT = null;
/*
* Test hook for replacing URL
*/
public static void setTestConnectionUrl(String url) {
TEST_CONNECTION_URL = url;
}
/*
* Test hook for replacing Host
*/
public static void setTestConnectionHost(String host) {
TEST_CONNECTION_HOST = host;
}
/*
* Test hook for replacing Port
*/
public static void setTestConnectionPort(String port) {
TEST_CONNECTION_PORT = port;
}
/**
*
* dynamically invokes all the methods in the specified object.
*
* Asif: Rewrote the function as the number of properties to be set for a given datasource is
* dynamic
*
* Rohit: Rewrote to accomodate the change in cache.xml. property tag is now replaced by
* config-property for vendor specific data to get the Class name dynamically. CLass name is
* specified in config-property-type tag and is stored in ConfigProperty Data Object after parsing
* cache.xml.
*
* @param c class of the object
* @param cpdsObj The object
*/
private void invokeAllMethods(Class c, Object cpdsObj, List props)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
String key = null;
String value = null;
String type = null;
String methodName = null;
Method m = null;
for (Iterator it = props.iterator(); it.hasNext();) {
ConfigProperty cp = (ConfigProperty) it.next();
key = cp.getName();
value = cp.getValue();
type = cp.getType();
if (key.indexOf("password") != -1) {
value = PasswordUtil.decrypt(String.valueOf(value));
}
methodName = new StringBuffer("set").append(Character.toUpperCase(key.charAt(0)))
.append(key.length() > 1 ? key.substring(1) : "").toString();
try {
Class cl = null;
Class realClass = null;
if ("int".equals(type)) {
cl = int.class;
realClass = java.lang.Integer.class;
} else {
cl = ClassPathLoader.getLatest().forName(type);
realClass = cl;
}
Constructor cr = realClass.getConstructor(new Class[] {java.lang.String.class});
Object ob = cr.newInstance(new Object[] {value});
m = c.getMethod(methodName, new Class[] {cl});
m.invoke(cpdsObj, new Object[] {ob});
} catch (ClassNotFoundException ex) {
String exception =
String.format(
"DataSourceFactory::invokeAllMethods: Exception in creating Class with the given config-property-type classname. Exception string=%s",
ex.toString());
if (logger.isDebugEnabled()) {
logger.debug(exception, ex);
}
} catch (NoSuchMethodException ex) {
String exception =
String.format(
"DataSourceFactory::invokeAllMethods: Exception in creating method using config-property-name property. Exception string=%s",
ex.toString());
if (logger.isDebugEnabled()) {
logger.debug(exception, ex);
}
} catch (InstantiationException ex) {
String exception =
String.format(
"DataSourceFactory::invokeAllMethods: Exception in creating instance of the class using the constructor with a String parameter. Exception string=%s",
ex.toString());
if (logger.isDebugEnabled()) {
logger.debug(exception, ex);
}
}
}
}
}