/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.resource.adapter.jdbc; import java.io.PrintWriter; import java.io.Serializable; import java.security.AccessController; import java.security.PrivilegedAction; import java.sql.Connection; import java.sql.SQLException; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.Set; import javax.resource.ResourceException; import javax.resource.spi.ConnectionManager; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.ManagedConnectionFactory; import javax.resource.spi.ValidatingManagedConnectionFactory; import javax.resource.spi.security.PasswordCredential; import javax.security.auth.Subject; import org.jboss.logging.Logger; import org.jboss.resource.JBossResourceException; /** * BaseWrapperManagedConnectionFactory * * @author David Jencks * @author Adrian Brock * @author Weston Price * @version $Revision: 57189 $ */ public abstract class BaseWrapperManagedConnectionFactory implements ManagedConnectionFactory, ValidatingManagedConnectionFactory, Serializable { /** @since 4.0.1 */ static final long serialVersionUID = -84923705377702088L; public static final int TRACK_STATEMENTS_FALSE_INT = 0; public static final int TRACK_STATEMENTS_TRUE_INT = 1; public static final int TRACK_STATEMENTS_NOWARN_INT = 2; public static final String TRACK_STATEMENTS_FALSE = "false"; public static final String TRACK_STATEMENTS_TRUE = "true"; public static final String TRACK_STATEMENTS_NOWARN = "nowarn"; protected final Logger log = Logger.getLogger(getClass()); protected String userName; protected String password; //This is used by Local wrapper for all properties, and is left //in this class for ease of writing getConnectionProperties, //which always holds the user/pw. protected final Properties connectionProps = new Properties(); protected int transactionIsolation = -1; protected int preparedStatementCacheSize = 0; protected boolean doQueryTimeout = false; /** * The variable newConnectionSQL holds an SQL * statement which if not null is executed when a new Connection is * obtained for a new ManagedConnection. */ protected String newConnectionSQL; /** * The variable checkValidConnectionSQL holds an sql * statement that may be executed whenever a managed connection is * removed from the pool, to check that it is still valid. This * requires setting up an mbean to execute it when notified by the * ConnectionManager. */ protected String checkValidConnectionSQL; /** * The classname used to check whether a connection is valid */ protected String validConnectionCheckerClassName; /** * The instance of the valid connection checker */ protected ValidConnectionChecker connectionChecker; private String exceptionSorterClassName; private ExceptionSorter exceptionSorter; protected int trackStatements = TRACK_STATEMENTS_NOWARN_INT; /** Whether to share cached prepared statements */ protected boolean sharePS = false; protected boolean isTransactionQueryTimeout = false; protected int queryTimeout = 0; private boolean validateOnMatch; public BaseWrapperManagedConnectionFactory () { } public PrintWriter getLogWriter() throws ResourceException { // TODO: implement this javax.resource.spi.ManagedConnectionFactory method return null; } public void setLogWriter(PrintWriter param1) throws ResourceException { // TODO: implement this javax.resource.spi.ManagedConnectionFactory method } public Object createConnectionFactory(ConnectionManager cm) throws ResourceException { return new WrapperDataSource(this, cm); } public Object createConnectionFactory() throws ResourceException { throw new JBossResourceException("NYI"); } public String getUserName() { return userName; } public void setUserName(final String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(final String password) { this.password = password; } public int getPreparedStatementCacheSize() { return preparedStatementCacheSize; } public void setPreparedStatementCacheSize(int size) { preparedStatementCacheSize = size; } public boolean getSharePreparedStatements() { return sharePS; } public void setSharePreparedStatements(boolean sharePS) { this.sharePS = sharePS; } public String getTransactionIsolation() { switch (this.transactionIsolation) { case Connection.TRANSACTION_NONE: return "TRANSACTION_NONE"; case Connection.TRANSACTION_READ_COMMITTED: return "TRANSACTION_READ_COMMITTED"; case Connection.TRANSACTION_READ_UNCOMMITTED: return "TRANSACTION_READ_UNCOMMITTED"; case Connection.TRANSACTION_REPEATABLE_READ: return "TRANSACTION_REPEATABLE_READ"; case Connection.TRANSACTION_SERIALIZABLE: return "TRANSACTION_SERIALIZABLE"; case -1: return "DEFAULT"; default: return Integer.toString(transactionIsolation); } } public void setTransactionIsolation(String transactionIsolation) { if (transactionIsolation.equals("TRANSACTION_NONE")) this.transactionIsolation = Connection.TRANSACTION_NONE; else if (transactionIsolation.equals("TRANSACTION_READ_COMMITTED")) this.transactionIsolation = Connection.TRANSACTION_READ_COMMITTED; else if (transactionIsolation.equals("TRANSACTION_READ_UNCOMMITTED")) this.transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED; else if (transactionIsolation.equals("TRANSACTION_REPEATABLE_READ")) this.transactionIsolation = Connection.TRANSACTION_REPEATABLE_READ; else if (transactionIsolation.equals("TRANSACTION_SERIALIZABLE")) this.transactionIsolation = Connection.TRANSACTION_SERIALIZABLE; else { try { this.transactionIsolation = Integer.parseInt(transactionIsolation); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Setting Isolation level to unknown state: " + transactionIsolation); } } } public String getNewConnectionSQL() { return newConnectionSQL; } public void setNewConnectionSQL(String newConnectionSQL) { this.newConnectionSQL = newConnectionSQL; } public String getCheckValidConnectionSQL() { return checkValidConnectionSQL; } public void setCheckValidConnectionSQL(String checkValidConnectionSQL) { this.checkValidConnectionSQL = checkValidConnectionSQL; } public String getTrackStatements() { if (trackStatements == TRACK_STATEMENTS_FALSE_INT) return TRACK_STATEMENTS_FALSE; else if (trackStatements == TRACK_STATEMENTS_TRUE_INT) return TRACK_STATEMENTS_TRUE; return TRACK_STATEMENTS_NOWARN; } public boolean getValidateOnMatch() { return this.validateOnMatch; } public void setValidateOnMatch(boolean validateOnMatch) { this.validateOnMatch = validateOnMatch; } public void setTrackStatements(String value) { if (value == null) throw new IllegalArgumentException("Null value for trackStatements"); String trimmed = value.trim(); if (trimmed.equalsIgnoreCase(TRACK_STATEMENTS_FALSE)) trackStatements = TRACK_STATEMENTS_FALSE_INT; else if (trimmed.equalsIgnoreCase(TRACK_STATEMENTS_TRUE)) trackStatements = TRACK_STATEMENTS_TRUE_INT; else trackStatements = TRACK_STATEMENTS_NOWARN_INT; } public String getExceptionSorterClassName() { return exceptionSorterClassName; } public void setExceptionSorterClassName(String exceptionSorterClassName) { this.exceptionSorterClassName = exceptionSorterClassName; } public String getValidConnectionCheckerClassName() { return validConnectionCheckerClassName; } public void setValidConnectionCheckerClassName(String value) { validConnectionCheckerClassName = value; } public boolean isTransactionQueryTimeout() { return isTransactionQueryTimeout; } public void setTransactionQueryTimeout(boolean value) { isTransactionQueryTimeout = value; } public int getQueryTimeout() { return queryTimeout; } public void setQueryTimeout(int timeout) { queryTimeout = timeout; } public Set getInvalidConnections(final Set connectionSet) throws ResourceException { final Set invalid = new HashSet(); for(Iterator iter = connectionSet.iterator(); iter.hasNext();){ Object anonymous = iter.next(); if (anonymous instanceof BaseWrapperManagedConnection) { BaseWrapperManagedConnection mc = (BaseWrapperManagedConnection) anonymous; if(!mc.checkValid()){ invalid.add(mc); } } } return invalid; } /** * Gets full set of connection properties, i.e. whatever is provided * in config plus "user" and "password" from subject/cri. * *

Note that the set is used to match connections to datasources as well * as to create new managed connections. * *

In fact, we have a problem here. Theoretically, there is a possible * name collision between config properties and "user"/"password". */ protected Properties getConnectionProperties(Subject subject, ConnectionRequestInfo cri) throws ResourceException { if (cri != null && cri.getClass() != WrappedConnectionRequestInfo.class) throw new JBossResourceException("Wrong kind of ConnectionRequestInfo: " + cri.getClass()); Properties props = new Properties(); props.putAll(connectionProps); if (subject != null) { if (SubjectActions.addMatchingProperties(subject, props, this) == true) return props; throw new JBossResourceException("No matching credentials in Subject!"); } WrappedConnectionRequestInfo lcri = (WrappedConnectionRequestInfo)cri; if (lcri != null) { props.setProperty("user", (lcri.getUserName() == null)? "": lcri.getUserName()); props.setProperty("password", (lcri.getPassword() == null)? "": lcri.getPassword()); return props; } if (userName != null) { props.setProperty("user", userName); props.setProperty("password", (password == null) ? "" : password); } return props; } boolean isExceptionFatal(SQLException e) { try { if (exceptionSorter != null) return exceptionSorter.isExceptionFatal(e); if (exceptionSorterClassName != null) { try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class clazz = cl.loadClass(exceptionSorterClassName); exceptionSorter = (ExceptionSorter)clazz.newInstance(); return exceptionSorter.isExceptionFatal(e); } catch (Exception e2) { log.warn("exception trying to create exception sorter (disabling):", e2); exceptionSorter = new NullExceptionSorter(); } } } catch (Throwable t) { log.warn("Error checking exception fatality: ", t); } return false; } /** * Checks whether a connection is valid */ SQLException isValidConnection(Connection c) { // Already got a checker if (connectionChecker != null) return connectionChecker.isValidConnection(c); // Class specified if (validConnectionCheckerClassName != null) { try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class clazz = cl.loadClass(validConnectionCheckerClassName); connectionChecker = (ValidConnectionChecker) clazz.newInstance(); return connectionChecker.isValidConnection(c); } catch (Exception e) { log.warn("Exception trying to create connection checker (disabling):", e); connectionChecker = new NullValidConnectionChecker(); } } // SQL statement specified if (checkValidConnectionSQL != null) { connectionChecker = new CheckValidConnectionSQL(checkValidConnectionSQL); return connectionChecker.isValidConnection(c); } // No Check return null; } static class SubjectActions implements PrivilegedAction { Subject subject; Properties props; ManagedConnectionFactory mcf; SubjectActions(Subject subject, Properties props, ManagedConnectionFactory mcf) { this.subject = subject; this.props = props; this.mcf = mcf; } public Object run() { Iterator i = subject.getPrivateCredentials().iterator(); while (i.hasNext()) { Object o = i.next(); if (o instanceof PasswordCredential) { PasswordCredential cred = (PasswordCredential) o; if (cred.getManagedConnectionFactory().equals(mcf)) { props.setProperty("user", (cred.getUserName() == null) ? "" : cred.getUserName()); if( cred.getPassword() != null ) props.setProperty("password", new String(cred.getPassword())); return Boolean.TRUE; } } } return Boolean.FALSE; } static boolean addMatchingProperties(Subject subject, Properties props, ManagedConnectionFactory mcf) { SubjectActions action = new SubjectActions(subject, props, mcf); Boolean matched = (Boolean) AccessController.doPrivileged(action); return matched.booleanValue(); } } }