001    /**
002     * Copyright 2007-2008 Arthur Blake
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     *    http://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    package net.sf.log4jdbc;
017    
018    import java.sql.*;
019    import java.util.Map;
020    import java.util.Properties;
021    
022    /**
023     * Wraps a JDBC Connection and reports method calls, returns and exceptions.
024     *
025     * This version is for jdbc 4.0.
026     *
027     * @see ConnectionSpy for the jdbc 3 version.
028     *
029     * @author Arthur Blake
030     */
031    public class ConnectionSpy implements Connection, Spy
032    {
033      private Connection realConnection;
034    
035      private SpyLogDelegator log;
036    
037      private int connectionNumber;
038      private static int lastConnectionNumber = 0;
039      private static final Object connectionNumberLock = new Object();
040    
041      /**
042       * Create a new ConnectionSpy that wraps a given Connection.
043       *
044       * @param realConnection "real" Connection that this ConnectionSpy wraps.
045       */
046      public ConnectionSpy(Connection realConnection)
047      {
048        setRdbmsSpecifics(DriverSpy.defaultRdbmsSpecifics); // just in case it's not initialized
049        if (realConnection == null)
050        {
051          throw new IllegalArgumentException("Must pass in a non null real Connection");
052        }
053        this.realConnection = realConnection;
054        log = SpyLogFactory.getSpyLogDelegator();
055    
056        synchronized (connectionNumberLock)
057        {
058          connectionNumber = ++lastConnectionNumber;
059        }
060      }
061    
062      /**
063       * Create a new ConnectionSpy that wraps a given Connection.
064       *
065       * @param realConnection "real" Connection that this ConnectionSpy wraps.
066       * @param rdbmsSpecifics the RdbmsSpecifics object for formatting logging appropriate for the Rdbms used.
067       */
068      public ConnectionSpy(Connection realConnection, RdbmsSpecifics rdbmsSpecifics)
069      {
070        setRdbmsSpecifics(rdbmsSpecifics);
071        if (realConnection == null)
072        {
073          throw new IllegalArgumentException("Must pass in a non null real Connection");
074        }
075        this.realConnection = realConnection;
076        log = SpyLogFactory.getSpyLogDelegator();
077    
078        synchronized (connectionNumberLock)
079        {
080          connectionNumber = ++lastConnectionNumber;
081        }
082      }
083    
084    
085      private RdbmsSpecifics rdbmsSpecifics;
086    
087      /**
088       * Set the RdbmsSpecifics object for formatting logging appropriate for the Rdbms used on this connection.
089       *
090       * @param rdbmsSpecifics the RdbmsSpecifics object for formatting logging appropriate for the Rdbms used.
091       */
092      void setRdbmsSpecifics(RdbmsSpecifics rdbmsSpecifics)
093      {
094        this.rdbmsSpecifics = rdbmsSpecifics;
095      }
096    
097      /**
098       * Get the RdbmsSpecifics object for formatting logging appropriate for the Rdbms used on this connection.
099       *
100       * @return the RdbmsSpecifics object for formatting logging appropriate for the Rdbms used.
101       */
102      RdbmsSpecifics getRdbmsSpecifics()
103      {
104        return rdbmsSpecifics;
105      }
106    
107      public int getConnectionNumber()
108      {
109        return connectionNumber;
110      }
111    
112      public String getClassType()
113      {
114        return "Connection";
115      }
116    
117      protected void reportException(String methodCall, SQLException exception, String sql)
118      {
119        log.exceptionOccured(this, methodCall, exception, sql, -1L);
120      }
121    
122      protected void reportException(String methodCall, SQLException exception)
123      {
124        log.exceptionOccured(this, methodCall, exception, null, -1L);
125      }
126    
127      protected void reportAllReturns(String methodCall, String returnValue)
128      {
129        log.methodReturned(this, methodCall, returnValue);
130      }
131    
132      private boolean reportReturn(String methodCall, boolean value)
133      {
134        reportAllReturns(methodCall, "" + value);
135        return value;
136      }
137    
138      private int reportReturn(String methodCall, int value)
139      {
140        reportAllReturns(methodCall, "" + value);
141        return value;
142      }
143    
144      private Object reportReturn(String methodCall, Object value)
145      {
146        reportAllReturns(methodCall, "" + value);
147        return value;
148      }
149    
150      private void reportReturn(String methodCall)
151      {
152        reportAllReturns(methodCall, "");
153      }
154    
155      // forwarding methods
156    
157      public boolean isClosed() throws SQLException
158      {
159        String methodCall = "isClosed()";
160        try
161        {
162          return reportReturn(methodCall, (realConnection.isClosed()));
163        }
164        catch (SQLException s)
165        {
166          reportException(methodCall, s);
167          throw s;
168        }
169      }
170    
171      public SQLWarning getWarnings() throws SQLException
172      {
173        String methodCall = "getWarnings()";
174        try
175        {
176          return (SQLWarning) reportReturn(methodCall, realConnection.getWarnings());
177        }
178        catch (SQLException s)
179        {
180          reportException(methodCall, s);
181          throw s;
182        }
183      }
184    
185      public Savepoint setSavepoint() throws SQLException
186      {
187        String methodCall = "setSavepoint()";
188        try
189        {
190          return (Savepoint) reportReturn(methodCall, realConnection.setSavepoint());
191        }
192        catch (SQLException s)
193        {
194          reportException(methodCall, s);
195          throw s;
196        }
197      }
198    
199      public void releaseSavepoint(Savepoint savepoint) throws SQLException
200      {
201        String methodCall = "releaseSavepoint(" + savepoint + ")";
202        try
203        {
204          realConnection.releaseSavepoint(savepoint);
205        }
206        catch (SQLException s)
207        {
208          reportException(methodCall, s);
209          throw s;
210        }
211        reportReturn(methodCall);
212      }
213    
214      public void rollback(Savepoint savepoint) throws SQLException
215      {
216        String methodCall = "rollback(" + savepoint + ")";
217        try
218        {
219          realConnection.rollback(savepoint);
220        }
221        catch (SQLException s)
222        {
223          reportException(methodCall, s);
224          throw s;
225        }
226        reportReturn(methodCall);
227      }
228    
229      public DatabaseMetaData getMetaData() throws SQLException
230      {
231        String methodCall = "getMetaData()";
232        try
233        {
234          return (DatabaseMetaData) reportReturn(methodCall, realConnection.getMetaData());
235        }
236        catch (SQLException s)
237        {
238          reportException(methodCall, s);
239          throw s;
240        }
241      }
242    
243      public void clearWarnings() throws SQLException
244      {
245        String methodCall = "clearWarnings()";
246        try
247        {
248          realConnection.clearWarnings();
249        }
250        catch (SQLException s)
251        {
252          reportException(methodCall, s);
253          throw s;
254        }
255        reportReturn(methodCall);
256      }
257    
258      public Statement createStatement() throws SQLException
259      {
260        String methodCall = "createStatement()";
261        try
262        {
263          Statement statement = realConnection.createStatement();
264          return (Statement) reportReturn(methodCall, new StatementSpy(this, statement));
265        }
266        catch (SQLException s)
267        {
268          reportException(methodCall, s);
269          throw s;
270        }
271      }
272    
273      public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
274      {
275        String methodCall = "createStatement(" + resultSetType + ", " + resultSetConcurrency + ")";
276        try
277        {
278          Statement statement = realConnection.createStatement(resultSetType, resultSetConcurrency);
279          return (Statement) reportReturn(methodCall, new StatementSpy(this, statement));
280        }
281        catch (SQLException s)
282        {
283          reportException(methodCall, s);
284          throw s;
285        }
286      }
287    
288      public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
289      {
290        String methodCall = "createStatement(" + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")";
291        try
292        {
293          Statement statement = realConnection.createStatement(resultSetType, resultSetConcurrency,
294            resultSetHoldability);
295          return (Statement) reportReturn(methodCall, new StatementSpy(this, statement));
296        }
297        catch (SQLException s)
298        {
299          reportException(methodCall, s);
300          throw s;
301        }
302      }
303    
304      public void setReadOnly(boolean readOnly) throws SQLException
305      {
306        String methodCall = "setReadOnly(" + readOnly + ")";
307        try
308        {
309          realConnection.setReadOnly(readOnly);
310        }
311        catch (SQLException s)
312        {
313          reportException(methodCall, s);
314          throw s;
315        }
316        reportReturn(methodCall);
317      }
318    
319      public PreparedStatement prepareStatement(String sql) throws SQLException
320      {
321        String methodCall = "prepareStatement(" + sql + ")";
322        try
323        {
324          PreparedStatement statement = realConnection.prepareStatement(sql);
325          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
326        }
327        catch (SQLException s)
328        {
329          reportException(methodCall, s, sql);
330          throw s;
331        }
332      }
333    
334      public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
335      {
336        String methodCall = "prepareStatement(" + sql + ", " + autoGeneratedKeys + ")";
337        try
338        {
339          PreparedStatement statement = realConnection.prepareStatement(sql, autoGeneratedKeys);
340          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
341        }
342        catch (SQLException s)
343        {
344          reportException(methodCall, s, sql);
345          throw s;
346        }
347      }
348    
349      public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
350      {
351        String methodCall = "prepareStatement(" + sql + ", " + resultSetType + ", " + resultSetConcurrency + ")";
352        try
353        {
354          PreparedStatement statement = realConnection.prepareStatement(sql, resultSetType, resultSetConcurrency);
355          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
356        }
357        catch (SQLException s)
358        {
359          reportException(methodCall, s, sql);
360          throw s;
361        }
362      }
363    
364      public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
365                                                int resultSetHoldability) throws SQLException
366      {
367        String methodCall = "prepareStatement(" + sql + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")";
368        try
369        {
370          PreparedStatement statement = realConnection.prepareStatement(sql, resultSetType, resultSetConcurrency,
371            resultSetHoldability);
372          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
373        }
374        catch (SQLException s)
375        {
376          reportException(methodCall, s, sql);
377          throw s;
378        }
379      }
380    
381      public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException
382      {
383        //todo: dump the array here?
384        String methodCall = "prepareStatement(" + sql + ", " + columnIndexes + ")";
385        try
386        {
387          PreparedStatement statement = realConnection.prepareStatement(sql, columnIndexes);
388          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
389        }
390        catch (SQLException s)
391        {
392          reportException(methodCall, s, sql);
393          throw s;
394        }
395      }
396    
397      public Savepoint setSavepoint(String name) throws SQLException
398      {
399        String methodCall = "setSavepoint(" + name + ")";
400        try
401        {
402          return (Savepoint) reportReturn(methodCall, realConnection.setSavepoint(name));
403        }
404        catch (SQLException s)
405        {
406          reportException(methodCall, s);
407          throw s;
408        }
409      }
410    
411      public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException
412      {
413        //todo: dump the array here?
414        String methodCall = "prepareStatement(" + sql + ", " + columnNames + ")";
415        try
416        {
417          PreparedStatement statement = realConnection.prepareStatement(sql, columnNames);
418          return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
419        }
420        catch (SQLException s)
421        {
422          reportException(methodCall, s, sql);
423          throw s;
424        }
425      }
426    
427      public Clob createClob() throws SQLException {
428        String methodCall = "createClob()";
429        try
430        {
431          return (Clob) reportReturn(methodCall, realConnection.createClob());
432        }
433        catch (SQLException s)
434        {
435          reportException(methodCall, s);
436          throw s;
437        }
438      }
439    
440      public Blob createBlob() throws SQLException {
441        String methodCall = "createBlob()";
442        try
443        {
444          return (Blob) reportReturn(methodCall, realConnection.createBlob());
445        }
446        catch (SQLException s)
447        {
448          reportException(methodCall, s);
449          throw s;
450        }
451      }
452    
453      public NClob createNClob() throws SQLException {
454        String methodCall = "createNClob()";
455        try
456        {
457          return (NClob) reportReturn(methodCall, realConnection.createNClob());
458        }
459        catch (SQLException s)
460        {
461          reportException(methodCall, s);
462          throw s;
463        }
464      }
465    
466      public SQLXML createSQLXML() throws SQLException {
467        String methodCall = "createSQLXML()";
468        try
469        {
470          return (SQLXML) reportReturn(methodCall, realConnection.createSQLXML());
471        }
472        catch (SQLException s)
473        {
474          reportException(methodCall, s);
475          throw s;
476        }
477      }
478    
479      public boolean isValid(int timeout) throws SQLException {
480        String methodCall = "isValid(" + timeout + ")";
481        try
482        {
483          return reportReturn(methodCall,realConnection.isValid(timeout));
484        }
485        catch (SQLException s)
486        {
487          reportException(methodCall, s);
488          throw s;
489        }
490      }
491    
492      public void setClientInfo(String name, String value) throws SQLClientInfoException {
493        String methodCall = "setClientInfo(" + name + ", " + value + ")";
494        try
495        {
496          realConnection.setClientInfo(name,value);
497        }
498        catch (SQLClientInfoException s)
499        {
500          reportException(methodCall, s);
501          throw s;
502        }
503        reportReturn(methodCall);
504      }
505    
506      public void setClientInfo(Properties properties) throws SQLClientInfoException {
507        // todo: dump properties?
508        String methodCall = "setClientInfo(" + properties + ")";
509        try
510        {
511          realConnection.setClientInfo(properties);
512        }
513        catch (SQLClientInfoException s)
514        {
515          reportException(methodCall, s);
516          throw s;
517        }
518        reportReturn(methodCall);
519      }
520    
521      public String getClientInfo(String name) throws SQLException {
522        String methodCall = "getClientInfo(" + name + ")";
523        try
524        {
525          return (String) reportReturn(methodCall,realConnection.getClientInfo(name));
526        }
527        catch (SQLException s)
528        {
529          reportException(methodCall, s);
530          throw s;
531        }
532      }
533    
534      public Properties getClientInfo() throws SQLException {
535        String methodCall = "getClientInfo()";
536        try
537        {
538          return (Properties) reportReturn(methodCall,realConnection.getClientInfo());
539        }
540        catch (SQLException s)
541        {
542          reportException(methodCall, s);
543          throw s;
544        }
545      }
546    
547      public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
548        //todo: dump elements?
549        String methodCall = "createArrayOf(" + typeName + ", " + elements +")";
550        try
551        {
552          return (Array) reportReturn(methodCall,realConnection.createArrayOf(typeName,elements));
553        }
554        catch (SQLException s)
555        {
556          reportException(methodCall, s);
557          throw s;
558        }
559      }
560    
561      public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
562        //todo: dump attributes?
563        String methodCall = "createStruct(" + typeName + ", " + attributes +")";
564        try
565        {
566          return (Struct) reportReturn(methodCall,realConnection.createStruct(typeName, attributes));
567        }
568        catch (SQLException s)
569        {
570          reportException(methodCall, s);
571          throw s;
572        }
573      }
574    
575      public boolean isReadOnly() throws SQLException
576      {
577        String methodCall = "isReadOnly()";
578        try
579        {
580          return reportReturn(methodCall,realConnection.isReadOnly());
581        }
582        catch (SQLException s)
583        {
584          reportException(methodCall, s);
585          throw s;
586        }
587      }
588    
589      public void setHoldability(int holdability) throws SQLException
590      {
591        String methodCall = "setHoldability(" + holdability + ")";
592        try
593        {
594          realConnection.setHoldability(holdability);
595        }
596        catch (SQLException s)
597        {
598          reportException(methodCall, s);
599          throw s;
600        }
601        reportReturn(methodCall);
602      }
603    
604      public CallableStatement prepareCall(String sql) throws SQLException
605      {
606        String methodCall = "prepareCall(" + sql + ")";
607        try
608        {
609          CallableStatement statement = realConnection.prepareCall(sql);
610          return (CallableStatement) reportReturn(methodCall, new CallableStatementSpy(sql, this, statement));
611        }
612        catch (SQLException s)
613        {
614          reportException(methodCall, s, sql);
615          throw s;
616        }
617      }
618    
619      public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
620      {
621        String methodCall = "prepareCall(" + sql + ", " + resultSetType + ", " + resultSetConcurrency + ")";
622        try
623        {
624          CallableStatement statement = realConnection.prepareCall(sql, resultSetType, resultSetConcurrency);
625          return (CallableStatement) reportReturn(methodCall, new CallableStatementSpy(sql, this, statement));
626        }
627        catch (SQLException s)
628        {
629          reportException(methodCall, s, sql);
630          throw s;
631        }
632      }
633    
634      public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
635                                           int resultSetHoldability) throws SQLException
636      {
637        String methodCall = "prepareCall(" + sql + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")";
638        try
639        {
640          CallableStatement statement = realConnection.prepareCall(sql, resultSetType, resultSetConcurrency,
641            resultSetHoldability);
642          return (CallableStatement) reportReturn(methodCall, new CallableStatementSpy(sql, this, statement));
643        }
644        catch (SQLException s)
645        {
646          reportException(methodCall, s, sql);
647          throw s;
648        }
649      }
650    
651      public void setCatalog(String catalog) throws SQLException
652      {
653        String methodCall = "setCatalog(" + catalog + ")";
654        try
655        {
656          realConnection.setCatalog(catalog);
657        }
658        catch (SQLException s)
659        {
660          reportException(methodCall, s);
661          throw s;
662        }
663        reportReturn(methodCall);
664      }
665    
666      public String nativeSQL(String sql) throws SQLException
667      {
668        String methodCall = "nativeSQL(" + sql + ")";
669        try
670        {
671          return (String) reportReturn(methodCall, realConnection.nativeSQL(sql));
672        }
673        catch (SQLException s)
674        {
675          reportException(methodCall, s, sql);
676          throw s;
677        }
678      }
679    
680      public Map<String,Class<?>> getTypeMap() throws SQLException
681      {
682        String methodCall = "getTypeMap()";
683        try
684        {
685          return (Map<String,Class<?>>) reportReturn(methodCall, realConnection.getTypeMap());
686        }
687        catch (SQLException s)
688        {
689          reportException(methodCall, s);
690          throw s;
691        }
692      }
693    
694      public void setAutoCommit(boolean autoCommit) throws SQLException
695      {
696        String methodCall = "setAutoCommit(" + autoCommit + ")";
697        try
698        {
699          realConnection.setAutoCommit(autoCommit);
700        }
701        catch (SQLException s)
702        {
703          reportException(methodCall, s);
704          throw s;
705        }
706        reportReturn(methodCall);
707      }
708    
709      public String getCatalog() throws SQLException
710      {
711        String methodCall = "getCatalog()";
712        try
713        {
714          return (String) reportReturn(methodCall, realConnection.getCatalog());
715        }
716        catch (SQLException s)
717        {
718          reportException(methodCall, s);
719          throw s;
720        }
721      }
722    
723      public void setTypeMap(java.util.Map<String,Class<?>> map) throws SQLException
724      {
725        //todo: dump map??
726        String methodCall = "setTypeMap(" + map + ")";
727        try
728        {
729          realConnection.setTypeMap(map);
730        }
731        catch (SQLException s)
732        {
733          reportException(methodCall, s);
734          throw s;
735        }
736        reportReturn(methodCall);
737      }
738    
739      public void setTransactionIsolation(int level) throws SQLException
740      {
741        String methodCall = "setTransactionIsolation(" + level + ")";
742        try
743        {
744          realConnection.setTransactionIsolation(level);
745        }
746        catch (SQLException s)
747        {
748          reportException(methodCall, s);
749          throw s;
750        }
751        reportReturn(methodCall);
752      }
753    
754      public boolean getAutoCommit() throws SQLException
755      {
756        String methodCall = "getAutoCommit()";
757        try
758        {
759          return reportReturn(methodCall, realConnection.getAutoCommit());
760        }
761        catch (SQLException s)
762        {
763          reportException(methodCall, s);
764          throw s;
765        }
766      }
767    
768      public int getHoldability() throws SQLException
769      {
770        String methodCall = "getHoldability()";
771        try
772        {
773          return reportReturn(methodCall, realConnection.getHoldability());
774        }
775        catch (SQLException s)
776        {
777          reportException(methodCall, s);
778          throw s;
779        }
780      }
781    
782      public int getTransactionIsolation() throws SQLException
783      {
784        String methodCall = "getTransactionIsolation()";
785        try
786        {
787          return reportReturn(methodCall, realConnection.getTransactionIsolation());
788        }
789        catch (SQLException s)
790        {
791          reportException(methodCall, s);
792          throw s;
793        }
794      }
795    
796      public void commit() throws SQLException
797      {
798        String methodCall = "commit()";
799        try
800        {
801          realConnection.commit();
802        }
803        catch (SQLException s)
804        {
805          reportException(methodCall, s);
806          throw s;
807        }
808        reportReturn(methodCall);
809      }
810    
811      public void rollback() throws SQLException
812      {
813        String methodCall = "rollback()";
814        try
815        {
816          realConnection.rollback();
817        }
818        catch (SQLException s)
819        {
820          reportException(methodCall, s);
821          throw s;
822        }
823        reportReturn(methodCall);
824      }
825    
826      public void close() throws SQLException
827      {
828        String methodCall = "close()";
829        try
830        {
831          realConnection.close();
832        }
833        catch (SQLException s)
834        {
835          reportException(methodCall, s);
836          throw s;
837        }
838        reportReturn(methodCall);
839      }
840    
841      public <T> T unwrap(Class<T> iface) throws SQLException {
842        String methodCall = "unwrap(" + (iface==null?"null":iface.getName()) + ")";
843        try
844        {
845          //todo: double check this logic
846          return (T)reportReturn(methodCall, (iface != null && (iface == Connection.class || iface == Spy.class))?(T)this:realConnection.unwrap(iface));
847        }
848        catch (SQLException s)
849        {
850          reportException(methodCall,s);
851          throw s;
852        }
853      }
854    
855      public boolean isWrapperFor(Class<?> iface) throws SQLException
856      {
857        String methodCall = "isWrapperFor(" + (iface==null?"null":iface.getName()) + ")";
858        try
859        {
860          return reportReturn(methodCall, (iface != null && (iface == Connection.class || iface == Spy.class)) ||
861              realConnection.isWrapperFor(iface));
862        }
863        catch (SQLException s)
864        {
865          reportException(methodCall,s);
866          throw s;
867        }
868      }
869    }