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