1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.resource.jdbc;
39  
40  import com.gargoylesoftware.base.util.ProxyUtil;
41  import java.lang.reflect.InvocationHandler;
42  import java.lang.reflect.Method;
43  import java.lang.reflect.Proxy;
44  import java.sql.CallableStatement;
45  import java.sql.Connection;
46  import java.sql.DatabaseMetaData;
47  import java.sql.PreparedStatement;
48  import java.sql.SQLException;
49  import java.sql.Statement;
50  import java.util.ArrayList;
51  import java.util.List;
52  import junit.framework.Test;
53  import junit.framework.TestCase;
54  import junit.framework.TestSuite;
55  
56  /***
57   *  Tests for ConnectionWrapper
58   *
59   * @version  $Revision: 1.5 $
60   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
61   */
62  public class ConnectionWrapperTest extends TestCase {
63  
64      /***
65       *  Create a test
66       *
67       * @param  name The name of the test
68       */
69      public ConnectionWrapperTest( final String name ) {
70          super( name );
71      }
72  
73  
74      /***
75       *  Return a test suite containing all the tests for this class
76       *
77       * @return  The test suite
78       */
79      public static Test suite() {
80          final String excludedMethods[] = {
81                  "close", "isClosed", "getMetaData"
82                  };
83          final String wrappedMethods[] = {
84                  "createStatement", "prepareStatement", "prepareCall"
85                  };
86          final TestSuite suite = WrapperTestCase.getWrapperTestSuite(
87                  Connection.class, ConnectionWrapper.class, excludedMethods, wrappedMethods );
88  
89          suite.addTest( new TestSuite( ConnectionWrapperTest.class ) );
90  
91          return suite;
92      }
93  
94  
95      /***
96       *  Try passing null into the constructor
97       */
98      public void testConstructor_Null() {
99          try {
100             new ConnectionWrapper( null );
101             fail( "Expected exception" );
102         }
103         catch( final NullPointerException e ) {
104             // Expected path
105         }
106     }
107 
108 
109     /***
110      *  Test createStatement(). Make sure that it returns a wrapped statement.
111      *  Then make sure that the statement is closed when the connection is
112      *  closed.
113      *
114      * @exception  SQLException If an error occurs
115      */
116     public void testCreateStatement()
117         throws SQLException {
118         final List list = new ArrayList();
119         final Statement statement = (Statement)createTracingProxy( Statement.class, list );
120         final Connection connection = (Connection)ProxyUtil.createProxy(
121                 Connection.class, new Object[][]{{"createStatement", statement}} );
122 
123         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
124         final Statement wrappedStatement = connectionWrapper.createStatement();
125 
126         assertTrue( "statement is wrapped", wrappedStatement instanceof StatementWrapper );
127         assertSame( "wrapped statement", statement, ( (StatementWrapper)wrappedStatement ).getDelegate() );
128 
129         assertEquals( "list.size before close", 0, list.size() );
130         connectionWrapper.close();
131         assertEquals( "list.size after close", 1, list.size() );
132         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
133     }
134 
135 
136     /***
137      *  Test createStatement(). Make sure that it returns a wrapped statement.
138      *  Then make sure that the statement is closed when the connection is
139      *  closed.
140      *
141      * @exception  SQLException If an error occurs
142      */
143     public void testCreateStatement_intint()
144         throws SQLException {
145         final List list = new ArrayList();
146         final Statement statement = (Statement)createTracingProxy( Statement.class, list );
147         final Connection connection = (Connection)ProxyUtil.createProxy(
148                 Connection.class, new Object[][]{{"createStatement", statement}} );
149 
150         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
151         final Statement wrappedStatement = connectionWrapper.createStatement( 1, 1 );
152 
153         assertTrue( "statement is wrapped", wrappedStatement instanceof StatementWrapper );
154         assertSame( "wrapped statement", statement, ( (StatementWrapper)wrappedStatement ).getDelegate() );
155 
156         assertEquals( "list.size before close", 0, list.size() );
157         connectionWrapper.close();
158         assertEquals( "list.size after close", 1, list.size() );
159         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
160     }
161 
162 
163     /***
164      *  Test prepareStatement(). Make sure that it returns a wrapped statement.
165      *  Then make sure that the statement is closed when the connection is
166      *  closed.
167      *
168      * @exception  SQLException If an error occurs
169      */
170     public void testPrepareStatement()
171         throws SQLException {
172         final List list = new ArrayList();
173         final PreparedStatement statement
174                  = (PreparedStatement)createTracingProxy( PreparedStatement.class, list );
175         final Connection connection = (Connection)ProxyUtil.createProxy(
176                 Connection.class, new Object[][]{{"prepareStatement", statement}} );
177 
178         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
179         final Statement wrappedStatement = connectionWrapper.prepareStatement( "foo" );
180 
181         assertTrue( "statement is wrapped", wrappedStatement instanceof PreparedStatementWrapper );
182         assertSame( "wrapped statement", statement, ( (PreparedStatementWrapper)wrappedStatement ).getDelegate() );
183 
184         assertEquals( "list.size before close", 0, list.size() );
185         connectionWrapper.close();
186         assertEquals( "list.size after close", 1, list.size() );
187         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
188     }
189 
190 
191     /***
192      *  Test prepareStatement(). Make sure that it returns a wrapped statement.
193      *  Then make sure that the statement is closed when the connection is
194      *  closed.
195      *
196      * @exception  SQLException If an error occurs
197      */
198     public void testPrepareStatement_StringIntInt()
199         throws SQLException {
200         final List list = new ArrayList();
201         final PreparedStatement statement
202                  = (PreparedStatement)createTracingProxy( PreparedStatement.class, list );
203         final Connection connection = (Connection)ProxyUtil.createProxy(
204                 Connection.class, new Object[][]{{"prepareStatement", statement}} );
205 
206         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
207         final Statement wrappedStatement = connectionWrapper.prepareStatement( "foo", 1, 1 );
208 
209         assertTrue( "statement is wrapped", wrappedStatement instanceof PreparedStatementWrapper );
210         assertSame( "wrapped statement", statement, ( (PreparedStatementWrapper)wrappedStatement ).getDelegate() );
211 
212         assertEquals( "list.size before close", 0, list.size() );
213         connectionWrapper.close();
214         assertEquals( "list.size after close", 1, list.size() );
215         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
216     }
217 
218 
219     /***
220      *  Test prepareCall(). Make sure that it returns a wrapped statement. Then
221      *  make sure that the statement is closed when the connection is closed.
222      *
223      * @exception  SQLException If an error occurs
224      */
225     public void testPrepareCall_String()
226         throws SQLException {
227         final List list = new ArrayList();
228         final CallableStatement statement
229                  = (CallableStatement)createTracingProxy( CallableStatement.class, list );
230         final Connection connection = (Connection)ProxyUtil.createProxy(
231                 Connection.class, new Object[][]{{"prepareCall", statement}} );
232 
233         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
234         final Statement wrappedStatement = connectionWrapper.prepareCall( "foo" );
235 
236         assertTrue( "statement is wrapped", wrappedStatement instanceof CallableStatementWrapper );
237         assertSame( "wrapped statement", statement, ( (CallableStatementWrapper)wrappedStatement ).getDelegate() );
238 
239         assertEquals( "list.size before close", 0, list.size() );
240         connectionWrapper.close();
241         assertEquals( "list.size after close", 1, list.size() );
242         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
243     }
244 
245 
246     /***
247      *  Test prepareCall(). Make sure that it returns a wrapped statement. Then
248      *  make sure that the statement is closed when the connection is closed.
249      *
250      * @exception  SQLException If an error occurs
251      */
252     public void testPrepareCall_StringIntInt()
253         throws SQLException {
254         final List list = new ArrayList();
255         final CallableStatement statement
256                  = (CallableStatement)createTracingProxy( CallableStatement.class, list );
257         final Connection connection = (Connection)ProxyUtil.createProxy(
258                 Connection.class, new Object[][]{{"prepareCall", statement}} );
259 
260         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
261         final Statement wrappedStatement = connectionWrapper.prepareCall( "foo", 1, 1 );
262 
263         assertTrue( "statement is wrapped", wrappedStatement instanceof CallableStatementWrapper );
264         assertSame( "wrapped statement", statement, ( (CallableStatementWrapper)wrappedStatement ).getDelegate() );
265 
266         assertEquals( "list.size before close", 0, list.size() );
267         connectionWrapper.close();
268         assertEquals( "list.size after close", 1, list.size() );
269         assertEquals( "closeMethod", "close", ( (Method)list.get( 0 ) ).getName() );
270     }
271 
272 
273     /***
274      *  Test the getMetaData() method to ensure that:
275      *  <ol>
276      *    <li> The DatabaseMetaData object is wrapped
277      *    <li> The DatabaseMetaData object returns the wrapped connection when
278      *    getConnection is called on it.
279      *  </ol>
280      *
281      *
282      * @exception  Exception If an error occurs
283      */
284     public void testGetDatabaseMetaData()
285         throws Exception {
286         final DatabaseMetaData metaData
287                  = (DatabaseMetaData)ProxyUtil.createProxy( DatabaseMetaData.class );
288         final Connection connection = (Connection)ProxyUtil.createProxy(
289                 Connection.class, new Object[][]{{"getMetaData", metaData}} );
290 
291         final ConnectionWrapper connectionWrapper = new ConnectionWrapper( connection );
292         final DatabaseMetaData wrappedMetaData = connectionWrapper.getMetaData();
293 
294         assertTrue( "metadata is wrapped", wrappedMetaData instanceof DatabaseMetaDataWrapper );
295         assertSame( "wrapped metaData", metaData, ( (DatabaseMetaDataWrapper)wrappedMetaData ).getDelegate() );
296 
297         assertEquals( "metaData.getConnection", connectionWrapper, wrappedMetaData.getConnection() );
298 
299         connectionWrapper.close();
300 
301         try {
302             wrappedMetaData.getConnection();
303             fail( "Expected exception" );
304         }
305         catch( final SQLException e ) {
306             // Expected path
307         }
308     }
309 
310 
311     /***
312      *  Try closing the connection twice
313      *
314      * @exception  Exception If an error occurs
315      */
316     public void testCloseTwice()
317         throws Exception {
318         final Connection connection = (Connection)ProxyUtil.createProxy( Connection.class );
319         final ConnectionWrapper wrapper = new ConnectionWrapper( connection );
320 
321         wrapper.close();
322         try {
323             wrapper.close();
324             fail( "Expected exception when calling close() on a closed object" );
325         }
326         catch( final AlreadyClosedException e ) {
327             // Expected path
328         }
329     }
330 
331 
332     /***
333      *  Regression test for bug where isClosed() would throw a
334      *  NullPointerException if the connection was already closed
335      *
336      * @exception  Exception If an error occurs
337      */
338     public void testIsClosedOnClosedConnection()
339         throws Exception {
340         final Connection connection = (Connection)ProxyUtil.createProxy( Connection.class );
341         final ConnectionWrapper wrapper = new ConnectionWrapper( connection );
342 
343         wrapper.close();
344         assertTrue( wrapper.isClosed() );
345     }
346 
347 
348     /***
349      *  Test releaseAll where one of the statements has already been closed
350      *
351      * @exception  Exception If an error occurs
352      */
353     public void testCloseWhenStatementAlreadyClosed()
354         throws Exception {
355         final Statement statement
356                  = (Statement)ProxyUtil.createProxy( Statement.class );
357 
358         final Connection connection
359                  = (Connection)ProxyUtil.createProxy( Connection.class,
360                 new Object[][]{{"createStatement", new StatementWrapper( statement )}} );
361 
362         final ConnectionWrapper connectionWrapper
363                  = new ConnectionWrapper( connection );
364 
365         final StatementWrapper statementWrapper
366                  = (StatementWrapper)connectionWrapper.createStatement();
367         statementWrapper.close();
368 
369         connectionWrapper.close();
370     }
371 
372 
373     /***
374      *  Create a proxy object that tracks what methods were called
375      *
376      * @param  clazz The interface used to create the proxy
377      * @param  list The list that will contain all the method objects
378      * @return  The new proxy object
379      */
380     private Object createTracingProxy( final Class clazz, final List list ) {
381         final InvocationHandler handler =
382             new InvocationHandler() {
383                 public Object invoke( Object proxy, Method method, Object[] methodArgs ) {
384                     list.add( method );
385                     return null;
386                 }
387             };
388         return Proxy.newProxyInstance(
389                 clazz.getClassLoader(),
390                 new Class[]{clazz},
391                 handler );
392     }
393 }
394