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}