1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.dbcp.datasources;
  17. import java.io.IOException;
  18. import java.io.ObjectInputStream;
  19. import java.sql.Connection;
  20. import java.sql.SQLException;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.Map;
  24. import javax.naming.NamingException;
  25. import javax.sql.ConnectionPoolDataSource;
  26. import org.apache.commons.pool.ObjectPool;
  27. import org.apache.commons.pool.impl.GenericObjectPool;
  28. import org.apache.commons.dbcp.SQLNestedException;
  29. /**
  30. * <p>
  31. * A pooling <code>DataSource</code> appropriate for deployment within
  32. * J2EE environment. There are many configuration options, most of which are
  33. * defined in the parent class. This datasource uses individual pools per
  34. * user, and some properties can be set specifically for a given user, if the
  35. * deployment environment can support initialization of mapped properties.
  36. * So for example, a pool of admin or write-access Connections can be
  37. * guaranteed a certain number of connections, separate from a maximum
  38. * set for users with read-only connections.
  39. * </p>
  40. *
  41. * @author John D. McNally
  42. * @version $Revision: 1.10 $ $Date: 2004/02/28 12:18:17 $
  43. */
  44. public class PerUserPoolDataSource
  45. extends InstanceKeyDataSource {
  46. private static final Map poolKeys = new HashMap();
  47. private int defaultMaxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
  48. private int defaultMaxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
  49. private int defaultMaxWait = (int)Math.min((long)Integer.MAX_VALUE,
  50. GenericObjectPool.DEFAULT_MAX_WAIT);
  51. Map perUserDefaultAutoCommit = null;
  52. Map perUserDefaultTransactionIsolation = null;
  53. Map perUserMaxActive = null;
  54. Map perUserMaxIdle = null;
  55. Map perUserMaxWait = null;
  56. Map perUserDefaultReadOnly = null;
  57. private transient Map pools = new HashMap();
  58. /**
  59. * Default no-arg constructor for Serialization
  60. */
  61. public PerUserPoolDataSource() {
  62. }
  63. /**
  64. * Close all pools in the given Map.
  65. */
  66. private static void close(Map poolMap) {
  67. }
  68. /**
  69. * Close pool(s) being maintained by this datasource.
  70. */
  71. public void close() {
  72. for (Iterator poolIter = pools.values().iterator();
  73. poolIter.hasNext();) {
  74. try {
  75. ((ObjectPool) poolIter.next()).close();
  76. } catch (Exception closePoolException) {
  77. //ignore and try to close others.
  78. }
  79. }
  80. InstanceKeyObjectFactory.removeInstance(instanceKey);
  81. }
  82. // -------------------------------------------------------------------
  83. // Properties
  84. /**
  85. * The maximum number of active connections that can be allocated from
  86. * this pool at the same time, or zero for no limit.
  87. * This value is used for any username which is not specified
  88. * in perUserMaxConnections. The default is 0.
  89. */
  90. public int getDefaultMaxActive() {
  91. return (this.defaultMaxActive);
  92. }
  93. /**
  94. * The maximum number of active connections that can be allocated from
  95. * this pool at the same time, or zero for no limit.
  96. * This value is used for any username which is not specified
  97. * in perUserMaxConnections. The default is 0.
  98. */
  99. public void setDefaultMaxActive(int maxActive) {
  100. assertInitializationAllowed();
  101. this.defaultMaxActive = maxActive;
  102. }
  103. /**
  104. * The maximum number of active connections that can remain idle in the
  105. * pool, without extra ones being released, or zero for no limit.
  106. * This value is used for any username which is not specified
  107. * in perUserMaxIdle. The default is 0.
  108. */
  109. public int getDefaultMaxIdle() {
  110. return (this.defaultMaxIdle);
  111. }
  112. /**
  113. * The maximum number of active connections that can remain idle in the
  114. * pool, without extra ones being released, or zero for no limit.
  115. * This value is used for any username which is not specified
  116. * in perUserMaxIdle. The default is 0.
  117. */
  118. public void setDefaultMaxIdle(int defaultMaxIdle) {
  119. assertInitializationAllowed();
  120. this.defaultMaxIdle = defaultMaxIdle;
  121. }
  122. /**
  123. * The maximum number of milliseconds that the pool will wait (when there
  124. * are no available connections) for a connection to be returned before
  125. * throwing an exception, or -1 to wait indefinitely. Will fail
  126. * immediately if value is 0.
  127. * This value is used for any username which is not specified
  128. * in perUserMaxWait. The default is -1.
  129. */
  130. public int getDefaultMaxWait() {
  131. return (this.defaultMaxWait);
  132. }
  133. /**
  134. * The maximum number of milliseconds that the pool will wait (when there
  135. * are no available connections) for a connection to be returned before
  136. * throwing an exception, or -1 to wait indefinitely. Will fail
  137. * immediately if value is 0.
  138. * This value is used for any username which is not specified
  139. * in perUserMaxWait. The default is -1.
  140. */
  141. public void setDefaultMaxWait(int defaultMaxWait) {
  142. assertInitializationAllowed();
  143. this.defaultMaxWait = defaultMaxWait;
  144. }
  145. /**
  146. * The keys are usernames and the value is the --. Any
  147. * username specified here will override the value of defaultAutoCommit.
  148. */
  149. public Boolean getPerUserDefaultAutoCommit(String key) {
  150. Boolean value = null;
  151. if (perUserDefaultAutoCommit != null) {
  152. value = (Boolean) perUserDefaultAutoCommit.get(key);
  153. }
  154. return value;
  155. }
  156. /**
  157. * The keys are usernames and the value is the --. Any
  158. * username specified here will override the value of defaultAutoCommit.
  159. */
  160. public void setPerUserDefaultAutoCommit(String username, Boolean value) {
  161. assertInitializationAllowed();
  162. if (perUserDefaultAutoCommit == null) {
  163. perUserDefaultAutoCommit = new HashMap();
  164. }
  165. perUserDefaultAutoCommit.put(username, value);
  166. }
  167. /**
  168. * The isolation level of connections when returned from getConnection.
  169. * If null, the username will use the value of defaultTransactionIsolation.
  170. */
  171. public Integer getPerUserDefaultTransactionIsolation(String username) {
  172. Integer value = null;
  173. if (perUserDefaultTransactionIsolation != null) {
  174. value = (Integer) perUserDefaultTransactionIsolation.get(username);
  175. }
  176. return value;
  177. }
  178. /**
  179. * The isolation level of connections when returned from getConnection.
  180. * Valid values are the constants defined in Connection.
  181. */
  182. public void setPerUserDefaultTransactionIsolation(String username,
  183. Integer value) {
  184. assertInitializationAllowed();
  185. if (perUserDefaultTransactionIsolation == null) {
  186. perUserDefaultTransactionIsolation = new HashMap();
  187. }
  188. perUserDefaultTransactionIsolation.put(username, value);
  189. }
  190. /**
  191. * The maximum number of active connections that can be allocated from
  192. * this pool at the same time, or zero for no limit.
  193. * The keys are usernames and the value is the maximum connections. Any
  194. * username specified here will override the value of defaultMaxActive.
  195. */
  196. public Integer getPerUserMaxActive(String username) {
  197. Integer value = null;
  198. if (perUserMaxActive != null) {
  199. value = (Integer) perUserMaxActive.get(username);
  200. }
  201. return value;
  202. }
  203. /**
  204. * The maximum number of active connections that can be allocated from
  205. * this pool at the same time, or zero for no limit.
  206. * The keys are usernames and the value is the maximum connections. Any
  207. * username specified here will override the value of defaultMaxActive.
  208. */
  209. public void setPerUserMaxActive(String username, Integer value) {
  210. assertInitializationAllowed();
  211. if (perUserMaxActive == null) {
  212. perUserMaxActive = new HashMap();
  213. }
  214. perUserMaxActive.put(username, value);
  215. }
  216. /**
  217. * The maximum number of active connections that can remain idle in the
  218. * pool, without extra ones being released, or zero for no limit.
  219. * The keys are usernames and the value is the maximum connections. Any
  220. * username specified here will override the value of defaultMaxIdle.
  221. */
  222. public Integer getPerUserMaxIdle(String username) {
  223. Integer value = null;
  224. if (perUserMaxIdle != null) {
  225. value = (Integer) perUserMaxIdle.get(username);
  226. }
  227. return value;
  228. }
  229. /**
  230. * The maximum number of active connections that can remain idle in the
  231. * pool, without extra ones being released, or zero for no limit.
  232. * The keys are usernames and the value is the maximum connections. Any
  233. * username specified here will override the value of defaultMaxIdle.
  234. */
  235. public void setPerUserMaxIdle(String username, Integer value) {
  236. assertInitializationAllowed();
  237. if (perUserMaxIdle == null) {
  238. perUserMaxIdle = new HashMap();
  239. }
  240. perUserMaxIdle.put(username, value);
  241. }
  242. /**
  243. * The maximum number of milliseconds that the pool will wait (when there
  244. * are no available connections) for a connection to be returned before
  245. * throwing an exception, or -1 to wait indefinitely. Will fail
  246. * immediately if value is 0.
  247. * The keys are usernames and the value is the maximum connections. Any
  248. * username specified here will override the value of defaultMaxWait.
  249. */
  250. public Integer getPerUserMaxWait(String username) {
  251. Integer value = null;
  252. if (perUserMaxWait != null) {
  253. value = (Integer) perUserMaxWait.get(username);
  254. }
  255. return value;
  256. }
  257. /**
  258. * The maximum number of milliseconds that the pool will wait (when there
  259. * are no available connections) for a connection to be returned before
  260. * throwing an exception, or -1 to wait indefinitely. Will fail
  261. * immediately if value is 0.
  262. * The keys are usernames and the value is the maximum connections. Any
  263. * username specified here will override the value of defaultMaxWait.
  264. */
  265. public void setPerUserMaxWait(String username, Integer value) {
  266. assertInitializationAllowed();
  267. if (perUserMaxWait == null) {
  268. perUserMaxWait = new HashMap();
  269. }
  270. perUserMaxWait.put(username, value);
  271. }
  272. /**
  273. * The keys are usernames and the value is the --. Any
  274. * username specified here will override the value of defaultReadOnly.
  275. */
  276. public Boolean getPerUserDefaultReadOnly(String username) {
  277. Boolean value = null;
  278. if (perUserDefaultReadOnly != null) {
  279. value = (Boolean) perUserDefaultReadOnly.get(username);
  280. }
  281. return value;
  282. }
  283. /**
  284. * The keys are usernames and the value is the --. Any
  285. * username specified here will override the value of defaultReadOnly.
  286. */
  287. public void setPerUserDefaultReadOnly(String username, Boolean value) {
  288. assertInitializationAllowed();
  289. if (perUserDefaultReadOnly == null) {
  290. perUserDefaultReadOnly = new HashMap();
  291. }
  292. perUserDefaultReadOnly.put(username, value);
  293. }
  294. // ----------------------------------------------------------------------
  295. // Instrumentation Methods
  296. /**
  297. * Get the number of active connections in the default pool.
  298. */
  299. public int getNumActive() {
  300. return getNumActive(null, null);
  301. }
  302. /**
  303. * Get the number of active connections in the pool for a given user.
  304. */
  305. public int getNumActive(String username, String password) {
  306. ObjectPool pool = (ObjectPool)pools.get(getPoolKey(username));
  307. return (pool == null) ? 0 : pool.getNumActive();
  308. }
  309. /**
  310. * Get the number of idle connections in the default pool.
  311. */
  312. public int getNumIdle() {
  313. return getNumIdle(null, null);
  314. }
  315. /**
  316. * Get the number of idle connections in the pool for a given user.
  317. */
  318. public int getNumIdle(String username, String password) {
  319. ObjectPool pool = (ObjectPool)pools.get(getPoolKey(username));
  320. return (pool == null) ? 0 : pool.getNumIdle();
  321. }
  322. // ----------------------------------------------------------------------
  323. // Inherited abstract methods
  324. protected synchronized PooledConnectionAndInfo
  325. getPooledConnectionAndInfo(String username, String password)
  326. throws SQLException {
  327. PoolKey key = getPoolKey(username);
  328. Object pool = pools.get(key);
  329. if (pool == null) {
  330. try {
  331. registerPool(username, password);
  332. pool = pools.get(key);
  333. } catch (NamingException e) {
  334. throw new SQLNestedException("RegisterPool failed", e);
  335. }
  336. }
  337. PooledConnectionAndInfo info = null;
  338. try {
  339. info = (PooledConnectionAndInfo)((ObjectPool) pool).borrowObject();
  340. }
  341. catch (Exception e) {
  342. throw new SQLNestedException(
  343. "Could not retrieve connection info from pool", e);
  344. }
  345. return info;
  346. }
  347. protected void setupDefaults(Connection con, String username)
  348. throws SQLException {
  349. boolean defaultAutoCommit = isDefaultAutoCommit();
  350. if (username != null) {
  351. Boolean userMax = getPerUserDefaultAutoCommit(username);
  352. if (userMax != null) {
  353. defaultAutoCommit = userMax.booleanValue();
  354. }
  355. }
  356. boolean defaultReadOnly = isDefaultReadOnly();
  357. if (username != null) {
  358. Boolean userMax = getPerUserDefaultReadOnly(username);
  359. if (userMax != null) {
  360. defaultReadOnly = userMax.booleanValue();
  361. }
  362. }
  363. int defaultTransactionIsolation = getDefaultTransactionIsolation();
  364. if (username != null) {
  365. Integer userMax = getPerUserDefaultTransactionIsolation(username);
  366. if (userMax != null) {
  367. defaultTransactionIsolation = userMax.intValue();
  368. }
  369. }
  370. con.setAutoCommit(defaultAutoCommit);
  371. con.setReadOnly(defaultReadOnly);
  372. if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
  373. con.setTransactionIsolation(defaultTransactionIsolation);
  374. }
  375. }
  376. private PoolKey getPoolKey(String username) {
  377. PoolKey key = null;
  378. String dsName = getDataSourceName();
  379. Map dsMap = (Map) poolKeys.get(dsName);
  380. if (dsMap != null) {
  381. key = (PoolKey) dsMap.get(username);
  382. }
  383. if (key == null) {
  384. key = new PoolKey(dsName, username);
  385. if (dsMap == null) {
  386. dsMap = new HashMap();
  387. poolKeys.put(dsName, dsMap);
  388. }
  389. dsMap.put(username, key);
  390. }
  391. return key;
  392. }
  393. private synchronized void registerPool(
  394. String username, String password)
  395. throws javax.naming.NamingException, SQLException {
  396. ConnectionPoolDataSource cpds = testCPDS(username, password);
  397. Integer userMax = getPerUserMaxActive(username);
  398. int maxActive = (userMax == null) ?
  399. getDefaultMaxActive() : userMax.intValue();
  400. userMax = getPerUserMaxIdle(username);
  401. int maxIdle = (userMax == null) ?
  402. getDefaultMaxIdle() : userMax.intValue();
  403. userMax = getPerUserMaxWait(username);
  404. int maxWait = (userMax == null) ?
  405. getDefaultMaxWait() : userMax.intValue();
  406. // Create an object pool to contain our PooledConnections
  407. GenericObjectPool pool = new GenericObjectPool(null);
  408. pool.setMaxActive(maxActive);
  409. pool.setMaxIdle(maxIdle);
  410. pool.setMaxWait(maxWait);
  411. pool.setWhenExhaustedAction(whenExhaustedAction(maxActive, maxWait));
  412. pool.setTestOnBorrow(getTestOnBorrow());
  413. pool.setTestOnReturn(getTestOnReturn());
  414. pool.setTimeBetweenEvictionRunsMillis(
  415. getTimeBetweenEvictionRunsMillis());
  416. pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
  417. pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
  418. pool.setTestWhileIdle(getTestWhileIdle());
  419. // Set up the factory we will use (passing the pool associates
  420. // the factory with the pool, so we do not have to do so
  421. // explicitly)
  422. new CPDSConnectionFactory(cpds, pool, getValidationQuery(),
  423. username, password);
  424. pools.put(getPoolKey(username), pool);
  425. }
  426. /**
  427. * Supports Serialization interface.
  428. *
  429. * @param in a <code>java.io.ObjectInputStream</code> value
  430. * @exception IOException if an error occurs
  431. * @exception ClassNotFoundException if an error occurs
  432. */
  433. private void readObject(ObjectInputStream in)
  434. throws IOException, ClassNotFoundException {
  435. try
  436. {
  437. in.defaultReadObject();
  438. PerUserPoolDataSource oldDS = (PerUserPoolDataSource)
  439. new PerUserPoolDataSourceFactory()
  440. .getObjectInstance(getReference(), null, null, null);
  441. this.pools = oldDS.pools;
  442. }
  443. catch (NamingException e)
  444. {
  445. throw new IOException("NamingException: " + e);
  446. }
  447. }
  448. }