| /* |
| * 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.jndi; |
| |
| import java.lang.reflect.Method; |
| import java.sql.Connection; |
| import java.sql.SQLException; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import javax.naming.Binding; |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NameNotFoundException; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.NoInitialContextException; |
| import javax.sql.DataSource; |
| import javax.transaction.SystemException; |
| import javax.transaction.TransactionManager; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.LogWriter; |
| import org.apache.geode.annotations.Immutable; |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| import org.apache.geode.annotations.internal.MutableForTesting; |
| import org.apache.geode.distributed.DistributedSystem; |
| import org.apache.geode.internal.ClassPathLoader; |
| import org.apache.geode.internal.datasource.ClientConnectionFactoryWrapper; |
| import org.apache.geode.internal.datasource.ConfigProperty; |
| import org.apache.geode.internal.datasource.DataSourceCreateException; |
| import org.apache.geode.internal.datasource.DataSourceFactory; |
| import org.apache.geode.internal.datasource.GemFireTransactionDataSource; |
| import org.apache.geode.internal.jta.TransactionManagerImpl; |
| import org.apache.geode.internal.jta.TransactionUtils; |
| import org.apache.geode.internal.jta.UserTransactionImpl; |
| import org.apache.geode.internal.util.DriverJarUtil; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.util.internal.GeodeGlossary; |
| |
| /** |
| * <p> |
| * Utility class for binding DataSources and Transactional resources to JNDI Tree. If there is a |
| * pre-existing JNDI tree in the system, the GemFire JNDI tree is not not generated. |
| * </p> |
| * <p> |
| * Datasources are bound to jndi tree after getting initialised. The initialisation parameters are |
| * read from cache.xml. |
| * </p> |
| * <p> |
| * If there is a pre-existing TransactionManager/UserTransaction (possible in presence of |
| * application server scenerio), the GemFire TransactionManager and UserTransaction will not be |
| * initialised. In that case, application server TransactionManager/UserTransaction will handle the |
| * transactional activity. But even in this case the datasource element will be bound to the |
| * available JNDI tree. The transactional datasource (XADataSource) will make use of available |
| * TransactionManager. |
| * </p> |
| * |
| */ |
| public class JNDIInvoker { |
| |
| private static final Logger logger = LogService.getLogger(); |
| |
| // private static boolean DEBUG = false; |
| /** |
| * JNDI Context, this may refer to GemFire JNDI Context or external Context, in case the external |
| * JNDI tree exists. |
| */ |
| @MakeNotStatic |
| private static Context ctx; |
| /** |
| * transactionManager TransactionManager, this refers to GemFire TransactionManager only. |
| */ |
| @MakeNotStatic |
| private static TransactionManager transactionManager; |
| // most of the following came from the javadocs at: |
| // http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/transaction/jta/JtaTransactionManager.html |
| private static final String[][] knownJNDIManagers = {{"java:/TransactionManager", "JBoss"}, |
| {"java:comp/TransactionManager", "Cosminexus"}, // and many others |
| {"java:appserver/TransactionManager", "GlassFish"}, {"java:pm/TransactionManager", "SunONE"}, |
| {"java:comp/UserTransaction", "Orion, JTOM, BEA WebLogic"}, |
| // not sure about the following but leaving it for backwards compat |
| {"javax.transaction.TransactionManager", "BEA WebLogic"}}; |
| /* ************************************************* */ |
| /** |
| * WebSphere 5.1 TransactionManagerFactory |
| */ |
| private static final String WS_FACTORY_CLASS_5_1 = |
| "com.ibm.ws.Transaction.TransactionManagerFactory"; |
| /** |
| * WebSphere 5.0 TransactionManagerFactory |
| */ |
| private static final String WS_FACTORY_CLASS_5_0 = |
| "com.ibm.ejs.jts.jta.TransactionManagerFactory"; |
| /** |
| * WebSphere 4.0 TransactionManagerFactory |
| */ |
| private static final String WS_FACTORY_CLASS_4 = "com.ibm.ejs.jts.jta.JTSXA"; |
| /** |
| * Maps data source name to the data source instance itself. |
| */ |
| @MakeNotStatic |
| private static final ConcurrentMap<String, Object> dataSourceMap = new ConcurrentHashMap<>(); |
| |
| /** |
| * If this system property is set to true, GemFire will not try to lookup for an existing JTA |
| * transaction manager bound to JNDI context or try to bind itself as a JTA transaction manager. |
| * Also region operations will <b>not</b> participate in an ongoing JTA transaction. |
| */ |
| @MutableForTesting |
| private static boolean IGNORE_JTA = |
| Boolean.getBoolean(GeodeGlossary.GEMFIRE_PREFIX + "ignoreJTA"); |
| |
| @Immutable |
| private static final DataSourceFactory dataSourceFactory = new DataSourceFactory(); |
| |
| /** |
| * Bind the transaction resources. Bind UserTransaction and TransactionManager. |
| * <p> |
| * If there is pre-existing JNDI tree in the system and TransactionManager / UserTransaction is |
| * already bound, GemFire will make use of these resources, but if TransactionManager / |
| * UserTransaction is not available, the GemFire TransactionManager / UserTransaction will be |
| * bound to the JNDI tree. |
| * </p> |
| * |
| */ |
| public static void mapTransactions(DistributedSystem distSystem) { |
| try { |
| TransactionUtils.setLogWriter(distSystem.getLogWriter()); |
| cleanup(); |
| if (IGNORE_JTA) { |
| return; |
| } |
| ctx = new InitialContext(); |
| doTransactionLookup(); |
| } catch (NamingException ne) { |
| LogWriter writer = TransactionUtils.getLogWriter(); |
| if (ne instanceof NoInitialContextException) { |
| String exception = |
| "JNDIInvoker::mapTransactions:: No application server context found, Starting GemFire JNDI Context Context "; |
| if (writer.finerEnabled()) |
| writer.finer(exception); |
| try { |
| initializeGemFireContext(); |
| transactionManager = TransactionManagerImpl.getTransactionManager(); |
| ctx.rebind("java:/TransactionManager", transactionManager); |
| if (writer.fineEnabled()) |
| writer.fine( |
| "JNDIInvoker::mapTransactions::Bound TransactionManager to Context GemFire JNDI Tree"); |
| UserTransactionImpl utx = new UserTransactionImpl(); |
| ctx.rebind("java:/UserTransaction", utx); |
| if (writer.fineEnabled()) |
| writer.fine( |
| "JNDIInvoker::mapTransactions::Bound Transaction to Context GemFire JNDI Tree"); |
| } catch (NamingException ne1) { |
| if (writer.infoEnabled()) |
| writer.info( |
| "JNDIInvoker::mapTransactions::NamingException while binding TransactionManager/UserTransaction to GemFire JNDI Tree"); |
| } catch (SystemException se1) { |
| if (writer.infoEnabled()) |
| writer.info( |
| "JNDIInvoker::mapTransactions::SystemException while binding UserTransaction to GemFire JNDI Tree"); |
| } |
| } else if (ne instanceof NameNotFoundException) { |
| String exception = |
| "JNDIInvoker::mapTransactions:: No TransactionManager associated to Application server context, trying to bind GemFire TransactionManager"; |
| if (writer.finerEnabled()) |
| writer.finer(exception); |
| try { |
| transactionManager = TransactionManagerImpl.getTransactionManager(); |
| ctx.rebind("java:/TransactionManager", transactionManager); |
| if (writer.fineEnabled()) |
| writer.fine( |
| "JNDIInvoker::mapTransactions::Bound TransactionManager to Application Server Context"); |
| UserTransactionImpl utx = new UserTransactionImpl(); |
| ctx.rebind("java:/UserTransaction", utx); |
| if (writer.fineEnabled()) |
| writer.fine( |
| "JNDIInvoker::mapTransactions::Bound UserTransaction to Application Server Context"); |
| } catch (NamingException ne1) { |
| if (writer.infoEnabled()) |
| writer.info( |
| "JNDIInvoker::mapTransactions::NamingException while binding TransactionManager/UserTransaction to Application Server JNDI Tree"); |
| } catch (SystemException se1) { |
| if (writer.infoEnabled()) |
| writer.info( |
| "JNDIInvoker::mapTransactions::SystemException while binding TransactionManager/UserTransaction to Application Server JNDI Tree"); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Cleans all the DataSource ans its associated threads gracefully, before start of the GemFire |
| * system. Also kills all of the threads associated with the TransactionManager before making it |
| * null. |
| */ |
| private static void cleanup() { |
| if (transactionManager instanceof TransactionManagerImpl) { |
| TransactionManagerImpl.refresh(); |
| // unbind and set TransactionManager to null, so as to |
| // initialize TXManager correctly if the cache is being restarted |
| transactionManager = null; |
| try { |
| if (ctx != null) { |
| ctx.unbind("java:/TransactionManager"); |
| } |
| } catch (NamingException e) { |
| // ok to ignore, rebind will be tried later |
| } |
| } |
| dataSourceMap.values().stream().forEach(JNDIInvoker::closeDataSource); |
| dataSourceMap.clear(); |
| IGNORE_JTA = Boolean.getBoolean(GeodeGlossary.GEMFIRE_PREFIX + "ignoreJTA"); |
| } |
| |
| private static void closeDataSource(Object dataSource) { |
| if (dataSource instanceof AutoCloseable) { |
| try { |
| ((AutoCloseable) dataSource).close(); |
| } catch (Exception e) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("Exception closing DataSource", e); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Helps in locating TransactionManager in presence of application server scenario. Stores the |
| * value of TransactionManager for reference in GemFire system. |
| */ |
| private static void doTransactionLookup() throws NamingException { |
| Object jndiObject = null; |
| LogWriter writer = TransactionUtils.getLogWriter(); |
| for (int i = 0; i < knownJNDIManagers.length; i++) { |
| try { |
| jndiObject = ctx.lookup(knownJNDIManagers[i][0]); |
| } catch (NamingException e) { |
| String exception = "JNDIInvoker::doTransactionLookup::Couldn't lookup [" |
| + knownJNDIManagers[i][0] + " (" + knownJNDIManagers[i][1] + ")]"; |
| if (writer.finerEnabled()) |
| writer.finer(exception); |
| } |
| if (jndiObject instanceof TransactionManager) { |
| transactionManager = (TransactionManager) jndiObject; |
| String exception = "JNDIInvoker::doTransactionLookup::Found TransactionManager for " |
| + knownJNDIManagers[i][1]; |
| if (writer.fineEnabled()) |
| writer.fine(exception); |
| return; |
| } else { |
| String exception = "JNDIInvoker::doTransactionLookup::Found TransactionManager of class " |
| + (jndiObject == null ? "null" : jndiObject.getClass()) |
| + " but is not of type javax.transaction.TransactionManager"; |
| if (writer.fineEnabled()) |
| writer.fine(exception); |
| } |
| } |
| Class clazz; |
| try { |
| if (writer.finerEnabled()) |
| writer.finer( |
| "JNDIInvoker::doTransactionLookup::Trying WebSphere 5.1: " + WS_FACTORY_CLASS_5_1); |
| clazz = ClassPathLoader.getLatest().forName(WS_FACTORY_CLASS_5_1); |
| if (writer.fineEnabled()) |
| writer |
| .fine("JNDIInvoker::doTransactionLookup::Found WebSphere 5.1: " + WS_FACTORY_CLASS_5_1); |
| } catch (ClassNotFoundException ex) { |
| try { |
| if (writer.finerEnabled()) |
| writer.finer( |
| "JNDIInvoker::doTransactionLookup::Trying WebSphere 5.0: " + WS_FACTORY_CLASS_5_0); |
| clazz = ClassPathLoader.getLatest().forName(WS_FACTORY_CLASS_5_0); |
| if (writer.fineEnabled()) |
| writer.fine( |
| "JNDIInvoker::doTransactionLookup::Found WebSphere 5.0: " + WS_FACTORY_CLASS_5_0); |
| } catch (ClassNotFoundException ex2) { |
| try { |
| clazz = ClassPathLoader.getLatest().forName(WS_FACTORY_CLASS_4); |
| String exception = |
| "JNDIInvoker::doTransactionLookup::Found WebSphere 4: " + WS_FACTORY_CLASS_4; |
| if (writer.fineEnabled()) |
| writer.fine(exception, ex); |
| } catch (ClassNotFoundException ex3) { |
| if (writer.finerEnabled()) |
| writer.finer( |
| "JNDIInvoker::doTransactionLookup::Couldn't find any WebSphere TransactionManager factory class, neither for WebSphere version 5.1 nor 5.0 nor 4"); |
| throw new NoInitialContextException(); |
| } |
| } |
| } |
| try { |
| Method method = clazz.getMethod("getTransactionManager", (Class[]) null); |
| transactionManager = (TransactionManager) method.invoke(null, (Object[]) null); |
| } catch (Exception ex) { |
| writer.warning( |
| String.format( |
| "JNDIInvoker::doTransactionLookup::Found WebSphere TransactionManager factory class [%s], but could not invoke its static 'getTransactionManager' method", |
| clazz.getName()), |
| ex); |
| throw new NameNotFoundException( |
| String.format( |
| "JNDIInvoker::doTransactionLookup::Found WebSphere TransactionManager factory class [%s], but could not invoke its static 'getTransactionManager' method", |
| new Object[] {clazz.getName()})); |
| } |
| } |
| |
| /** |
| * Initialises the GemFire context. This is called when no external JNDI Context is found. |
| * |
| */ |
| private static void initializeGemFireContext() throws NamingException { |
| Hashtable table = new Hashtable(); |
| table.put(Context.INITIAL_CONTEXT_FACTORY, |
| "org.apache.geode.internal.jndi.InitialContextFactoryImpl"); |
| ctx = new InitialContext(table); |
| } |
| |
| /** |
| * Binds a single Datasource to the existing JNDI tree. The JNDI tree may be The Datasource |
| * properties are contained in the map. The Datasource implementation class is populated based on |
| * properties in the map. |
| * |
| * @param map contains Datasource configuration properties. |
| */ |
| public static void mapDatasource(Map map, List<ConfigProperty> props) |
| throws NamingException, DataSourceCreateException, ClassNotFoundException, SQLException, |
| InstantiationException, IllegalAccessException { |
| mapDatasource(map, props, dataSourceFactory, ctx); |
| } |
| |
| static void mapDatasource(Map map, List<ConfigProperty> props, |
| DataSourceFactory dataSourceFactory, Context context) |
| throws NamingException, DataSourceCreateException, ClassNotFoundException, SQLException, |
| InstantiationException, IllegalAccessException { |
| String driverClassName = null; |
| if (map != null) { |
| driverClassName = (String) map.get("jdbc-driver-class"); |
| DriverJarUtil util = new DriverJarUtil(); |
| if (driverClassName != null) { |
| util.registerDriver(driverClassName); |
| } |
| } |
| |
| String value = (String) map.get("type"); |
| String jndiName = ""; |
| jndiName = (String) map.get("jndi-name"); |
| if (value.equals("PooledDataSource")) { |
| validateAndBindDataSource(context, jndiName, |
| dataSourceFactory.getPooledDataSource(map, props), props); |
| } else if (value.equals("XAPooledDataSource")) { |
| validateAndBindDataSource(context, jndiName, |
| dataSourceFactory.getTranxDataSource(map, props), props); |
| } else if (value.equals("SimpleDataSource")) { |
| validateAndBindDataSource(context, jndiName, dataSourceFactory.getSimpleDataSource(map), |
| props); |
| } else if (value.equals("ManagedDataSource")) { |
| ClientConnectionFactoryWrapper wrapper = dataSourceFactory.getManagedDataSource(map, props); |
| ctx.rebind("java:/" + jndiName, wrapper.getClientConnFactory()); |
| dataSourceMap.put(jndiName, wrapper); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Bound java:/" + jndiName + " to Context"); |
| } |
| } else { |
| String exception = "JNDIInvoker::mapDataSource::No correct type of DataSource"; |
| if (logger.isDebugEnabled()) { |
| logger.debug(exception); |
| } |
| throw new DataSourceCreateException(exception); |
| } |
| } |
| |
| private static void validateAndBindDataSource(Context context, String jndiName, |
| DataSource dataSource, List<ConfigProperty> props) |
| throws NamingException, DataSourceCreateException { |
| try (Connection connection = getConnection(dataSource, props)) { |
| } catch (SQLException sqlEx) { |
| closeDataSource(dataSource); |
| throw new DataSourceCreateException( |
| "Failed to connect to \"" + jndiName + "\". See log for details", sqlEx); |
| } |
| context.rebind("java:/" + jndiName, dataSource); |
| dataSourceMap.put(jndiName, dataSource); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Bound java:/" + jndiName + " to Context"); |
| } |
| } |
| |
| private static Connection getConnection(DataSource dataSource, List<ConfigProperty> props) |
| throws SQLException { |
| return dataSource.getConnection(); |
| } |
| |
| public static void unMapDatasource(String jndiName) throws NamingException { |
| ctx.unbind("java:/" + jndiName); |
| Object removedDataSource = dataSourceMap.remove(jndiName); |
| closeDataSource(removedDataSource); |
| } |
| |
| public static DataSource getDataSource(String name) { |
| Object result = dataSourceMap.get(name); |
| if (result instanceof DataSource) { |
| return (DataSource) result; |
| } else { |
| return null; |
| } |
| } |
| |
| public static boolean isValidDataSource(String name) { |
| Object dataSource = dataSourceMap.get(name); |
| |
| if (dataSource == null || (dataSource instanceof DataSource |
| && !(dataSource instanceof GemFireTransactionDataSource))) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @return Context the existing JNDI Context. If there is no pre-esisting JNDI Context, the |
| * GemFire JNDI Context is returned. |
| */ |
| public static Context getJNDIContext() { |
| return ctx; |
| } |
| |
| /** |
| * returns the GemFire TransactionManager. |
| * |
| */ |
| public static TransactionManager getTransactionManager() { |
| return transactionManager; |
| } |
| |
| public static int getNoOfAvailableDataSources() { |
| return dataSourceMap.size(); |
| } |
| |
| public static Map<String, String> getBindingNamesRecursively(Context ctx) throws Exception { |
| Map<String, String> result = new HashMap<>(); |
| NamingEnumeration<Binding> enumeration = ctx.listBindings(""); |
| |
| while (enumeration.hasMore()) { |
| Binding binding = enumeration.next(); |
| String name = binding.getName(); |
| String separator = name.endsWith(":") ? "" : "/"; |
| Object o = binding.getObject(); |
| if (o instanceof Context) { |
| Map<String, String> innerBindings = getBindingNamesRecursively((Context) o); |
| innerBindings.forEach((k, v) -> result.put(name + separator + k, v)); |
| } else { |
| result.put(name, binding.getClassName()); |
| } |
| } |
| |
| return result; |
| } |
| } |