View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbutils.wrappers;
18  
19  import java.io.InputStream;
20  import java.io.Reader;
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.math.BigDecimal;
24  import java.net.URL;
25  import java.sql.Blob;
26  import java.sql.Clob;
27  import java.sql.Date;
28  import java.sql.Ref;
29  import java.sql.ResultSet;
30  import java.sql.Time;
31  import java.sql.Timestamp;
32  import java.util.HashMap;
33  import java.util.Map;
34  
35  import org.apache.commons.dbutils.ProxyFactory;
36  
37  /**
38   * Decorates a <code>ResultSet</code> with checks for a SQL NULL value on each
39   * <code>getXXX</code> method. If a column value obtained by a
40   * <code>getXXX</code> method is not SQL NULL, the column value is returned. If
41   * the column value is SQL null, an alternate value is returned. The alternate
42   * value defaults to the Java <code>null</code> value, which can be overridden
43   * for instances of the class.
44   *
45   * <p>
46   * Usage example:
47   * <blockquote>
48   * <pre>
49   * Connection conn = // somehow get a connection
50   * Statement stmt = conn.createStatement();
51   * ResultSet rs = stmt.executeQuery("SELECT col1, col2 FROM table1");
52   *
53   * // Wrap the result set for SQL NULL checking
54   * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(rs);
55   * wrapper.setNullString("---N/A---"); // Set null string
56   * wrapper.setNullInt(-999); // Set null integer
57   * rs = ProxyFactory.instance().createResultSet(wrapper);
58   *
59   * while (rs.next()) {
60   *     // If col1 is SQL NULL, value returned will be "---N/A---"
61   *     String col1 = rs.getString("col1");
62   *     // If col2 is SQL NULL, value returned will be -999
63   *     int col2 = rs.getInt("col2");
64   * }
65   * rs.close();
66   * </pre>
67   * </blockquote>
68   * </p>
69   * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
70   */
71  public class SqlNullCheckedResultSet implements InvocationHandler {
72  
73      /**
74       * Maps normal method names (ie. "getBigDecimal") to the corresponding null
75       * Method object (ie. getNullBigDecimal).
76       */
77      private static final Map<String, Method> nullMethods = new HashMap<String, Method>();
78  
79      /**
80       * The {@code getNull} string prefix.
81       * @since 1.4
82       */
83      private static final String GET_NULL_PREFIX = "getNull";
84  
85      static {
86          Method[] methods = SqlNullCheckedResultSet.class.getMethods();
87          for (int i = 0; i < methods.length; i++) {
88              String methodName = methods[i].getName();
89  
90              if (methodName.startsWith(GET_NULL_PREFIX)) {
91                  String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
92                  nullMethods.put(normalName, methods[i]);
93              }
94          }
95      }
96  
97      /**
98       * The factory to create proxies with.
99       */
100     private static final ProxyFactory factory = ProxyFactory.instance();
101 
102     /**
103      * Wraps the <code>ResultSet</code> in an instance of this class.  This is
104      * equivalent to:
105      * <pre>
106      * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(rs));
107      * </pre>
108      *
109      * @param rs The <code>ResultSet</code> to wrap.
110      * @return wrapped ResultSet
111      */
112     public static ResultSet wrap(ResultSet rs) {
113         return factory.createResultSet(new SqlNullCheckedResultSet(rs));
114     }
115 
116     private InputStream nullAsciiStream = null;
117     private BigDecimal nullBigDecimal = null;
118     private InputStream nullBinaryStream = null;
119     private Blob nullBlob = null;
120     private boolean nullBoolean = false;
121     private byte nullByte = 0;
122     private byte[] nullBytes = null;
123     private Reader nullCharacterStream = null;
124     private Clob nullClob = null;
125     private Date nullDate = null;
126     private double nullDouble = 0.0;
127     private float nullFloat = 0.0f;
128     private int nullInt = 0;
129     private long nullLong = 0;
130     private Object nullObject = null;
131     private Ref nullRef = null;
132     private short nullShort = 0;
133     private String nullString = null;
134     private Time nullTime = null;
135     private Timestamp nullTimestamp = null;
136     private URL nullURL = null;
137 
138     /**
139      * The wrapped result.
140      */
141     private final ResultSet rs;
142 
143     /**
144      * Constructs a new instance of
145      * <code>SqlNullCheckedResultSet</code>
146      * to wrap the specified <code>ResultSet</code>.
147      * @param rs ResultSet to wrap
148      */
149     public SqlNullCheckedResultSet(ResultSet rs) {
150         super();
151         this.rs = rs;
152     }
153 
154     /**
155      * Returns the value when a SQL null is encountered as the result of
156      * invoking a <code>getAsciiStream</code> method.
157      *
158      * @return the value
159      */
160     public InputStream getNullAsciiStream() {
161         return this.nullAsciiStream;
162     }
163 
164     /**
165      * Returns the value when a SQL null is encountered as the result of
166      * invoking a <code>getBigDecimal</code> method.
167      *
168      * @return the value
169      */
170     public BigDecimal getNullBigDecimal() {
171         return this.nullBigDecimal;
172     }
173 
174     /**
175      * Returns the value when a SQL null is encountered as the result of
176      * invoking a <code>getBinaryStream</code> method.
177      *
178      * @return the value
179      */
180     public InputStream getNullBinaryStream() {
181         return this.nullBinaryStream;
182     }
183 
184     /**
185      * Returns the value when a SQL null is encountered as the result of
186      * invoking a <code>getBlob</code> method.
187      *
188      * @return the value
189      */
190     public Blob getNullBlob() {
191         return this.nullBlob;
192     }
193 
194     /**
195      * Returns the value when a SQL null is encountered as the result of
196      * invoking a <code>getBoolean</code> method.
197      *
198      * @return the value
199      */
200     public boolean getNullBoolean() {
201         return this.nullBoolean;
202     }
203 
204     /**
205      * Returns the value when a SQL null is encountered as the result of
206      * invoking a <code>getByte</code> method.
207      *
208      * @return the value
209      */
210     public byte getNullByte() {
211         return this.nullByte;
212     }
213 
214     /**
215      * Returns the value when a SQL null is encountered as the result of
216      * invoking a <code>getBytes</code> method.
217      *
218      * @return the value
219      */
220     public byte[] getNullBytes() {
221         if (this.nullBytes == null) {
222             return null;
223         }
224         byte[] copy = new byte[this.nullBytes.length];
225         System.arraycopy(this.nullBytes, 0, copy, 0, this.nullBytes.length);
226         return copy;
227     }
228 
229     /**
230      * Returns the value when a SQL null is encountered as the result of
231      * invoking a <code>getCharacterStream</code> method.
232      *
233      * @return the value
234      */
235     public Reader getNullCharacterStream() {
236         return this.nullCharacterStream;
237     }
238 
239     /**
240      * Returns the value when a SQL null is encountered as the result of
241      * invoking a <code>getClob</code> method.
242      *
243      * @return the value
244      */
245     public Clob getNullClob() {
246         return this.nullClob;
247     }
248 
249     /**
250      * Returns the value when a SQL null is encountered as the result of
251      * invoking a <code>getDate</code> method.
252      *
253      * @return the value
254      */
255     public Date getNullDate() {
256         return this.nullDate;
257     }
258 
259     /**
260      * Returns the value when a SQL null is encountered as the result of
261      * invoking a <code>getDouble</code> method.
262      *
263      * @return the value
264      */
265     public double getNullDouble() {
266         return this.nullDouble;
267     }
268 
269     /**
270      * Returns the value when a SQL null is encountered as the result of
271      * invoking a <code>getFloat</code> method.
272      *
273      * @return the value
274      */
275     public float getNullFloat() {
276         return this.nullFloat;
277     }
278 
279     /**
280      * Returns the value when a SQL null is encountered as the result of
281      * invoking a <code>getInt</code> method.
282      *
283      * @return the value
284      */
285     public int getNullInt() {
286         return this.nullInt;
287     }
288 
289     /**
290      * Returns the value when a SQL null is encountered as the result of
291      * invoking a <code>getLong</code> method.
292      *
293      * @return the value
294      */
295     public long getNullLong() {
296         return this.nullLong;
297     }
298 
299     /**
300      * Returns the value when a SQL null is encountered as the result of
301      * invoking a <code>getObject</code> method.
302      *
303      * @return the value
304      */
305     public Object getNullObject() {
306         return this.nullObject;
307     }
308 
309     /**
310      * Returns the value when a SQL null is encountered as the result of
311      * invoking a <code>getRef</code> method.
312      *
313      * @return the value
314      */
315     public Ref getNullRef() {
316         return this.nullRef;
317     }
318 
319     /**
320      * Returns the value when a SQL null is encountered as the result of
321      * invoking a <code>getShort</code> method.
322      *
323      * @return the value
324      */
325     public short getNullShort() {
326         return this.nullShort;
327     }
328 
329     /**
330      * Returns the value when a SQL null is encountered as the result of
331      * invoking a <code>getString</code> method.
332      *
333      * @return the value
334      */
335     public String getNullString() {
336         return this.nullString;
337     }
338 
339     /**
340      * Returns the value when a SQL null is encountered as the result of
341      * invoking a <code>getTime</code> method.
342      *
343      * @return the value
344      */
345     public Time getNullTime() {
346         return this.nullTime;
347     }
348 
349     /**
350      * Returns the value when a SQL null is encountered as the result of
351      * invoking a <code>getTimestamp</code> method.
352      *
353      * @return the value
354      */
355     public Timestamp getNullTimestamp() {
356         return this.nullTimestamp;
357     }
358 
359     /**
360      * Returns the value when a SQL null is encountered as the result of
361      * invoking a <code>getURL</code> method.
362      *
363      * @return the value
364      */
365     public URL getNullURL() {
366         return this.nullURL;
367     }
368 
369     /**
370      * Intercepts calls to <code>get*</code> methods and calls the appropriate
371      * <code>getNull*</code> method if the <code>ResultSet</code> returned
372      * <code>null</code>.
373      *
374      *  @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
375      * @param proxy Not used; all method calls go to the internal result set
376      * @param method The method to invoke on the result set
377      * @param args The arguments to pass to the result set
378      * @return null checked result
379      * @throws Throwable error
380      */
381     @Override
382     public Object invoke(Object proxy, Method method, Object[] args)
383         throws Throwable {
384 
385         Object result = method.invoke(this.rs, args);
386 
387         Method nullMethod = nullMethods.get(method.getName());
388 
389         // Check nullMethod != null first so that we don't call wasNull()
390         // before a true getter method was invoked on the ResultSet.
391         return (nullMethod != null && this.rs.wasNull())
392             ? nullMethod.invoke(this, (Object[]) null)
393             : result;
394     }
395 
396     /**
397      * Sets the value to return when a SQL null is encountered as the result of
398      * invoking a <code>getAsciiStream</code> method.
399      *
400      * @param nullAsciiStream the value
401      */
402     public void setNullAsciiStream(InputStream nullAsciiStream) {
403         this.nullAsciiStream = nullAsciiStream;
404     }
405 
406     /**
407      * Sets the value to return when a SQL null is encountered as the result of
408      * invoking a <code>getBigDecimal</code> method.
409      *
410      * @param nullBigDecimal the value
411      */
412     public void setNullBigDecimal(BigDecimal nullBigDecimal) {
413         this.nullBigDecimal = nullBigDecimal;
414     }
415 
416     /**
417      * Sets the value to return when a SQL null is encountered as the result of
418      * invoking a <code>getBinaryStream</code> method.
419      *
420      * @param nullBinaryStream the value
421      */
422     public void setNullBinaryStream(InputStream nullBinaryStream) {
423         this.nullBinaryStream = nullBinaryStream;
424     }
425 
426     /**
427      * Sets the value to return when a SQL null is encountered as the result of
428      * invoking a <code>getBlob</code> method.
429      *
430      * @param nullBlob the value
431      */
432     public void setNullBlob(Blob nullBlob) {
433         this.nullBlob = nullBlob;
434     }
435 
436     /**
437      * Sets the value to return when a SQL null is encountered as the result of
438      * invoking a <code>getBoolean</code> method.
439      *
440      * @param nullBoolean the value
441      */
442     public void setNullBoolean(boolean nullBoolean) {
443         this.nullBoolean = nullBoolean;
444     }
445 
446     /**
447      * Sets the value to return when a SQL null is encountered as the result of
448      * invoking a <code>getByte</code> method.
449      *
450      * @param nullByte the value
451      */
452     public void setNullByte(byte nullByte) {
453         this.nullByte = nullByte;
454     }
455 
456     /**
457      * Sets the value to return when a SQL null is encountered as the result of
458      * invoking a <code>getBytes</code> method.
459      *
460      * @param nullBytes the value
461      */
462     public void setNullBytes(byte[] nullBytes) {
463         byte[] copy = new byte[nullBytes.length];
464         System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
465         this.nullBytes = copy;
466     }
467 
468     /**
469      * Sets the value to return when a SQL null is encountered as the result of
470      * invoking a <code>getCharacterStream</code> method.
471      *
472      * @param nullCharacterStream the value
473      */
474     public void setNullCharacterStream(Reader nullCharacterStream) {
475         this.nullCharacterStream = nullCharacterStream;
476     }
477 
478     /**
479      * Sets the value to return when a SQL null is encountered as the result of
480      * invoking a <code>getClob</code> method.
481      *
482      * @param nullClob the value
483      */
484     public void setNullClob(Clob nullClob) {
485         this.nullClob = nullClob;
486     }
487 
488     /**
489      * Sets the value to return when a SQL null is encountered as the result of
490      * invoking a <code>getDate</code> method.
491      *
492      * @param nullDate the value
493      */
494     public void setNullDate(Date nullDate) {
495         this.nullDate = nullDate;
496     }
497 
498     /**
499      * Sets the value to return when a SQL null is encountered as the result of
500      * invoking a <code>getDouble</code> method.
501      *
502      * @param nullDouble the value
503      */
504     public void setNullDouble(double nullDouble) {
505         this.nullDouble = nullDouble;
506     }
507 
508     /**
509      * Sets the value to return when a SQL null is encountered as the result of
510      * invoking a <code>getFloat</code> method.
511      *
512      * @param nullFloat the value
513      */
514     public void setNullFloat(float nullFloat) {
515         this.nullFloat = nullFloat;
516     }
517 
518     /**
519      * Sets the value to return when a SQL null is encountered as the result of
520      * invoking a <code>getInt</code> method.
521      *
522      * @param nullInt the value
523      */
524     public void setNullInt(int nullInt) {
525         this.nullInt = nullInt;
526     }
527 
528     /**
529      * Sets the value to return when a SQL null is encountered as the result of
530      * invoking a <code>getLong</code> method.
531      *
532      * @param nullLong the value
533      */
534     public void setNullLong(long nullLong) {
535         this.nullLong = nullLong;
536     }
537 
538     /**
539      * Sets the value to return when a SQL null is encountered as the result of
540      * invoking a <code>getObject</code> method.
541      *
542      * @param nullObject the value
543      */
544     public void setNullObject(Object nullObject) {
545         this.nullObject = nullObject;
546     }
547 
548     /**
549      * Sets the value to return when a SQL null is encountered as the result of
550      * invoking a <code>getRef</code> method.
551      *
552      * @param nullRef the value
553      */
554     public void setNullRef(Ref nullRef) {
555         this.nullRef = nullRef;
556     }
557 
558     /**
559      * Sets the value to return when a SQL null is encountered as the result of
560      * invoking a <code>getShort</code> method.
561      *
562      * @param nullShort the value
563      */
564     public void setNullShort(short nullShort) {
565         this.nullShort = nullShort;
566     }
567 
568     /**
569      * Sets the value to return when a SQL null is encountered as the result of
570      * invoking a <code>getString</code> method.
571      *
572      * @param nullString the value
573      */
574     public void setNullString(String nullString) {
575         this.nullString = nullString;
576     }
577 
578     /**
579      * Sets the value to return when a SQL null is encountered as the result of
580      * invoking a <code>getTime</code> method.
581      *
582      * @param nullTime the value
583      */
584     public void setNullTime(Time nullTime) {
585         this.nullTime = nullTime;
586     }
587 
588     /**
589      * Sets the value to return when a SQL null is encountered as the result of
590      * invoking a <code>getTimestamp</code> method.
591      *
592      * @param nullTimestamp the value
593      */
594     public void setNullTimestamp(Timestamp nullTimestamp) {
595         this.nullTimestamp = nullTimestamp;
596     }
597 
598     /**
599      * Sets the value to return when a SQL null is encountered as the result of
600      * invoking a <code>getURL</code> method.
601      *
602      * @param nullURL the value
603      */
604     public void setNullURL(URL nullURL) {
605         this.nullURL = nullURL;
606     }
607 
608 }