001/* 002 * Copyright 2002-2012 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jca.cci.connection; 018 019import java.lang.reflect.InvocationHandler; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import javax.resource.NotSupportedException; 024import javax.resource.ResourceException; 025import javax.resource.cci.Connection; 026import javax.resource.cci.ConnectionFactory; 027import javax.resource.cci.ConnectionSpec; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import org.springframework.beans.factory.DisposableBean; 033import org.springframework.util.Assert; 034 035/** 036 * A CCI ConnectionFactory adapter that returns the same Connection on all 037 * {@code getConnection} calls, and ignores calls to 038 * {@code Connection.close()}. 039 * 040 * <p>Useful for testing and standalone environments, to keep using the same 041 * Connection for multiple CciTemplate calls, without having a pooling 042 * ConnectionFactory, also spanning any number of transactions. 043 * 044 * <p>You can either pass in a CCI Connection directly, or let this 045 * factory lazily create a Connection via a given target ConnectionFactory. 046 * 047 * @author Juergen Hoeller 048 * @since 1.2 049 * @see #getConnection() 050 * @see javax.resource.cci.Connection#close() 051 * @see org.springframework.jca.cci.core.CciTemplate 052 */ 053@SuppressWarnings("serial") 054public class SingleConnectionFactory extends DelegatingConnectionFactory implements DisposableBean { 055 056 protected final Log logger = LogFactory.getLog(getClass()); 057 058 /** Wrapped Connection */ 059 private Connection target; 060 061 /** Proxy Connection */ 062 private Connection connection; 063 064 /** Synchronization monitor for the shared Connection */ 065 private final Object connectionMonitor = new Object(); 066 067 068 /** 069 * Create a new SingleConnectionFactory for bean-style usage. 070 * @see #setTargetConnectionFactory 071 */ 072 public SingleConnectionFactory() { 073 } 074 075 /** 076 * Create a new SingleConnectionFactory that always returns the 077 * given Connection. 078 * @param target the single Connection 079 */ 080 public SingleConnectionFactory(Connection target) { 081 Assert.notNull(target, "Target Connection must not be null"); 082 this.target = target; 083 this.connection = getCloseSuppressingConnectionProxy(target); 084 } 085 086 /** 087 * Create a new SingleConnectionFactory that always returns a single 088 * Connection which it will lazily create via the given target 089 * ConnectionFactory. 090 * @param targetConnectionFactory the target ConnectionFactory 091 */ 092 public SingleConnectionFactory(ConnectionFactory targetConnectionFactory) { 093 Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null"); 094 setTargetConnectionFactory(targetConnectionFactory); 095 } 096 097 098 /** 099 * Make sure a Connection or ConnectionFactory has been set. 100 */ 101 @Override 102 public void afterPropertiesSet() { 103 if (this.connection == null && getTargetConnectionFactory() == null) { 104 throw new IllegalArgumentException("Connection or 'targetConnectionFactory' is required"); 105 } 106 } 107 108 109 @Override 110 public Connection getConnection() throws ResourceException { 111 synchronized (this.connectionMonitor) { 112 if (this.connection == null) { 113 initConnection(); 114 } 115 return this.connection; 116 } 117 } 118 119 @Override 120 public Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException { 121 throw new NotSupportedException( 122 "SingleConnectionFactory does not support custom ConnectionSpec"); 123 } 124 125 /** 126 * Close the underlying Connection. 127 * The provider of this ConnectionFactory needs to care for proper shutdown. 128 * <p>As this bean implements DisposableBean, a bean factory will 129 * automatically invoke this on destruction of its cached singletons. 130 */ 131 @Override 132 public void destroy() { 133 resetConnection(); 134 } 135 136 137 /** 138 * Initialize the single underlying Connection. 139 * <p>Closes and reinitializes the Connection if an underlying 140 * Connection is present already. 141 * @throws javax.resource.ResourceException if thrown by CCI API methods 142 */ 143 public void initConnection() throws ResourceException { 144 if (getTargetConnectionFactory() == null) { 145 throw new IllegalStateException( 146 "'targetConnectionFactory' is required for lazily initializing a Connection"); 147 } 148 synchronized (this.connectionMonitor) { 149 if (this.target != null) { 150 closeConnection(this.target); 151 } 152 this.target = doCreateConnection(); 153 prepareConnection(this.target); 154 if (logger.isInfoEnabled()) { 155 logger.info("Established shared CCI Connection: " + this.target); 156 } 157 this.connection = getCloseSuppressingConnectionProxy(this.target); 158 } 159 } 160 161 /** 162 * Reset the underlying shared Connection, to be reinitialized on next access. 163 */ 164 public void resetConnection() { 165 synchronized (this.connectionMonitor) { 166 if (this.target != null) { 167 closeConnection(this.target); 168 } 169 this.target = null; 170 this.connection = null; 171 } 172 } 173 174 /** 175 * Create a CCI Connection via this template's ConnectionFactory. 176 * @return the new CCI Connection 177 * @throws javax.resource.ResourceException if thrown by CCI API methods 178 */ 179 protected Connection doCreateConnection() throws ResourceException { 180 return getTargetConnectionFactory().getConnection(); 181 } 182 183 /** 184 * Prepare the given Connection before it is exposed. 185 * <p>The default implementation is empty. Can be overridden in subclasses. 186 * @param con the Connection to prepare 187 */ 188 protected void prepareConnection(Connection con) throws ResourceException { 189 } 190 191 /** 192 * Close the given Connection. 193 * @param con the Connection to close 194 */ 195 protected void closeConnection(Connection con) { 196 try { 197 con.close(); 198 } 199 catch (Throwable ex) { 200 logger.warn("Could not close shared CCI Connection", ex); 201 } 202 } 203 204 /** 205 * Wrap the given Connection with a proxy that delegates every method call to it 206 * but suppresses close calls. This is useful for allowing application code to 207 * handle a special framework Connection just like an ordinary Connection from a 208 * CCI ConnectionFactory. 209 * @param target the original Connection to wrap 210 * @return the wrapped Connection 211 */ 212 protected Connection getCloseSuppressingConnectionProxy(Connection target) { 213 return (Connection) Proxy.newProxyInstance( 214 Connection.class.getClassLoader(), 215 new Class<?>[] {Connection.class}, 216 new CloseSuppressingInvocationHandler(target)); 217 } 218 219 220 /** 221 * Invocation handler that suppresses close calls on CCI Connections. 222 */ 223 private static class CloseSuppressingInvocationHandler implements InvocationHandler { 224 225 private final Connection target; 226 227 private CloseSuppressingInvocationHandler(Connection target) { 228 this.target = target; 229 } 230 231 @Override 232 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 233 if (method.getName().equals("equals")) { 234 // Only consider equal when proxies are identical. 235 return (proxy == args[0]); 236 } 237 else if (method.getName().equals("hashCode")) { 238 // Use hashCode of Connection proxy. 239 return System.identityHashCode(proxy); 240 } 241 else if (method.getName().equals("close")) { 242 // Handle close method: don't pass the call on. 243 return null; 244 } 245 try { 246 return method.invoke(this.target, args); 247 } 248 catch (InvocationTargetException ex) { 249 throw ex.getTargetException(); 250 } 251 } 252 } 253 254}