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}