View Javadoc

1   package org.springframework.batch.item.database;
2   
3   import static org.easymock.EasyMock.createMock;
4   import static org.easymock.EasyMock.expect;
5   import static org.easymock.EasyMock.replay;
6   import static org.easymock.EasyMock.verify;
7   import static org.junit.Assert.assertEquals;
8   import static org.junit.Assert.assertFalse;
9   import static org.junit.Assert.assertNotSame;
10  import static org.junit.Assert.assertSame;
11  import static org.junit.Assert.assertTrue;
12  import static org.junit.Assert.fail;
13  
14  import java.io.PrintWriter;
15  import java.sql.Connection;
16  import java.sql.ResultSet;
17  import java.sql.SQLException;
18  import java.sql.Statement;
19  import java.util.logging.Logger;
20  
21  import javax.sql.DataSource;
22  
23  import org.junit.Test;
24  import org.junit.internal.runners.JUnit4ClassRunner;
25  import org.junit.runner.RunWith;
26  import org.springframework.jdbc.core.JdbcTemplate;
27  import org.springframework.jdbc.datasource.DataSourceTransactionManager;
28  import org.springframework.jdbc.datasource.DataSourceUtils;
29  import org.springframework.jdbc.datasource.SmartDataSource;
30  import org.springframework.transaction.PlatformTransactionManager;
31  import org.springframework.transaction.TransactionDefinition;
32  import org.springframework.transaction.TransactionStatus;
33  import org.springframework.transaction.support.TransactionCallback;
34  import org.springframework.transaction.support.TransactionTemplate;
35  
36  @RunWith(JUnit4ClassRunner.class)
37  public class ExtendedConnectionDataSourceProxyTests {
38  
39  	@Test
40  	public void testOperationWithDataSourceUtils() throws SQLException {
41  		Connection con = createMock(Connection.class);
42  		DataSource ds = createMock(DataSource.class);
43  
44  		expect(ds.getConnection()).andReturn(con); // con1
45  		con.close();
46  		expect(ds.getConnection()).andReturn(con); // con2
47  		con.close();
48  
49  		expect(ds.getConnection()).andReturn(con); // con3
50  		con.close(); // con3
51  		expect(ds.getConnection()).andReturn(con); // con4
52  		con.close(); // con4
53  
54  		replay(ds);
55  		replay(con);
56  
57  		final ExtendedConnectionDataSourceProxy csds = new ExtendedConnectionDataSourceProxy(ds);
58  
59  		Connection con1 = csds.getConnection();
60  		Connection con2 = csds.getConnection();
61  		assertNotSame("shouldn't be the same connection", con1, con2);
62  
63  		assertTrue("should be able to close connection", csds.shouldClose(con1));
64  		con1.close();
65  		assertTrue("should be able to close connection", csds.shouldClose(con2));
66  		con2.close();
67  
68  		Connection con3 = csds.getConnection();
69  		csds.startCloseSuppression(con3);
70  		Connection con3_1 = csds.getConnection();
71  		assertSame("should be same connection", con3_1, con3);
72  		assertFalse("should not be able to close connection", csds.shouldClose(con3));
73  		con3_1.close(); // no mock call for this - should be suppressed
74  		Connection con3_2 = csds.getConnection();
75  		assertSame("should be same connection", con3_2, con3);
76  		Connection con4 = csds.getConnection();
77  		assertNotSame("shouldn't be same connection", con4, con3);
78  		csds.stopCloseSuppression(con3);
79  		assertTrue("should be able to close connection", csds.shouldClose(con3));
80  		con3_1 = null;
81  		con3_2 = null;
82  		con3.close();
83  		assertTrue("should be able to close connection", csds.shouldClose(con4));
84  		con4.close();
85  
86  		verify(ds);
87  		verify(con);
88  
89  	}
90  
91  	@Test
92  	public void testOperationWithDirectCloseCall() throws SQLException {
93  		Connection con = createMock(Connection.class);
94  		DataSource ds = createMock(DataSource.class);
95  
96  		expect(ds.getConnection()).andReturn(con); // con1
97  		con.close();
98  		expect(ds.getConnection()).andReturn(con); // con2
99  		con.close();
100 
101 		replay(ds);
102 		replay(con);
103 
104 		final ExtendedConnectionDataSourceProxy csds = new ExtendedConnectionDataSourceProxy(ds);
105 
106 		Connection con1 = csds.getConnection();
107 		csds.startCloseSuppression(con1);
108 		Connection con1_1 = csds.getConnection();
109 		assertSame("should be same connection", con1_1, con1);
110 		con1_1.close(); // no mock call for this - should be suppressed
111 		Connection con1_2 = csds.getConnection();
112 		assertSame("should be same connection", con1_2, con1);
113 		Connection con2 = csds.getConnection();
114 		assertNotSame("shouldn't be same connection", con2, con1);
115 		csds.stopCloseSuppression(con1);
116 		assertTrue("should be able to close connection", csds.shouldClose(con1));
117 		con1_1 = null;
118 		con1_2 = null;
119 		con1.close();
120 		assertTrue("should be able to close connection", csds.shouldClose(con2));
121 		con2.close();
122 
123 		verify(ds);
124 		verify(con);
125 
126 	}
127 
128 	@Test
129 	public void testSupressOfCloseWithJdbcTemplate() throws Exception {
130 
131 		Connection con = createMock(Connection.class);
132 		DataSource ds = createMock(DataSource.class);
133 		Statement stmt = createMock(Statement.class);
134 		ResultSet rs = createMock(ResultSet.class);
135 
136 		// open and start suppressing close
137 		expect(ds.getConnection()).andReturn(con);
138 
139 		// transaction 1
140 		expect(con.getAutoCommit()).andReturn(false);
141 		expect(con.createStatement()).andReturn(stmt);
142 		expect(stmt.executeQuery("select baz from bar")).andReturn(rs);
143 		expect(rs.next()).andReturn(false);
144 		expect(con.createStatement()).andReturn(stmt);
145 		expect(stmt.executeQuery("select foo from bar")).andReturn(rs);
146 		expect(rs.next()).andReturn(false);
147 		con.commit();
148 
149 		// transaction 2
150 		expect(con.getAutoCommit()).andReturn(false);
151 		expect(con.createStatement()).andReturn(stmt);
152 		expect(stmt.executeQuery("select ham from foo")).andReturn(rs);
153 		expect(rs.next()).andReturn(false);
154 		// REQUIRES_NEW transaction
155 		expect(ds.getConnection()).andReturn(con);
156 		expect(con.getAutoCommit()).andReturn(false);
157 		expect(con.createStatement()).andReturn(stmt);
158 		expect(stmt.executeQuery("select 1 from eggs")).andReturn(rs);
159 		expect(rs.next()).andReturn(false);
160 		con.commit();
161 		con.close();
162 		// resume transaction 2
163 		expect(con.createStatement()).andReturn(stmt);
164 		expect(stmt.executeQuery("select more, ham from foo")).andReturn(rs);
165 		expect(rs.next()).andReturn(false);
166 		con.commit();
167 
168 		// transaction 3
169 		expect(con.getAutoCommit()).andReturn(false);
170 		expect(con.createStatement()).andReturn(stmt);
171 		expect(stmt.executeQuery("select spam from ham")).andReturn(rs);
172 		expect(rs.next()).andReturn(false);
173 		con.commit();
174 
175 		// stop suppressing close and close
176 		con.close();
177 
178 		// standalone query
179 		expect(ds.getConnection()).andReturn(con);
180 		expect(con.createStatement()).andReturn(stmt);
181 		expect(stmt.executeQuery("select egg from bar")).andReturn(rs);
182 		expect(rs.next()).andReturn(false);
183 		con.close();
184 
185 		replay(rs);
186 		replay(stmt);
187 		replay(con);
188 		replay(ds);
189 
190 		final ExtendedConnectionDataSourceProxy csds = new ExtendedConnectionDataSourceProxy();
191 		csds.setDataSource(ds);
192 		PlatformTransactionManager tm = new DataSourceTransactionManager(csds);
193 		TransactionTemplate tt = new TransactionTemplate(tm);
194 		final TransactionTemplate tt2 = new TransactionTemplate(tm);
195 		tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
196 		final JdbcTemplate template = new JdbcTemplate(csds);
197 
198 		Connection connection = DataSourceUtils.getConnection(csds);
199 		csds.startCloseSuppression(connection);
200 		tt.execute(new TransactionCallback() {
201 			public Object doInTransaction(TransactionStatus status) {
202 				template.queryForList("select baz from bar");
203 				template.queryForList("select foo from bar");
204 				return null;
205 			}
206 		});
207 		tt.execute(new TransactionCallback() {
208 			public Object doInTransaction(TransactionStatus status) {
209 				template.queryForList("select ham from foo");
210 				tt2.execute(new TransactionCallback() {
211 					public Object doInTransaction(TransactionStatus status) {
212 						template.queryForList("select 1 from eggs");
213 						return null;
214 					}
215 				});
216 				template.queryForList("select more, ham from foo");
217 				return null;
218 			}
219 		});
220 		tt.execute(new TransactionCallback() {
221 			public Object doInTransaction(TransactionStatus status) {
222 				template.queryForList("select spam from ham");
223 				return null;
224 			}
225 		});
226 		csds.stopCloseSuppression(connection);
227 		DataSourceUtils.releaseConnection(connection, csds);
228 		template.queryForList("select egg from bar");
229 
230 		verify(rs);
231 		verify(stmt);
232 		verify(con);
233 		verify(ds);
234 	}
235 
236 	@Test(expected = IllegalArgumentException.class)
237 	public void delegateIsRequired() throws Exception {
238 
239 		ExtendedConnectionDataSourceProxy tested = new ExtendedConnectionDataSourceProxy(null);
240 		tested.afterPropertiesSet();
241 	}
242 
243 	@Test
244 	public void unwrapForUnsupportedInterface() throws Exception {
245 
246 		ExtendedConnectionDataSourceProxy tested = new ExtendedConnectionDataSourceProxy(new DataSourceStub());
247 
248 		assertFalse(tested.isWrapperFor(Unsupported.class));
249 
250 		try {
251 			tested.unwrap(Unsupported.class);
252 			fail();
253 		}
254 		catch (SQLException expected) {
255 //			this would be the correct behavior in a Java6-only recursive implementation
256 //			assertEquals(DataSourceStub.UNWRAP_ERROR_MESSAGE, expected.getMessage());
257 			assertEquals("Unsupported class " + Unsupported.class.getSimpleName(), expected.getMessage());
258 		}
259 	}
260 
261 	@Test
262 	public void unwrapForSupportedInterface() throws Exception {
263 
264 		DataSourceStub ds = new DataSourceStub();
265 		ExtendedConnectionDataSourceProxy tested = new ExtendedConnectionDataSourceProxy(ds);
266 
267 		assertTrue(tested.isWrapperFor(Supported.class));
268 		assertEquals(ds, tested.unwrap(Supported.class));
269 	}
270 
271 	@Test
272 	public void unwrapForSmartDataSource() throws Exception {
273 
274 		ExtendedConnectionDataSourceProxy tested = new ExtendedConnectionDataSourceProxy(new DataSourceStub());
275 
276 		assertTrue(tested.isWrapperFor(DataSource.class));
277 		assertEquals(tested, tested.unwrap(DataSource.class));
278 		
279 		assertTrue(tested.isWrapperFor(SmartDataSource.class));
280 		assertEquals(tested, tested.unwrap(SmartDataSource.class));
281 	}
282 
283 	/**
284 	 * Interface implemented by the wrapped DataSource
285 	 */
286 	private static interface Supported {
287 	}
288 
289 	/**
290 	 * Interface *not* implemented by the wrapped DataSource
291 	 */
292 	private static interface Unsupported {
293 	}
294 
295 	/**
296 	 * Stub for a wrapped DataSource that implements additional interface. Its
297 	 * purpose is testing of {@link DataSource#isWrapperFor(Class)} and
298 	 * {@link DataSource#unwrap(Class)} methods.
299 	 */
300 	private static class DataSourceStub implements DataSource, Supported {
301 
302 		private static final String UNWRAP_ERROR_MESSAGE = "supplied type is not implemented by this class";
303 
304 		public Connection getConnection() throws SQLException {
305 			throw new UnsupportedOperationException();
306 		}
307 
308 		public Connection getConnection(String username, String password) throws SQLException {
309 			throw new UnsupportedOperationException();
310 		}
311 
312 		public PrintWriter getLogWriter() throws SQLException {
313 			throw new UnsupportedOperationException();
314 		}
315 
316 		public int getLoginTimeout() throws SQLException {
317 			throw new UnsupportedOperationException();
318 		}
319 
320 		public void setLogWriter(PrintWriter out) throws SQLException {
321 			throw new UnsupportedOperationException();
322 		}
323 
324 		public void setLoginTimeout(int seconds) throws SQLException {
325 			throw new UnsupportedOperationException();
326 		}
327 
328 		public boolean isWrapperFor(Class<?> iface) throws SQLException {
329 			if (iface.equals(Supported.class) || (iface.equals(DataSource.class))) {
330 				return true;
331 			}
332 			return false;
333 		}
334 
335 		@SuppressWarnings("unchecked")
336 		public <T> T unwrap(Class<T> iface) throws SQLException {
337 			if (iface.equals(Supported.class) || iface.equals(DataSource.class)) {
338 				return (T) this;
339 			}
340 			throw new SQLException(UNWRAP_ERROR_MESSAGE);
341 		}
342 
343 		/**
344 		 * Added due to JDK 7 compatibility, sadly a proper implementation
345 		 * that would throw SqlFeatureNotSupportedException is not possible
346 		 * in Java 5 (the class was added in Java 6).
347 		 */
348 		public Logger getParentLogger() {
349 			throw new UnsupportedOperationException();
350 		}
351 
352 	}
353 }