EMMA Coverage Report (generated Thu Jan 24 13:37:04 CST 2013)
[all classes][org.springframework.batch.item.database]

COVERAGE SUMMARY FOR SOURCE FILE [ExtendedConnectionDataSourceProxy.java]

nameclass, %method, %block, %line, %
ExtendedConnectionDataSourceProxy.java100% (2/2)70%  (16/23)78%  (277/353)79%  (67.1/85)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ExtendedConnectionDataSourceProxy100% (1/1)67%  (14/21)76%  (216/283)76%  (51.1/67)
afterPropertiesSet (): void 0%   (0/1)0%   (0/4)0%   (0/2)
getConnection (String, String): Connection 0%   (0/1)0%   (0/17)0%   (0/3)
getLogWriter (): PrintWriter 0%   (0/1)0%   (0/4)0%   (0/1)
getLoginTimeout (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getParentLogger (): Logger 0%   (0/1)0%   (0/4)0%   (0/1)
setLogWriter (PrintWriter): void 0%   (0/1)0%   (0/5)0%   (0/2)
setLoginTimeout (int): void 0%   (0/1)0%   (0/5)0%   (0/2)
getConnection (): Connection 100% (1/1)71%  (12/17)67%  (2/3)
stopCloseSuppression (Connection): void 100% (1/1)75%  (15/20)95%  (4.7/5)
startCloseSuppression (Connection): void 100% (1/1)77%  (17/22)90%  (5.4/6)
initConnection (String, String): Connection 100% (1/1)77%  (24/31)89%  (8/9)
isCloseSuppressionActive (Connection): boolean 100% (1/1)85%  (11/13)67%  (2/3)
ExtendedConnectionDataSourceProxy (): void 100% (1/1)100% (14/14)100% (5/5)
ExtendedConnectionDataSourceProxy (DataSource): void 100% (1/1)100% (17/17)100% (6/6)
access$000 (ExtendedConnectionDataSourceProxy, Connection): boolean 100% (1/1)100% (4/4)100% (1/1)
completeCloseCall (Connection): boolean 100% (1/1)100% (15/15)100% (3/3)
getCloseSuppressingConnectionProxy (Connection): Connection 100% (1/1)100% (16/16)100% (1/1)
isWrapperFor (Class): boolean 100% (1/1)100% (14/14)100% (3/3)
setDataSource (DataSource): void 100% (1/1)100% (4/4)100% (2/2)
shouldClose (Connection): boolean 100% (1/1)100% (21/21)100% (4/4)
unwrap (Class): Object 100% (1/1)100% (32/32)100% (7/7)
     
class ExtendedConnectionDataSourceProxy$CloseSuppressingInvocationHandler100% (1/1)100% (2/2)87%  (61/70)89%  (16/18)
invoke (Object, Method, Object []): Object 100% (1/1)85%  (52/61)86%  (12/14)
ExtendedConnectionDataSourceProxy$CloseSuppressingInvocationHandler (Connecti... 100% (1/1)100% (9/9)100% (4/4)

1/*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 
17package org.springframework.batch.item.database;
18 
19import java.io.PrintWriter;
20import java.lang.reflect.InvocationHandler;
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.lang.reflect.Proxy;
24import java.sql.Connection;
25import java.sql.SQLException;
26import java.util.logging.Logger;
27 
28import javax.sql.DataSource;
29 
30import org.springframework.beans.factory.InitializingBean;
31import org.springframework.jdbc.datasource.ConnectionProxy;
32import org.springframework.jdbc.datasource.DataSourceUtils;
33import org.springframework.jdbc.datasource.SmartDataSource;
34import org.springframework.transaction.support.TransactionSynchronizationManager;
35import org.springframework.util.Assert;
36 
37/**
38 * Implementation of {@link SmartDataSource} that is capable of keeping a single
39 * JDBC Connection which is NOT closed after each use even if
40 * {@link Connection#close()} is called.
41 * 
42 * The connection can be kept open over multiple transactions when used together
43 * with any of Spring's
44 * {@link org.springframework.transaction.PlatformTransactionManager}
45 * implementations.
46 * 
47 * <p>
48 * Loosely based on the SingleConnectionDataSource implementation in Spring
49 * Core. Intended to be used with the {@link JdbcCursorItemReader} to provide a
50 * connection that remains open across transaction boundaries, It remains open
51 * for the life of the cursor, and can be shared with the main transaction of
52 * the rest of the step processing.
53 * 
54 * <p>
55 * Once close suppression has been turned on for a connection, it will be
56 * returned for the first {@link #getConnection()} call. Any subsequent calls to
57 * {@link #getConnection()} will retrieve a new connection from the wrapped
58 * {@link DataSource} until the {@link DataSourceUtils} queries whether the
59 * connection should be closed or not by calling
60 * {@link #shouldClose(Connection)} for the close-suppressed {@link Connection}.
61 * At that point the cycle starts over again, and the next
62 * {@link #getConnection()} call will have the {@link Connection} that is being
63 * close-suppressed returned. This allows the use of the close-suppressed
64 * {@link Connection} to be the main {@link Connection} for an extended data
65 * access process. The close suppression is turned off by calling
66 * {@link #stopCloseSuppression(Connection)}.
67 * 
68 * <p>
69 * This class is not multi-threading capable.
70 * 
71 * <p>
72 * The connection returned will be a close-suppressing proxy instead of the
73 * physical {@link Connection}. Be aware that you will not be able to cast this
74 * to a native <code>OracleConnection</code> or the like anymore; you need to
75 * use a {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}.
76 * 
77 * @author Thomas Risberg
78 * @see #getConnection()
79 * @see java.sql.Connection#close()
80 * @see DataSourceUtils#releaseConnection
81 * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
82 * @since 2.0
83 */
84public class ExtendedConnectionDataSourceProxy implements SmartDataSource, InitializingBean {
85 
86        /** Provided DataSource */
87        private DataSource dataSource;
88 
89        /** The connection to suppress close calls for */
90        private Connection closeSuppressedConnection = null;
91 
92        /** The connection to suppress close calls for */
93        private boolean borrowedConnection = false;
94 
95        /** Synchronization monitor for the shared Connection */
96        private final Object connectionMonitor = new Object();
97 
98        /**
99         * No arg constructor for use when configured using JavaBean style.
100         */
101        public ExtendedConnectionDataSourceProxy() {
102        }
103 
104        /**
105         * Constructor that takes as a parameter with the {&link DataSource} to be
106         * wrapped.
107         */
108        public ExtendedConnectionDataSourceProxy(DataSource dataSource) {
109                this.dataSource = dataSource;
110        }
111 
112        /**
113         * Setter for the {&link DataSource} that is to be wrapped.
114         * 
115         * @param dataSource the DataSource
116         */
117        public void setDataSource(DataSource dataSource) {
118                this.dataSource = dataSource;
119        }
120 
121        /**
122         * @see SmartDataSource
123         */
124        public boolean shouldClose(Connection connection) {
125                boolean shouldClose = !isCloseSuppressionActive(connection);
126                if (borrowedConnection && closeSuppressedConnection.equals(connection)) {
127                        borrowedConnection = false;
128                }
129                return shouldClose;
130        }
131 
132        /**
133         * Return the status of close suppression being activated for a given
134         * {@link Connection}
135         * 
136         * @param connection the {@link Connection} that the close suppression
137         * status is requested for
138         * @return true or false
139         */
140        public boolean isCloseSuppressionActive(Connection connection) {
141                if (connection == null) {
142                        return false;
143                }
144                return connection.equals(closeSuppressedConnection) ? true : false;
145        }
146 
147        /**
148         * 
149         * @param connection the {@link Connection} that close suppression is
150         * requested for
151         */
152        public void startCloseSuppression(Connection connection) {
153                synchronized (this.connectionMonitor) {
154                        closeSuppressedConnection = connection;
155                        if (TransactionSynchronizationManager.isActualTransactionActive()) {
156                                borrowedConnection = true;
157                        }
158                }
159        }
160 
161        /**
162         * 
163         * @param connection the {@link Connection} that close suppression should be
164         * turned off for
165         */
166        public void stopCloseSuppression(Connection connection) {
167                synchronized (this.connectionMonitor) {
168                        closeSuppressedConnection = null;
169                        borrowedConnection = false;
170                }
171        }
172 
173        public Connection getConnection() throws SQLException {
174                synchronized (this.connectionMonitor) {
175                        return initConnection(null, null);
176                }
177        }
178 
179        public Connection getConnection(String username, String password) throws SQLException {
180                synchronized (this.connectionMonitor) {
181                        return initConnection(username, password);
182                }
183        }
184 
185        private boolean completeCloseCall(Connection connection) {
186                if (borrowedConnection && closeSuppressedConnection.equals(connection)) {
187                        borrowedConnection = false;
188                }
189                return isCloseSuppressionActive(connection);
190        }
191 
192        private Connection initConnection(String username, String password) throws SQLException {
193                if (closeSuppressedConnection != null) {
194                        if (!borrowedConnection) {
195                                borrowedConnection = true;
196                                return closeSuppressedConnection;
197                        }
198                }
199                Connection target;
200                if (username != null) {
201                        target = dataSource.getConnection(username, password);
202                }
203                else {
204                        target = dataSource.getConnection();
205                }
206                Connection connection = getCloseSuppressingConnectionProxy(target);
207                return connection;
208        }
209 
210        public PrintWriter getLogWriter() throws SQLException {
211                return dataSource.getLogWriter();
212        }
213 
214        public int getLoginTimeout() throws SQLException {
215                return dataSource.getLoginTimeout();
216        }
217 
218        public void setLogWriter(PrintWriter out) throws SQLException {
219                dataSource.setLogWriter(out);
220        }
221 
222        public void setLoginTimeout(int seconds) throws SQLException {
223                dataSource.setLoginTimeout(seconds);
224        }
225 
226        /**
227         * Wrap the given Connection with a proxy that delegates every method call
228         * to it but suppresses close calls.
229         * @param target the original Connection to wrap
230         * @return the wrapped Connection
231         */
232        protected Connection getCloseSuppressingConnectionProxy(Connection target) {
233                return (Connection) Proxy.newProxyInstance(ConnectionProxy.class.getClassLoader(),
234                                new Class[] { ConnectionProxy.class }, new CloseSuppressingInvocationHandler(target, this));
235        }
236 
237        /**
238         * Invocation handler that suppresses close calls on JDBC Connections until
239         * the associated instance of the ExtendedConnectionDataSourceProxy
240         * determines the connection should actually be closed.
241         */
242        private static class CloseSuppressingInvocationHandler implements InvocationHandler {
243 
244                private final Connection target;
245 
246                private final ExtendedConnectionDataSourceProxy dataSource;
247 
248                public CloseSuppressingInvocationHandler(Connection target, ExtendedConnectionDataSourceProxy dataSource) {
249                        this.dataSource = dataSource;
250                        this.target = target;
251                }
252 
253                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
254                        // Invocation on ConnectionProxy interface coming in...
255 
256                        if (method.getName().equals("equals")) {
257                                // Only consider equal when proxies are identical.
258                                return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
259                        }
260                        else if (method.getName().equals("hashCode")) {
261                                // Use hashCode of Connection proxy.
262                                return new Integer(System.identityHashCode(proxy));
263                        }
264                        else if (method.getName().equals("close")) {
265                                // Handle close method: don't pass the call on if we are
266                                // suppressing close calls.
267                                if (dataSource.completeCloseCall((Connection) proxy)) {
268                                        return null;
269                                }
270                                else {
271                                        target.close();
272                                        return null;
273                                }
274                        }
275                        else if (method.getName().equals("getTargetConnection")) {
276                                // Handle getTargetConnection method: return underlying
277                                // Connection.
278                                return this.target;
279                        }
280 
281                        // Invoke method on target Connection.
282                        try {
283                                return method.invoke(this.target, args);
284                        }
285                        catch (InvocationTargetException ex) {
286                                throw ex.getTargetException();
287                        }
288                }
289        }
290 
291        /**
292         * Performs only a 'shallow' non-recursive check of self's and delegate's
293         * class to retain Java 5 compatibility.
294         */
295        public boolean isWrapperFor(Class<?> iface) throws SQLException {
296                if (iface.isAssignableFrom(SmartDataSource.class) || iface.isAssignableFrom(dataSource.getClass())) {
297                        return true;
298                }
299                return false;
300        }
301 
302        /**
303         * Returns either self or delegate (in this order) if one of them can be
304         * cast to supplied parameter class. Does *not* support recursive unwrapping
305         * of the delegate to retain Java 5 compatibility.
306         */
307        public <T> T unwrap(Class<T> iface) throws SQLException {
308                if (iface.isAssignableFrom(SmartDataSource.class)) {
309                        @SuppressWarnings("unchecked")
310                        T casted = (T) this;
311                        return casted;
312                }
313                else if (iface.isAssignableFrom(dataSource.getClass())) {
314                        @SuppressWarnings("unchecked")
315                        T casted = (T) dataSource;
316                        return casted;
317                }
318                throw new SQLException("Unsupported class " + iface.getSimpleName());
319        }
320 
321        public void afterPropertiesSet() throws Exception {
322                Assert.notNull(dataSource);
323        }
324 
325        /**
326         * Added due to JDK 7 compatibility, sadly a proper implementation
327         * that would throw SqlFeatureNotSupportedException is not possible
328         * in Java 5 (the class was added in Java 6).
329         */
330        public Logger getParentLogger() {
331                throw new UnsupportedOperationException();
332        }
333 
334}

[all classes][org.springframework.batch.item.database]
EMMA 2.0.5312 (C) Vladimir Roubtsov