/* * Copyright 2008-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.jdbc.config.oracle; import org.springframework.beans.factory.FactoryBean; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.DelegatingDataSource; import org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor; import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; import org.springframework.dao.NonTransientDataAccessResourceException; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.sql.DataSource; import javax.jms.ConnectionFactory; import oracle.jms.AQjmsFactory; import oracle.jdbc.OracleConnection; import java.sql.*; import java.lang.reflect.Proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; /** * FactoryBean used for the "aq-jms-queue-connection-factory" element of the "orcl" name space * * @author Thomas Risberg * @since 1.0 */ public class AqJmsFactoryBeanFactory implements FactoryBean<ConnectionFactory> { protected static final Log logger = LogFactory.getLog(AqJmsFactoryBeanFactory.class); public enum ConnectionFactoryType { CONNECTION, QUEUE_CONNECTION, TOPIC_CONNECTION, } private ConnectionFactory aqConnectionFactory; private boolean coordinateWithDataSourceTransactions = false; private DataSource dataSource; private ConnectionFactoryType connectionFactoryType = ConnectionFactoryType.CONNECTION; private NativeJdbcExtractor nativeJdbcExtractor = new SimpleNativeJdbcExtractor(); public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void setConnectionFactoryType(ConnectionFactoryType connectionFactoryType) { this.connectionFactoryType = connectionFactoryType; } public void setCoordinateWithDataSourceTransactions(boolean coordinateWithDataSourceTransactions) { this.coordinateWithDataSourceTransactions = coordinateWithDataSourceTransactions; } public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) { this.nativeJdbcExtractor = nativeJdbcExtractor; } public synchronized ConnectionFactory getObject() throws Exception { DataSource dataSourceToUse; if (coordinateWithDataSourceTransactions) { if (logger.isDebugEnabled()) { logger.debug("Enabling coordination of messaging transactions with data source transactions"); } dataSourceToUse = new TransactionAwareDataSource(dataSource); } else { dataSourceToUse = dataSource; } if (aqConnectionFactory == null) { if (this.connectionFactoryType == ConnectionFactoryType.CONNECTION) { logger.debug("Using a 'ConnectionFactory' as the AQ Connection Factory"); aqConnectionFactory = AQjmsFactory.getConnectionFactory(dataSourceToUse); } if (this.connectionFactoryType == ConnectionFactoryType.QUEUE_CONNECTION) { logger.debug("Using a 'QueueConnectionFactory' as the AQ Connection Factory"); aqConnectionFactory = AQjmsFactory.getQueueConnectionFactory(dataSourceToUse); } if (this.connectionFactoryType == ConnectionFactoryType.TOPIC_CONNECTION) { logger.debug("Using a 'TopicConnectionFactory' as the AQ Connection Factory"); aqConnectionFactory = AQjmsFactory.getTopicConnectionFactory(dataSourceToUse); } } return aqConnectionFactory; } public Class<? extends ConnectionFactory> getObjectType() { return ConnectionFactory.class; } public boolean isSingleton() { return true; } private class TransactionAwareDataSource extends DelegatingDataSource { public TransactionAwareDataSource(DataSource dataSource) { super(dataSource); } @Override public java.sql.Connection getConnection() throws SQLException { java.sql.Connection con = DataSourceUtils.getConnection(getTargetDataSource()); java.sql.Connection conToUse = con; if (!(con instanceof OracleConnection)) { if (logger.isDebugEnabled()) { logger.debug("Unwrapping JDBC Connection of type:" + con.getClass().getName()); } try { conToUse = nativeJdbcExtractor.getNativeConnection(con); } catch (SQLException e) { throw new NonTransientDataAccessResourceException( "Error unwrapping the Oracle Connection: " + e.getMessage(), e); } } if (TransactionSynchronizationManager.isActualTransactionActive()) { if (logger.isDebugEnabled()) { logger.debug("Using Proxied JDBC Connection [" + conToUse + "]"); } return getCloseSuppressingConnectionProxy(conToUse); } else { if (con instanceof OracleConnection) { if (logger.isDebugEnabled()) { logger.debug("Using Native JDBC Connection [" + conToUse + "]"); } return conToUse; } else { if (logger.isDebugEnabled()) { logger.debug("Using Closeable Proxy fo JDBC Connection [" + conToUse + "]"); } return getCloseDelegatingConnectionProxy(conToUse, con); } } } } /** * Wrap the given Connection with a proxy that delegates every method call * to it but suppresses close calls. * @param target the original Connection to wrap * @return the wrapped Connection */ protected Connection getCloseSuppressingConnectionProxy(Connection target) { return (Connection) Proxy.newProxyInstance(OracleConnectionProxy.class.getClassLoader(), new Class[] { OracleConnectionProxy.class }, new CloseSuppressingInvocationHandler(target)); } /** * Invocation handler that suppresses close calls on JDBC Connections until the * associated PlatformTransactiunManager determines the connection should actually be closed. */ private static class CloseSuppressingInvocationHandler implements InvocationHandler { private final Connection target; public CloseSuppressingInvocationHandler(Connection target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on ConnectionProxy interface coming in... if (method.getName().equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); } else if (method.getName().equals("hashCode")) { // Use hashCode of Connection proxy. return new Integer(System.identityHashCode(proxy)); } else if (method.getName().equals("close")) { // Handle close method: don't pass the call on if (logger.isDebugEnabled()) { logger.debug("Coordinating transaction management for Connection [" + target + "]"); } return null; } else if (method.getName().equals("getTargetConnection")) { // Handle getTargetConnection method: return underlying // Connection. return this.target; } // Invoke method on target Connection. try { return method.invoke(this.target, args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } /** * Wrap the given Connection with a proxy that delegates every method call * to it and delegates close calls to the source (unwrapped) pooled Connection. * @param target the original Connection to wrap * @param source the pooled Connection to call close on * @return the wrapped Connection */ protected Connection getCloseDelegatingConnectionProxy(Connection target, Connection source) { return (Connection) Proxy.newProxyInstance(OracleConnectionProxy.class.getClassLoader(), new Class[] { OracleConnectionProxy.class }, new CloseDelegatingInvocationHandler(target, source)); } /** * Invocation handler that delegates close calls on JDBC Connections to the source * Connection which usually is the Connection obtained from a pool. */ private static class CloseDelegatingInvocationHandler implements InvocationHandler { private final Connection target; private final Connection source; public CloseDelegatingInvocationHandler(Connection target, Connection source) { this.target = target; this.source = source; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on ConnectionProxy interface coming in... if (method.getName().equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); } else if (method.getName().equals("hashCode")) { // Use hashCode of Connection proxy. return new Integer(System.identityHashCode(proxy)); } else if (method.getName().equals("close")) { // Handle close method: don't pass the call on if (logger.isDebugEnabled()) { logger.debug("Calling close for Connection [" + target + "] on source [" + source + "]"); } source.close(); return null; } else if (method.getName().equals("getTargetConnection")) { // Handle getTargetConnection method: return underlying // Connection. return this.target; } // Invoke method on target Connection. try { return method.invoke(this.target, args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } }