001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.resource.adapter.jdbc;
023:
024: import java.io.PrintWriter;
025: import java.io.Serializable;
026: import java.security.AccessController;
027: import java.security.PrivilegedAction;
028: import java.sql.Connection;
029: import java.sql.SQLException;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.Properties;
033: import java.util.Set;
034:
035: import javax.resource.ResourceException;
036: import javax.resource.spi.ConnectionManager;
037: import javax.resource.spi.ConnectionRequestInfo;
038: import javax.resource.spi.ManagedConnectionFactory;
039: import javax.resource.spi.ValidatingManagedConnectionFactory;
040: import javax.resource.spi.security.PasswordCredential;
041: import javax.security.auth.Subject;
042:
043: import org.jboss.logging.Logger;
044: import org.jboss.resource.JBossResourceException;
045:
046: /**
047: * BaseWrapperManagedConnectionFactory
048: *
049: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
050: * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
051: * @author <a href="weston.price@jboss.com">Weston Price</a>
052: * @version $Revision: 57189 $
053: */
054:
055: public abstract class BaseWrapperManagedConnectionFactory implements
056: ManagedConnectionFactory, ValidatingManagedConnectionFactory,
057: Serializable {
058: /** @since 4.0.1 */
059: static final long serialVersionUID = -84923705377702088L;
060:
061: public static final int TRACK_STATEMENTS_FALSE_INT = 0;
062: public static final int TRACK_STATEMENTS_TRUE_INT = 1;
063: public static final int TRACK_STATEMENTS_NOWARN_INT = 2;
064:
065: public static final String TRACK_STATEMENTS_FALSE = "false";
066: public static final String TRACK_STATEMENTS_TRUE = "true";
067: public static final String TRACK_STATEMENTS_NOWARN = "nowarn";
068:
069: protected final Logger log = Logger.getLogger(getClass());
070:
071: protected String userName;
072: protected String password;
073:
074: //This is used by Local wrapper for all properties, and is left
075: //in this class for ease of writing getConnectionProperties,
076: //which always holds the user/pw.
077: protected final Properties connectionProps = new Properties();
078:
079: protected int transactionIsolation = -1;
080:
081: protected int preparedStatementCacheSize = 0;
082:
083: protected boolean doQueryTimeout = false;
084:
085: /**
086: * The variable <code>newConnectionSQL</code> holds an SQL
087: * statement which if not null is executed when a new Connection is
088: * obtained for a new ManagedConnection.
089: */
090: protected String newConnectionSQL;
091:
092: /**
093: * The variable <code>checkValidConnectionSQL</code> holds an sql
094: * statement that may be executed whenever a managed connection is
095: * removed from the pool, to check that it is still valid. This
096: * requires setting up an mbean to execute it when notified by the
097: * ConnectionManager.
098: */
099: protected String checkValidConnectionSQL;
100:
101: /**
102: * The classname used to check whether a connection is valid
103: */
104: protected String validConnectionCheckerClassName;
105:
106: /**
107: * The instance of the valid connection checker
108: */
109: protected ValidConnectionChecker connectionChecker;
110:
111: private String exceptionSorterClassName;
112:
113: private ExceptionSorter exceptionSorter;
114:
115: protected int trackStatements = TRACK_STATEMENTS_NOWARN_INT;
116:
117: /** Whether to share cached prepared statements */
118: protected boolean sharePS = false;
119:
120: protected boolean isTransactionQueryTimeout = false;
121:
122: protected int queryTimeout = 0;
123:
124: private boolean validateOnMatch;
125:
126: public BaseWrapperManagedConnectionFactory() {
127:
128: }
129:
130: public PrintWriter getLogWriter() throws ResourceException {
131: // TODO: implement this javax.resource.spi.ManagedConnectionFactory method
132: return null;
133: }
134:
135: public void setLogWriter(PrintWriter param1)
136: throws ResourceException {
137: // TODO: implement this javax.resource.spi.ManagedConnectionFactory method
138: }
139:
140: public Object createConnectionFactory(ConnectionManager cm)
141: throws ResourceException {
142: return new WrapperDataSource(this , cm);
143: }
144:
145: public Object createConnectionFactory() throws ResourceException {
146: throw new JBossResourceException("NYI");
147: }
148:
149: public String getUserName() {
150: return userName;
151: }
152:
153: public void setUserName(final String userName) {
154: this .userName = userName;
155: }
156:
157: public String getPassword() {
158: return password;
159: }
160:
161: public void setPassword(final String password) {
162: this .password = password;
163: }
164:
165: public int getPreparedStatementCacheSize() {
166: return preparedStatementCacheSize;
167: }
168:
169: public void setPreparedStatementCacheSize(int size) {
170: preparedStatementCacheSize = size;
171: }
172:
173: public boolean getSharePreparedStatements() {
174: return sharePS;
175: }
176:
177: public void setSharePreparedStatements(boolean sharePS) {
178: this .sharePS = sharePS;
179: }
180:
181: public String getTransactionIsolation() {
182: switch (this .transactionIsolation) {
183: case Connection.TRANSACTION_NONE:
184: return "TRANSACTION_NONE";
185: case Connection.TRANSACTION_READ_COMMITTED:
186: return "TRANSACTION_READ_COMMITTED";
187: case Connection.TRANSACTION_READ_UNCOMMITTED:
188: return "TRANSACTION_READ_UNCOMMITTED";
189: case Connection.TRANSACTION_REPEATABLE_READ:
190: return "TRANSACTION_REPEATABLE_READ";
191: case Connection.TRANSACTION_SERIALIZABLE:
192: return "TRANSACTION_SERIALIZABLE";
193: case -1:
194: return "DEFAULT";
195: default:
196: return Integer.toString(transactionIsolation);
197: }
198: }
199:
200: public void setTransactionIsolation(String transactionIsolation) {
201: if (transactionIsolation.equals("TRANSACTION_NONE"))
202: this .transactionIsolation = Connection.TRANSACTION_NONE;
203: else if (transactionIsolation
204: .equals("TRANSACTION_READ_COMMITTED"))
205: this .transactionIsolation = Connection.TRANSACTION_READ_COMMITTED;
206: else if (transactionIsolation
207: .equals("TRANSACTION_READ_UNCOMMITTED"))
208: this .transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED;
209: else if (transactionIsolation
210: .equals("TRANSACTION_REPEATABLE_READ"))
211: this .transactionIsolation = Connection.TRANSACTION_REPEATABLE_READ;
212: else if (transactionIsolation
213: .equals("TRANSACTION_SERIALIZABLE"))
214: this .transactionIsolation = Connection.TRANSACTION_SERIALIZABLE;
215: else {
216: try {
217: this .transactionIsolation = Integer
218: .parseInt(transactionIsolation);
219: } catch (NumberFormatException nfe) {
220: throw new IllegalArgumentException(
221: "Setting Isolation level to unknown state: "
222: + transactionIsolation);
223: }
224: }
225: }
226:
227: public String getNewConnectionSQL() {
228: return newConnectionSQL;
229: }
230:
231: public void setNewConnectionSQL(String newConnectionSQL) {
232: this .newConnectionSQL = newConnectionSQL;
233: }
234:
235: public String getCheckValidConnectionSQL() {
236: return checkValidConnectionSQL;
237: }
238:
239: public void setCheckValidConnectionSQL(
240: String checkValidConnectionSQL) {
241: this .checkValidConnectionSQL = checkValidConnectionSQL;
242: }
243:
244: public String getTrackStatements() {
245: if (trackStatements == TRACK_STATEMENTS_FALSE_INT)
246: return TRACK_STATEMENTS_FALSE;
247: else if (trackStatements == TRACK_STATEMENTS_TRUE_INT)
248: return TRACK_STATEMENTS_TRUE;
249: return TRACK_STATEMENTS_NOWARN;
250: }
251:
252: public boolean getValidateOnMatch() {
253:
254: return this .validateOnMatch;
255:
256: }
257:
258: public void setValidateOnMatch(boolean validateOnMatch) {
259:
260: this .validateOnMatch = validateOnMatch;
261:
262: }
263:
264: public void setTrackStatements(String value) {
265: if (value == null)
266: throw new IllegalArgumentException(
267: "Null value for trackStatements");
268: String trimmed = value.trim();
269: if (trimmed.equalsIgnoreCase(TRACK_STATEMENTS_FALSE))
270: trackStatements = TRACK_STATEMENTS_FALSE_INT;
271: else if (trimmed.equalsIgnoreCase(TRACK_STATEMENTS_TRUE))
272: trackStatements = TRACK_STATEMENTS_TRUE_INT;
273: else
274: trackStatements = TRACK_STATEMENTS_NOWARN_INT;
275: }
276:
277: public String getExceptionSorterClassName() {
278: return exceptionSorterClassName;
279: }
280:
281: public void setExceptionSorterClassName(
282: String exceptionSorterClassName) {
283: this .exceptionSorterClassName = exceptionSorterClassName;
284: }
285:
286: public String getValidConnectionCheckerClassName() {
287: return validConnectionCheckerClassName;
288: }
289:
290: public void setValidConnectionCheckerClassName(String value) {
291: validConnectionCheckerClassName = value;
292: }
293:
294: public boolean isTransactionQueryTimeout() {
295: return isTransactionQueryTimeout;
296: }
297:
298: public void setTransactionQueryTimeout(boolean value) {
299: isTransactionQueryTimeout = value;
300: }
301:
302: public int getQueryTimeout() {
303: return queryTimeout;
304: }
305:
306: public void setQueryTimeout(int timeout) {
307: queryTimeout = timeout;
308: }
309:
310: public Set getInvalidConnections(final Set connectionSet)
311: throws ResourceException {
312:
313: final Set invalid = new HashSet();
314:
315: for (Iterator iter = connectionSet.iterator(); iter.hasNext();) {
316:
317: Object anonymous = iter.next();
318:
319: if (anonymous instanceof BaseWrapperManagedConnection) {
320: BaseWrapperManagedConnection mc = (BaseWrapperManagedConnection) anonymous;
321:
322: if (!mc.checkValid()) {
323:
324: invalid.add(mc);
325: }
326:
327: }
328:
329: }
330:
331: return invalid;
332: }
333:
334: /**
335: * Gets full set of connection properties, i.e. whatever is provided
336: * in config plus "user" and "password" from subject/cri.
337: *
338: * <p>Note that the set is used to match connections to datasources as well
339: * as to create new managed connections.
340: *
341: * <p>In fact, we have a problem here. Theoretically, there is a possible
342: * name collision between config properties and "user"/"password".
343: */
344: protected Properties getConnectionProperties(Subject subject,
345: ConnectionRequestInfo cri) throws ResourceException {
346: if (cri != null
347: && cri.getClass() != WrappedConnectionRequestInfo.class)
348: throw new JBossResourceException(
349: "Wrong kind of ConnectionRequestInfo: "
350: + cri.getClass());
351:
352: Properties props = new Properties();
353: props.putAll(connectionProps);
354: if (subject != null) {
355: if (SubjectActions.addMatchingProperties(subject, props,
356: this ) == true)
357: return props;
358: throw new JBossResourceException(
359: "No matching credentials in Subject!");
360: }
361: WrappedConnectionRequestInfo lcri = (WrappedConnectionRequestInfo) cri;
362: if (lcri != null) {
363: props.setProperty("user", (lcri.getUserName() == null) ? ""
364: : lcri.getUserName());
365: props.setProperty("password",
366: (lcri.getPassword() == null) ? "" : lcri
367: .getPassword());
368: return props;
369: }
370: if (userName != null) {
371: props.setProperty("user", userName);
372: props.setProperty("password", (password == null) ? ""
373: : password);
374: }
375: return props;
376: }
377:
378: boolean isExceptionFatal(SQLException e) {
379: try {
380: if (exceptionSorter != null)
381: return exceptionSorter.isExceptionFatal(e);
382:
383: if (exceptionSorterClassName != null) {
384: try {
385: ClassLoader cl = Thread.currentThread()
386: .getContextClassLoader();
387: Class clazz = cl
388: .loadClass(exceptionSorterClassName);
389: exceptionSorter = (ExceptionSorter) clazz
390: .newInstance();
391: return exceptionSorter.isExceptionFatal(e);
392: } catch (Exception e2) {
393: log
394: .warn(
395: "exception trying to create exception sorter (disabling):",
396: e2);
397: exceptionSorter = new NullExceptionSorter();
398: }
399: }
400: } catch (Throwable t) {
401: log.warn("Error checking exception fatality: ", t);
402: }
403: return false;
404: }
405:
406: /**
407: * Checks whether a connection is valid
408: */
409: SQLException isValidConnection(Connection c) {
410: // Already got a checker
411: if (connectionChecker != null)
412: return connectionChecker.isValidConnection(c);
413:
414: // Class specified
415: if (validConnectionCheckerClassName != null) {
416: try {
417: ClassLoader cl = Thread.currentThread()
418: .getContextClassLoader();
419: Class clazz = cl
420: .loadClass(validConnectionCheckerClassName);
421: connectionChecker = (ValidConnectionChecker) clazz
422: .newInstance();
423: return connectionChecker.isValidConnection(c);
424: } catch (Exception e) {
425: log
426: .warn(
427: "Exception trying to create connection checker (disabling):",
428: e);
429: connectionChecker = new NullValidConnectionChecker();
430: }
431: }
432:
433: // SQL statement specified
434: if (checkValidConnectionSQL != null) {
435: connectionChecker = new CheckValidConnectionSQL(
436: checkValidConnectionSQL);
437: return connectionChecker.isValidConnection(c);
438: }
439:
440: // No Check
441: return null;
442: }
443:
444: static class SubjectActions implements PrivilegedAction {
445: Subject subject;
446:
447: Properties props;
448:
449: ManagedConnectionFactory mcf;
450:
451: SubjectActions(Subject subject, Properties props,
452: ManagedConnectionFactory mcf) {
453: this .subject = subject;
454: this .props = props;
455: this .mcf = mcf;
456: }
457:
458: public Object run() {
459: Iterator i = subject.getPrivateCredentials().iterator();
460: while (i.hasNext()) {
461: Object o = i.next();
462: if (o instanceof PasswordCredential) {
463: PasswordCredential cred = (PasswordCredential) o;
464: if (cred.getManagedConnectionFactory().equals(mcf)) {
465: props.setProperty("user",
466: (cred.getUserName() == null) ? ""
467: : cred.getUserName());
468: if (cred.getPassword() != null)
469: props.setProperty("password", new String(
470: cred.getPassword()));
471: return Boolean.TRUE;
472: }
473: }
474: }
475: return Boolean.FALSE;
476: }
477:
478: static boolean addMatchingProperties(Subject subject,
479: Properties props, ManagedConnectionFactory mcf) {
480: SubjectActions action = new SubjectActions(subject, props,
481: mcf);
482: Boolean matched = (Boolean) AccessController
483: .doPrivileged(action);
484: return matched.booleanValue();
485: }
486: }
487: }
|