001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jmeter.protocol.jdbc.config;
018:
019: import java.sql.Connection;
020: import java.sql.SQLException;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Set;
027:
028: import org.apache.avalon.excalibur.datasource.DataSourceComponent;
029: import org.apache.avalon.excalibur.datasource.ResourceLimitingJdbcDataSource;
030: import org.apache.avalon.framework.configuration.Configuration;
031: import org.apache.avalon.framework.configuration.ConfigurationException;
032: import org.apache.avalon.framework.configuration.DefaultConfiguration;
033: import org.apache.avalon.framework.logger.LogKitLogger;
034: import org.apache.jmeter.config.ConfigElement;
035: import org.apache.jmeter.engine.event.LoopIterationEvent;
036: import org.apache.jmeter.testbeans.TestBean;
037: import org.apache.jmeter.testbeans.TestBeanHelper;
038: import org.apache.jmeter.testelement.AbstractTestElement;
039: import org.apache.jmeter.testelement.TestListener;
040: import org.apache.jmeter.threads.JMeterContextService;
041: import org.apache.jmeter.threads.JMeterVariables;
042: import org.apache.jorphan.logging.LoggingManager;
043: import org.apache.log.Logger;
044:
045: /**
046: * @author Michael Stover
047: *
048: */
049: public class DataSourceElement extends AbstractTestElement implements
050: ConfigElement, TestListener, TestBean {
051: private static final Logger log = LoggingManager
052: .getLoggerForClass();
053:
054: transient String dataSource, driver, dbUrl, username, password,
055: checkQuery, poolMax, connectionAge, timeout, trimInterval;
056:
057: transient boolean keepAlive, autocommit;
058:
059: /*
060: * The datasource is set up by testStarted and cleared by testEnded.
061: * These are called from different threads, so access must be synchronized.
062: * The same instance is called in each case.
063: */
064: transient ResourceLimitingJdbcDataSource excaliburSource;
065:
066: // Keep a record of the pre-thread pools so that they can be disposed of at the end of a test
067: private transient Set perThreadPoolSet = Collections
068: .synchronizedSet(new HashSet());
069:
070: public DataSourceElement() {
071: }
072:
073: public void testEnded() {
074: synchronized (this ) {
075: if (excaliburSource != null) {
076: excaliburSource.dispose();
077: }
078: excaliburSource = null;
079: }
080: if (perThreadPoolSet != null) {// in case
081: Iterator it = perThreadPoolSet.iterator();
082: while (it.hasNext()) {
083: ResourceLimitingJdbcDataSource dsc = (ResourceLimitingJdbcDataSource) it
084: .next();
085: log.debug("Disposing pool: "
086: + dsc.getInstrumentableName() + " @"
087: + System.identityHashCode(dsc));
088: dsc.dispose();
089: }
090: perThreadPoolSet.clear();
091: }
092: }
093:
094: public void testEnded(String host) {
095: testEnded();
096: }
097:
098: public void testIterationStart(LoopIterationEvent event) {
099: }
100:
101: public void testStarted() {
102: this .setRunningVersion(true);
103: TestBeanHelper.prepare(this );
104: JMeterVariables variables = getThreadContext().getVariables();
105: String poolName = getDataSource();
106: if (variables.getObject(poolName) != null) {
107: log.error("JDBC data source already defined for: "
108: + poolName);
109: } else {
110: String maxPool = getPoolMax();
111: if (maxPool.equals("0")) { // i.e. if we want per thread pooling
112: variables.putObject(poolName,
113: new DataSourceComponentImpl()); // pool will be created later
114: } else {
115: ResourceLimitingJdbcDataSource src = initPool(maxPool);
116: synchronized (this ) {
117: excaliburSource = src;
118: variables
119: .putObject(poolName,
120: new DataSourceComponentImpl(
121: excaliburSource));
122: }
123: }
124: }
125: }
126:
127: public void testStarted(String host) {
128: testStarted();
129: }
130:
131: public Object clone() {
132: DataSourceElement el = (DataSourceElement) super .clone();
133: el.excaliburSource = excaliburSource;
134: el.perThreadPoolSet = perThreadPoolSet;
135: return el;
136: }
137:
138: /*
139: * Utility routine to get the connection from the pool.
140: * Purpose:
141: * - allows JDBCSampler to be entirely independent of the pooling classes
142: * - allows the pool storage mechanism to be changed if necessary
143: */
144: public static Connection getConnection(String poolName)
145: throws SQLException {
146: DataSourceComponent pool = (DataSourceComponent) JMeterContextService
147: .getContext().getVariables().getObject(poolName);
148: if (pool == null) {
149: throw new SQLException("No pool found named: '" + poolName
150: + "'");
151: }
152: return pool.getConnection();
153: }
154:
155: /*
156: * Set up the DataSource - maxPool is a parameter, so the same code can
157: * also be used for setting up the per-thread pools.
158: */
159: private ResourceLimitingJdbcDataSource initPool(String maxPool) {
160: ResourceLimitingJdbcDataSource source = null;
161: source = new ResourceLimitingJdbcDataSource();
162: DefaultConfiguration config = new DefaultConfiguration(
163: "rl-jdbc"); // $NON-NLS-1$
164:
165: if (log.isDebugEnabled()) {
166: StringBuffer sb = new StringBuffer(40);
167: sb.append("MaxPool: ");
168: sb.append(maxPool);
169: sb.append(" Timeout: ");
170: sb.append(getTimeout());
171: sb.append(" TrimInt: ");
172: sb.append(getTrimInterval());
173: sb.append(" Auto-Commit: ");
174: sb.append(isAutocommit());
175: log.debug(sb.toString());
176: }
177: DefaultConfiguration poolController = new DefaultConfiguration(
178: "pool-controller"); // $NON-NLS-1$
179: poolController.setAttribute("max", maxPool); // $NON-NLS-1$
180: poolController.setAttribute("max-strict", "true"); // $NON-NLS-1$ $NON-NLS-2$
181: poolController.setAttribute("blocking", "true"); // $NON-NLS-1$ $NON-NLS-2$
182: poolController.setAttribute("timeout", getTimeout()); // $NON-NLS-1$
183: poolController.setAttribute("trim-interval", getTrimInterval()); // $NON-NLS-1$
184: config.addChild(poolController);
185:
186: DefaultConfiguration autoCommit = new DefaultConfiguration(
187: "auto-commit"); // $NON-NLS-1$
188: autoCommit.setValue(String.valueOf(isAutocommit()));
189: config.addChild(autoCommit);
190:
191: if (log.isDebugEnabled()) {
192: StringBuffer sb = new StringBuffer(40);
193: sb.append("KeepAlive: ");
194: sb.append(isKeepAlive());
195: sb.append(" Age: ");
196: sb.append(getConnectionAge());
197: sb.append(" CheckQuery: ");
198: sb.append(getCheckQuery());
199: log.debug(sb.toString());
200: }
201: DefaultConfiguration cfgKeepAlive = new DefaultConfiguration(
202: "keep-alive"); // $NON-NLS-1$
203: cfgKeepAlive.setAttribute("disable", String
204: .valueOf(!isKeepAlive())); // $NON-NLS-1$
205: cfgKeepAlive.setAttribute("age", getConnectionAge()); // $NON-NLS-1$
206: cfgKeepAlive.setValue(getCheckQuery());
207: poolController.addChild(cfgKeepAlive);
208:
209: String _username = getUsername();
210: if (log.isDebugEnabled()) {
211: StringBuffer sb = new StringBuffer(40);
212: sb.append("Driver: ");
213: sb.append(getDriver());
214: sb.append(" DbUrl: ");
215: sb.append(getDbUrl());
216: sb.append(" User: ");
217: sb.append(_username);
218: log.debug(sb.toString());
219: }
220: DefaultConfiguration cfgDriver = new DefaultConfiguration(
221: "driver"); // $NON-NLS-1$
222: cfgDriver.setValue(getDriver());
223: config.addChild(cfgDriver);
224: DefaultConfiguration cfgDbUrl = new DefaultConfiguration(
225: "dburl"); // $NON-NLS-1$
226: cfgDbUrl.setValue(getDbUrl());
227: config.addChild(cfgDbUrl);
228:
229: if (_username.length() > 0) {
230: DefaultConfiguration cfgUsername = new DefaultConfiguration(
231: "user"); // $NON-NLS-1$
232: cfgUsername.setValue(_username);
233: config.addChild(cfgUsername);
234: DefaultConfiguration cfgPassword = new DefaultConfiguration(
235: "password"); // $NON-NLS-1$
236: cfgPassword.setValue(getPassword());
237: config.addChild(cfgPassword);
238: }
239:
240: // log is required to ensure errors are available
241: source.enableLogging(new LogKitLogger(log));
242: try {
243: source.configure(config);
244: source.setInstrumentableName(getDataSource());
245: } catch (ConfigurationException e) {
246: log.error("Could not configure datasource for pool: "
247: + getDataSource(), e);
248: }
249: return source;
250: }
251:
252: // used to hold per-thread singleton connection pools
253: private static ThreadLocal perThreadPoolMap = new ThreadLocal() {
254: protected synchronized Object initialValue() {
255: return new HashMap();
256: }
257: };
258:
259: /*
260: * Wrapper class to allow getConnection() to be implemented for both shared
261: * and per-thread pools.
262: *
263: */
264: private class DataSourceComponentImpl implements
265: DataSourceComponent {
266:
267: private final ResourceLimitingJdbcDataSource sharedDSC;
268:
269: DataSourceComponentImpl() {
270: sharedDSC = null;
271: }
272:
273: DataSourceComponentImpl(ResourceLimitingJdbcDataSource p_dsc) {
274: sharedDSC = p_dsc;
275: }
276:
277: public Connection getConnection() throws SQLException {
278: Connection conn = null;
279: ResourceLimitingJdbcDataSource dsc = null;
280: if (sharedDSC != null) { // i.e. shared pool
281: dsc = sharedDSC;
282: } else {
283: Map poolMap = (Map) perThreadPoolMap.get();
284: dsc = (ResourceLimitingJdbcDataSource) poolMap
285: .get(getDataSource());
286: if (dsc == null) {
287: dsc = initPool("1");
288: poolMap.put(getDataSource(), dsc);
289: log.debug("Storing pool: "
290: + dsc.getInstrumentableName() + " @"
291: + System.identityHashCode(dsc));
292: perThreadPoolSet.add(dsc);
293: }
294: }
295: if (dsc != null) {
296: conn = dsc.getConnection();
297: }
298: return conn;
299: }
300:
301: public void configure(Configuration arg0)
302: throws ConfigurationException {
303: }
304:
305: }
306:
307: public void addConfigElement(ConfigElement config) {
308: }
309:
310: public boolean expectsModification() {
311: return false;
312: }
313:
314: /**
315: * @return Returns the checkQuery.
316: */
317: public String getCheckQuery() {
318: return checkQuery;
319: }
320:
321: /**
322: * @param checkQuery
323: * The checkQuery to set.
324: */
325: public void setCheckQuery(String checkQuery) {
326: this .checkQuery = checkQuery;
327: }
328:
329: /**
330: * @return Returns the connectionAge.
331: */
332: public String getConnectionAge() {
333: return connectionAge;
334: }
335:
336: /**
337: * @param connectionAge
338: * The connectionAge to set.
339: */
340: public void setConnectionAge(String connectionAge) {
341: this .connectionAge = connectionAge;
342: }
343:
344: /**
345: * @return Returns the poolname.
346: */
347: public String getDataSource() {
348: return dataSource;
349: }
350:
351: /**
352: * @param dataSource
353: * The poolname to set.
354: */
355: public void setDataSource(String dataSource) {
356: this .dataSource = dataSource;
357: }
358:
359: /**
360: * @return Returns the dbUrl.
361: */
362: public String getDbUrl() {
363: return dbUrl;
364: }
365:
366: /**
367: * @param dbUrl
368: * The dbUrl to set.
369: */
370: public void setDbUrl(String dbUrl) {
371: this .dbUrl = dbUrl;
372: }
373:
374: /**
375: * @return Returns the driver.
376: */
377: public String getDriver() {
378: return driver;
379: }
380:
381: /**
382: * @param driver
383: * The driver to set.
384: */
385: public void setDriver(String driver) {
386: this .driver = driver;
387: }
388:
389: /**
390: * @return Returns the password.
391: */
392: public String getPassword() {
393: return password;
394: }
395:
396: /**
397: * @param password
398: * The password to set.
399: */
400: public void setPassword(String password) {
401: this .password = password;
402: }
403:
404: /**
405: * @return Returns the poolMax.
406: */
407: public String getPoolMax() {
408: return poolMax;
409: }
410:
411: /**
412: * @param poolMax
413: * The poolMax to set.
414: */
415: public void setPoolMax(String poolMax) {
416: this .poolMax = poolMax;
417: }
418:
419: /**
420: * @return Returns the timeout.
421: */
422: public String getTimeout() {
423: return timeout;
424: }
425:
426: /**
427: * @param timeout
428: * The timeout to set.
429: */
430: public void setTimeout(String timeout) {
431: this .timeout = timeout;
432: }
433:
434: /**
435: * @return Returns the trimInterval.
436: */
437: public String getTrimInterval() {
438: return trimInterval;
439: }
440:
441: /**
442: * @param trimInterval
443: * The trimInterval to set.
444: */
445: public void setTrimInterval(String trimInterval) {
446: this .trimInterval = trimInterval;
447: }
448:
449: /**
450: * @return Returns the username.
451: */
452: public String getUsername() {
453: return username;
454: }
455:
456: /**
457: * @param username
458: * The username to set.
459: */
460: public void setUsername(String username) {
461: this .username = username;
462: }
463:
464: /**
465: * @return Returns the autocommit.
466: */
467: public boolean isAutocommit() {
468: return autocommit;
469: }
470:
471: /**
472: * @param autocommit
473: * The autocommit to set.
474: */
475: public void setAutocommit(boolean autocommit) {
476: this .autocommit = autocommit;
477: }
478:
479: /**
480: * @return Returns the keepAlive.
481: */
482: public boolean isKeepAlive() {
483: return keepAlive;
484: }
485:
486: /**
487: * @param keepAlive
488: * The keepAlive to set.
489: */
490: public void setKeepAlive(boolean keepAlive) {
491: this.keepAlive = keepAlive;
492: }
493: }
|