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} with checks for a SQL NULL value on each
39   * {@code getXXX} method. If a column value obtained by a
40   * {@code getXXX} 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} 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 resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");
52   *
53   * // Wrap the result set for SQL NULL checking
54   * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);
55   * wrapper.setNullString("---N/A---"); // Set null string
56   * wrapper.setNullInt(-999); // Set null integer
57   * resultSet = ProxyFactory.instance().createResultSet(wrapper);
58   *
59   * while (resultSet.next()) {
60   *     // If col1 is SQL NULL, value returned will be "---N/A---"
61   *     String col1 = resultSet.getString("col1");
62   *     // If col2 is SQL NULL, value returned will be -999
63   *     int col2 = resultSet.getInt("col2");
64   * }
65   * resultSet.close();
66   * </pre>
67   * </blockquote>
68   * &lt;/p&gt;
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> NULL_METHODS = new HashMap<>();
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          final Method[] methods = SqlNullCheckedResultSet.class.getMethods();
87          for (final Method method : methods) {
88              final String methodName = method.getName();
89  
90              if (methodName.startsWith(GET_NULL_PREFIX)) {
91                  final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
92                  NULL_METHODS.put(normalName, method);
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} in an instance of this class.  This is
104      * equivalent to:
105      * <pre>
106      * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(resultSet));
107      * </pre>
108      *
109      * @param resultSet The {@code ResultSet} to wrap.
110      * @return wrapped ResultSet
111      */
112     public static ResultSet wrap(final ResultSet resultSet) {
113         return factory.createResultSet(new SqlNullCheckedResultSet(resultSet));
114     }
115 
116     private InputStream nullAsciiStream;
117     private BigDecimal nullBigDecimal;
118     private InputStream nullBinaryStream;
119     private Blob nullBlob;
120     private boolean nullBoolean;
121     private byte nullByte;
122     private byte[] nullBytes;
123     private Reader nullCharacterStream;
124     private Clob nullClob;
125     private Date nullDate;
126     private double nullDouble;
127     private float nullFloat;
128     private int nullInt;
129     private long nullLong;
130     private Object nullObject;
131     private Ref nullRef;
132     private short nullShort;
133     private String nullString;
134     private Time nullTime;
135     private Timestamp nullTimestamp;
136     private URL nullURL;
137 
138     /**
139      * The wrapped result.
140      */
141     private final ResultSet resultSet;
142 
143     /**
144      * Constructs a new instance of
145      * {@code SqlNullCheckedResultSet}
146      * to wrap the specified {@code ResultSet}.
147      * @param resultSet ResultSet to wrap
148      */
149     public SqlNullCheckedResultSet(final ResultSet resultSet) {
150         this.resultSet = resultSet;
151     }
152 
153     /**
154      * Returns the value when a SQL null is encountered as the result of
155      * invoking a {@code getAsciiStream} method.
156      *
157      * @return the value
158      */
159     public InputStream getNullAsciiStream() {
160         return this.nullAsciiStream;
161     }
162 
163     /**
164      * Returns the value when a SQL null is encountered as the result of
165      * invoking a {@code getBigDecimal} method.
166      *
167      * @return the value
168      */
169     public BigDecimal getNullBigDecimal() {
170         return this.nullBigDecimal;
171     }
172 
173     /**
174      * Returns the value when a SQL null is encountered as the result of
175      * invoking a {@code getBinaryStream} method.
176      *
177      * @return the value
178      */
179     public InputStream getNullBinaryStream() {
180         return this.nullBinaryStream;
181     }
182 
183     /**
184      * Returns the value when a SQL null is encountered as the result of
185      * invoking a {@code getBlob} method.
186      *
187      * @return the value
188      */
189     public Blob getNullBlob() {
190         return this.nullBlob;
191     }
192 
193     /**
194      * Returns the value when a SQL null is encountered as the result of
195      * invoking a {@code getBoolean} method.
196      *
197      * @return the value
198      */
199     public boolean getNullBoolean() {
200         return this.nullBoolean;
201     }
202 
203     /**
204      * Returns the value when a SQL null is encountered as the result of
205      * invoking a {@code getByte} method.
206      *
207      * @return the value
208      */
209     public byte getNullByte() {
210         return this.nullByte;
211     }
212 
213     /**
214      * Returns the value when a SQL null is encountered as the result of
215      * invoking a {@code getBytes} method.
216      *
217      * @return the value
218      */
219     public byte[] getNullBytes() {
220         if (this.nullBytes == null) {
221             return null;
222         }
223         return this.nullBytes.clone();
224     }
225 
226     /**
227      * Returns the value when a SQL null is encountered as the result of
228      * invoking a {@code getCharacterStream} method.
229      *
230      * @return the value
231      */
232     public Reader getNullCharacterStream() {
233         return this.nullCharacterStream;
234     }
235 
236     /**
237      * Returns the value when a SQL null is encountered as the result of
238      * invoking a {@code getClob} method.
239      *
240      * @return the value
241      */
242     public Clob getNullClob() {
243         return this.nullClob;
244     }
245 
246     /**
247      * Returns the value when a SQL null is encountered as the result of
248      * invoking a {@code getDate} method.
249      *
250      * @return the value
251      */
252     public Date getNullDate() {
253         return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;
254     }
255 
256     /**
257      * Returns the value when a SQL null is encountered as the result of
258      * invoking a {@code getDouble} method.
259      *
260      * @return the value
261      */
262     public double getNullDouble() {
263         return this.nullDouble;
264     }
265 
266     /**
267      * Returns the value when a SQL null is encountered as the result of
268      * invoking a {@code getFloat} method.
269      *
270      * @return the value
271      */
272     public float getNullFloat() {
273         return this.nullFloat;
274     }
275 
276     /**
277      * Returns the value when a SQL null is encountered as the result of
278      * invoking a {@code getInt} method.
279      *
280      * @return the value
281      */
282     public int getNullInt() {
283         return this.nullInt;
284     }
285 
286     /**
287      * Returns the value when a SQL null is encountered as the result of
288      * invoking a {@code getLong} method.
289      *
290      * @return the value
291      */
292     public long getNullLong() {
293         return this.nullLong;
294     }
295 
296     /**
297      * Returns the value when a SQL null is encountered as the result of
298      * invoking a {@code getObject} method.
299      *
300      * @return the value
301      */
302     public Object getNullObject() {
303         return this.nullObject;
304     }
305 
306     /**
307      * Returns the value when a SQL null is encountered as the result of
308      * invoking a {@code getRef} method.
309      *
310      * @return the value
311      */
312     public Ref getNullRef() {
313         return this.nullRef;
314     }
315 
316     /**
317      * Returns the value when a SQL null is encountered as the result of
318      * invoking a {@code getShort} method.
319      *
320      * @return the value
321      */
322     public short getNullShort() {
323         return this.nullShort;
324     }
325 
326     /**
327      * Returns the value when a SQL null is encountered as the result of
328      * invoking a {@code getString} method.
329      *
330      * @return the value
331      */
332     public String getNullString() {
333         return this.nullString;
334     }
335 
336     /**
337      * Returns the value when a SQL null is encountered as the result of
338      * invoking a {@code getTime} method.
339      *
340      * @return the value
341      */
342     public Time getNullTime() {
343         return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;
344     }
345 
346     /**
347      * Returns the value when a SQL null is encountered as the result of
348      * invoking a {@code getTimestamp} method.
349      *
350      * @return the value
351      */
352     public Timestamp getNullTimestamp() {
353         if (this.nullTimestamp == null) {
354             return null;
355         }
356 
357         final Timestamp ts = new Timestamp(this.nullTimestamp.getTime());
358         ts.setNanos(this.nullTimestamp.getNanos());
359         return ts;
360     }
361 
362     /**
363      * Returns the value when a SQL null is encountered as the result of
364      * invoking a {@code getURL} method.
365      *
366      * @return the value
367      */
368     public URL getNullURL() {
369         return this.nullURL;
370     }
371 
372     /**
373      * Intercepts calls to {@code get*} methods and calls the appropriate
374      * {@code getNull*} method if the {@code ResultSet} returned
375      * {@code null}.
376      *
377      * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
378      * @param proxy Not used; all method calls go to the internal result set
379      * @param method The method to invoke on the result set
380      * @param args The arguments to pass to the result set
381      * @return null checked result
382      * @throws Throwable error
383      */
384     @Override
385     public Object invoke(final Object proxy, final Method method, final Object[] args)
386         throws Throwable {
387 
388         final Object result = method.invoke(this.resultSet, args);
389 
390         final Method nullMethod = NULL_METHODS.get(method.getName());
391 
392         // Check nullMethod != null first so that we don't call wasNull()
393         // before a true getter method was invoked on the ResultSet.
394         return nullMethod != null && this.resultSet.wasNull()
395             ? nullMethod.invoke(this, (Object[]) null)
396             : result;
397     }
398 
399     /**
400      * Sets the value to return when a SQL null is encountered as the result of
401      * invoking a {@code getAsciiStream} method.
402      *
403      * @param nullAsciiStream the value
404      */
405     public void setNullAsciiStream(final InputStream nullAsciiStream) {
406         this.nullAsciiStream = nullAsciiStream;
407     }
408 
409     /**
410      * Sets the value to return when a SQL null is encountered as the result of
411      * invoking a {@code getBigDecimal} method.
412      *
413      * @param nullBigDecimal the value
414      */
415     public void setNullBigDecimal(final BigDecimal nullBigDecimal) {
416         this.nullBigDecimal = nullBigDecimal;
417     }
418 
419     /**
420      * Sets the value to return when a SQL null is encountered as the result of
421      * invoking a {@code getBinaryStream} method.
422      *
423      * @param nullBinaryStream the value
424      */
425     public void setNullBinaryStream(final InputStream nullBinaryStream) {
426         this.nullBinaryStream = nullBinaryStream;
427     }
428 
429     /**
430      * Sets the value to return when a SQL null is encountered as the result of
431      * invoking a {@code getBlob} method.
432      *
433      * @param nullBlob the value
434      */
435     public void setNullBlob(final Blob nullBlob) {
436         this.nullBlob = nullBlob;
437     }
438 
439     /**
440      * Sets the value to return when a SQL null is encountered as the result of
441      * invoking a {@code getBoolean} method.
442      *
443      * @param nullBoolean the value
444      */
445     public void setNullBoolean(final boolean nullBoolean) {
446         this.nullBoolean = nullBoolean;
447     }
448 
449     /**
450      * Sets the value to return when a SQL null is encountered as the result of
451      * invoking a {@code getByte} method.
452      *
453      * @param nullByte the value
454      */
455     public void setNullByte(final byte nullByte) {
456         this.nullByte = nullByte;
457     }
458 
459     /**
460      * Sets the value to return when a SQL null is encountered as the result of
461      * invoking a {@code getBytes} method.
462      *
463      * @param nullBytes the value
464      */
465     public void setNullBytes(final byte[] nullBytes) {
466         if (nullBytes != null) {
467             this.nullBytes = nullBytes.clone();
468         } else {
469             this.nullBytes = null;
470         }
471     }
472 
473     /**
474      * Sets the value to return when a SQL null is encountered as the result of
475      * invoking a {@code getCharacterStream} method.
476      *
477      * @param nullCharacterStream the value
478      */
479     public void setNullCharacterStream(final Reader nullCharacterStream) {
480         this.nullCharacterStream = nullCharacterStream;
481     }
482 
483     /**
484      * Sets the value to return when a SQL null is encountered as the result of
485      * invoking a {@code getClob} method.
486      *
487      * @param nullClob the value
488      */
489     public void setNullClob(final Clob nullClob) {
490         this.nullClob = nullClob;
491     }
492 
493     /**
494      * Sets the value to return when a SQL null is encountered as the result of
495      * invoking a {@code getDate} method.
496      *
497      * @param nullDate the value
498      */
499     public void setNullDate(final Date nullDate) {
500         this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;
501     }
502 
503     /**
504      * Sets the value to return when a SQL null is encountered as the result of
505      * invoking a {@code getDouble} method.
506      *
507      * @param nullDouble the value
508      */
509     public void setNullDouble(final double nullDouble) {
510         this.nullDouble = nullDouble;
511     }
512 
513     /**
514      * Sets the value to return when a SQL null is encountered as the result of
515      * invoking a {@code getFloat} method.
516      *
517      * @param nullFloat the value
518      */
519     public void setNullFloat(final float nullFloat) {
520         this.nullFloat = nullFloat;
521     }
522 
523     /**
524      * Sets the value to return when a SQL null is encountered as the result of
525      * invoking a {@code getInt} method.
526      *
527      * @param nullInt the value
528      */
529     public void setNullInt(final int nullInt) {
530         this.nullInt = nullInt;
531     }
532 
533     /**
534      * Sets the value to return when a SQL null is encountered as the result of
535      * invoking a {@code getLong} method.
536      *
537      * @param nullLong the value
538      */
539     public void setNullLong(final long nullLong) {
540         this.nullLong = nullLong;
541     }
542 
543     /**
544      * Sets the value to return when a SQL null is encountered as the result of
545      * invoking a {@code getObject} method.
546      *
547      * @param nullObject the value
548      */
549     public void setNullObject(final Object nullObject) {
550         this.nullObject = nullObject;
551     }
552 
553     /**
554      * Sets the value to return when a SQL null is encountered as the result of
555      * invoking a {@code getRef} method.
556      *
557      * @param nullRef the value
558      */
559     public void setNullRef(final Ref nullRef) {
560         this.nullRef = nullRef;
561     }
562 
563     /**
564      * Sets the value to return when a SQL null is encountered as the result of
565      * invoking a {@code getShort} method.
566      *
567      * @param nullShort the value
568      */
569     public void setNullShort(final short nullShort) {
570         this.nullShort = nullShort;
571     }
572 
573     /**
574      * Sets the value to return when a SQL null is encountered as the result of
575      * invoking a {@code getString} method.
576      *
577      * @param nullString the value
578      */
579     public void setNullString(final String nullString) {
580         this.nullString = nullString;
581     }
582 
583     /**
584      * Sets the value to return when a SQL null is encountered as the result of
585      * invoking a {@code getTime} method.
586      *
587      * @param nullTime the value
588      */
589     public void setNullTime(final Time nullTime) {
590         this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
591     }
592 
593     /**
594      * Sets the value to return when a SQL null is encountered as the result of
595      * invoking a {@code getTimestamp} method.
596      *
597      * @param nullTimestamp the value
598      */
599     public void setNullTimestamp(final Timestamp nullTimestamp) {
600         if (nullTimestamp != null) {
601             this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
602             this.nullTimestamp.setNanos(nullTimestamp.getNanos());
603         } else {
604             this.nullTimestamp = null;
605         }
606     }
607 
608     /**
609      * Sets the value to return when a SQL null is encountered as the result of
610      * invoking a {@code getURL} method.
611      *
612      * @param nullURL the value
613      */
614     public void setNullURL(final URL nullURL) {
615         this.nullURL = nullURL;
616     }
617 
618 }