001/* 002 * (C) Copyright 2012-2020 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.runtime.datasource; 020 021import static org.apache.commons.lang3.StringUtils.defaultString; 022 023import java.lang.reflect.InvocationTargetException; 024import java.sql.SQLException; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.concurrent.ConcurrentHashMap; 028 029import javax.naming.NamingException; 030import javax.sql.DataSource; 031import javax.sql.XADataSource; 032import javax.transaction.Status; 033import javax.transaction.Transaction; 034import javax.transaction.TransactionManager; 035import javax.transaction.TransactionSynchronizationRegistry; 036 037import org.apache.commons.beanutils.BeanUtils; 038import org.apache.commons.dbcp2.ConnectionFactory; 039import org.apache.commons.dbcp2.managed.BasicManagedDataSource; 040import org.apache.logging.log4j.LogManager; 041import org.apache.logging.log4j.Logger; 042import org.nuxeo.runtime.RuntimeServiceException; 043import org.nuxeo.runtime.transaction.TransactionHelper; 044 045public class PooledDataSourceRegistry { 046 047 protected final Map<String, DataSource> dataSources = new ConcurrentHashMap<>(); 048 049 protected final Map<String, DataSource> dataSourcesNoSharing = new ConcurrentHashMap<>(); 050 051 public <T> T getDataSource(String name, Class<T> type, boolean noSharing) { 052 Map<String, DataSource> map = noSharing ? dataSourcesNoSharing : dataSources; 053 return type.cast(map.get(name)); 054 } 055 056 public void registerPooledDataSource(String name, Map<String, String> properties) { 057 dataSources.computeIfAbsent(name, k -> createPooledDataSource(properties, false)); 058 dataSourcesNoSharing.computeIfAbsent(name, k -> createPooledDataSource(properties, true)); 059 } 060 061 /** 062 * A {@link BasicManagedDataSource} that can configure its internal {@link XADataSource}. 063 * 064 * @since 11.1 065 */ 066 public static class ConfigurableManagedDataSource extends BasicManagedDataSource { 067 068 private static final Logger log = LogManager.getLogger(ConfigurableManagedDataSource.class); 069 070 protected final Map<String, String> properties; 071 072 public ConfigurableManagedDataSource(Map<String, String> properties) { 073 this.properties = properties; 074 } 075 076 @Override 077 protected ConnectionFactory createConnectionFactory() throws SQLException { 078 ConnectionFactory connectionFactory = super.createConnectionFactory(); 079 configureXADataSource(getXaDataSourceInstance()); 080 return connectionFactory; 081 } 082 083 // initialize the XADataSource through JavaBeans 084 protected void configureXADataSource(XADataSource xaDataSource) { 085 if (xaDataSource != null) { 086 try { 087 BeanUtils.populate(xaDataSource, properties); 088 } catch (IllegalAccessException | InvocationTargetException e) { 089 log.error(e, e); 090 } 091 } 092 } 093 } 094 095 public BasicManagedDataSource createPooledDataSource(Map<String, String> properties, boolean noSharing) { 096 // compatibility with previous Geronimo Connection configuration 097 properties.computeIfAbsent("minTotal", k -> properties.get("minPoolSize")); 098 properties.computeIfAbsent("maxTotal", k -> properties.get("maxPoolSize")); 099 properties.computeIfAbsent("maxWaitMillis", k -> properties.get("blockingTimeoutMillis")); 100 // compatibility aliases for username 101 properties.computeIfAbsent("username", k -> defaultString( // 102 properties.get("user"), // 103 properties.get("User"))); 104 // JavaBeans sucks 105 properties.computeIfAbsent("XADataSource", k -> properties.get("xaDataSource")); 106 properties.computeIfAbsent("URL", k -> properties.get("url")); 107 108 BasicManagedDataSource ds = new ConfigurableManagedDataSource(properties); 109 110 // properties for which the DBCP default is not suitable 111 ds.setMaxWaitMillis(1000); 112 ds.setAccessToUnderlyingConnectionAllowed(true); 113 114 // populate datasource via JavaBeans properties 115 try { 116 BeanUtils.populate(ds, properties); 117 } catch (IllegalAccessException | InvocationTargetException e) { 118 throw new RuntimeServiceException(e); 119 } 120 // populate connection properties 121 properties.forEach(ds::addConnectionProperty); 122 123 // transaction management 124 TransactionManager transactionManager; 125 TransactionSynchronizationRegistry transactionSynchronizationRegistry; 126 if (noSharing) { 127 // pretend we never have a transaction 128 transactionManager = new TransactionManagerWithoutTransaction(); 129 transactionSynchronizationRegistry = null; 130 } else { 131 try { 132 transactionManager = TransactionHelper.lookupTransactionManager(); 133 transactionSynchronizationRegistry = TransactionHelper.lookupSynchronizationRegistry(); 134 } catch (NamingException e) { 135 throw new RuntimeServiceException(e); 136 } 137 } 138 ds.setTransactionManager(transactionManager); 139 ds.setTransactionSynchronizationRegistry(transactionSynchronizationRegistry); 140 141 return ds; 142 } 143 144 protected void unregisterPooledDataSource(String name) { 145 dataSources.remove(name); 146 dataSourcesNoSharing.remove(name); 147 } 148 149 public void createAlias(String name, DataSource ds) { 150 // alias noSharing version too 151 for (Entry<String, DataSource> es : dataSources.entrySet()) { 152 if (es.getValue() == ds) { 153 DataSource noSharingDs = dataSourcesNoSharing.get(es.getKey()); 154 if (noSharingDs != null) { 155 dataSourcesNoSharing.put(name, noSharingDs); 156 } 157 break; 158 } 159 } 160 dataSources.put(name, ds); 161 } 162 163 public void removeAlias(String name) { 164 unregisterPooledDataSource(name); 165 } 166 167 /** 168 * Transaction Manager that is never in a transaction and doesn't allow starting one. 169 * 170 * @since 11.1 171 */ 172 public static class TransactionManagerWithoutTransaction implements TransactionManager { 173 174 @Override 175 public Transaction getTransaction() { 176 return null; 177 } 178 179 @Override 180 public int getStatus() { 181 return Status.STATUS_NO_TRANSACTION; 182 } 183 184 @Override 185 public void setTransactionTimeout(int seconds) { 186 // nothing 187 } 188 189 @Override 190 public void begin() { 191 throw new UnsupportedOperationException(); 192 } 193 194 @Override 195 public void commit() { 196 throw new UnsupportedOperationException(); 197 } 198 199 @Override 200 public void rollback() { 201 throw new UnsupportedOperationException(); 202 } 203 204 @Override 205 public void resume(Transaction transaction) { 206 throw new UnsupportedOperationException(); 207 } 208 209 @Override 210 public void setRollbackOnly() { 211 throw new UnsupportedOperationException(); 212 } 213 214 @Override 215 public Transaction suspend() { 216 throw new UnsupportedOperationException(); 217 } 218 } 219 220}