001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbutils.wrappers;
018
019import java.io.InputStream;
020import java.io.Reader;
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.Method;
023import java.math.BigDecimal;
024import java.net.URL;
025import java.sql.Blob;
026import java.sql.Clob;
027import java.sql.Date;
028import java.sql.Ref;
029import java.sql.ResultSet;
030import java.sql.Time;
031import java.sql.Timestamp;
032import java.util.HashMap;
033import java.util.Map;
034
035import org.apache.commons.dbutils.ProxyFactory;
036
037/**
038 * Decorates a {@code ResultSet} with checks for a SQL NULL value on each
039 * {@code getXXX} method. If a column value obtained by a
040 * {@code getXXX} method is not SQL NULL, the column value is returned. If
041 * the column value is SQL null, an alternate value is returned. The alternate
042 * value defaults to the Java {@code null} value, which can be overridden
043 * for instances of the class.
044 *
045 * <p>
046 * Usage example:
047 * <blockquote>
048 * <pre>
049 * Connection conn = // somehow get a connection
050 * Statement stmt = conn.createStatement();
051 * ResultSet rs = stmt.executeQuery("SELECT col1, col2 FROM table1");
052 *
053 * // Wrap the result set for SQL NULL checking
054 * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(rs);
055 * wrapper.setNullString("---N/A---"); // Set null string
056 * wrapper.setNullInt(-999); // Set null integer
057 * rs = ProxyFactory.instance().createResultSet(wrapper);
058 *
059 * while (rs.next()) {
060 *     // If col1 is SQL NULL, value returned will be "---N/A---"
061 *     String col1 = rs.getString("col1");
062 *     // If col2 is SQL NULL, value returned will be -999
063 *     int col2 = rs.getInt("col2");
064 * }
065 * rs.close();
066 * </pre>
067 * </blockquote>
068 * &lt;/p&gt;
069 * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
070 */
071public class SqlNullCheckedResultSet implements InvocationHandler {
072
073    /**
074     * Maps normal method names (ie. "getBigDecimal") to the corresponding null
075     * Method object (ie. getNullBigDecimal).
076     */
077    private static final Map<String, Method> nullMethods = new HashMap<>();
078
079    /**
080     * The {@code getNull} string prefix.
081     * @since 1.4
082     */
083    private static final String GET_NULL_PREFIX = "getNull";
084
085    static {
086        final Method[] methods = SqlNullCheckedResultSet.class.getMethods();
087        for (Method method : methods) {
088            final String methodName = method.getName();
089
090            if (methodName.startsWith(GET_NULL_PREFIX)) {
091                final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
092                nullMethods.put(normalName, method);
093            }
094        }
095    }
096
097    /**
098     * The factory to create proxies with.
099     */
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(rs));
107     * </pre>
108     *
109     * @param rs The {@code ResultSet} to wrap.
110     * @return wrapped ResultSet
111     */
112    public static ResultSet wrap(final 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}
146     * to wrap the specified {@code ResultSet}.
147     * @param rs ResultSet to wrap
148     */
149    public SqlNullCheckedResultSet(final 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} 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} 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} 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} 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} 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} 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} method.
217     *
218     * @return the value
219     */
220    public byte[] getNullBytes() {
221        if (this.nullBytes == null) {
222            return null;
223        }
224        final 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} 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} 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} method.
252     *
253     * @return the value
254     */
255    public Date getNullDate() {
256        return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;
257    }
258
259    /**
260     * Returns the value when a SQL null is encountered as the result of
261     * invoking a {@code getDouble} 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} 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} 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} 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} 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} 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} 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} 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} method.
342     *
343     * @return the value
344     */
345    public Time getNullTime() {
346        return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;
347    }
348
349    /**
350     * Returns the value when a SQL null is encountered as the result of
351     * invoking a {@code getTimestamp} method.
352     *
353     * @return the value
354     */
355    public Timestamp getNullTimestamp() {
356        if (this.nullTimestamp == null) {
357            return null;
358        }
359
360        Timestamp ts = new Timestamp(this.nullTimestamp.getTime());
361        ts.setNanos(this.nullTimestamp.getNanos());
362        return ts;
363    }
364
365    /**
366     * Returns the value when a SQL null is encountered as the result of
367     * invoking a {@code getURL} method.
368     *
369     * @return the value
370     */
371    public URL getNullURL() {
372        return this.nullURL;
373    }
374
375    /**
376     * Intercepts calls to {@code get*} methods and calls the appropriate
377     * {@code getNull*} method if the {@code ResultSet} returned
378     * {@code null}.
379     *
380     *  @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
381     * @param proxy Not used; all method calls go to the internal result set
382     * @param method The method to invoke on the result set
383     * @param args The arguments to pass to the result set
384     * @return null checked result
385     * @throws Throwable error
386     */
387    @Override
388    public Object invoke(final Object proxy, final Method method, final Object[] args)
389        throws Throwable {
390
391        final Object result = method.invoke(this.rs, args);
392
393        final Method nullMethod = nullMethods.get(method.getName());
394
395        // Check nullMethod != null first so that we don't call wasNull()
396        // before a true getter method was invoked on the ResultSet.
397        return (nullMethod != null && this.rs.wasNull())
398            ? nullMethod.invoke(this, (Object[]) null)
399            : result;
400    }
401
402    /**
403     * Sets the value to return when a SQL null is encountered as the result of
404     * invoking a {@code getAsciiStream} method.
405     *
406     * @param nullAsciiStream the value
407     */
408    public void setNullAsciiStream(final InputStream nullAsciiStream) {
409        this.nullAsciiStream = nullAsciiStream;
410    }
411
412    /**
413     * Sets the value to return when a SQL null is encountered as the result of
414     * invoking a {@code getBigDecimal} method.
415     *
416     * @param nullBigDecimal the value
417     */
418    public void setNullBigDecimal(final BigDecimal nullBigDecimal) {
419        this.nullBigDecimal = nullBigDecimal;
420    }
421
422    /**
423     * Sets the value to return when a SQL null is encountered as the result of
424     * invoking a {@code getBinaryStream} method.
425     *
426     * @param nullBinaryStream the value
427     */
428    public void setNullBinaryStream(final InputStream nullBinaryStream) {
429        this.nullBinaryStream = nullBinaryStream;
430    }
431
432    /**
433     * Sets the value to return when a SQL null is encountered as the result of
434     * invoking a {@code getBlob} method.
435     *
436     * @param nullBlob the value
437     */
438    public void setNullBlob(final Blob nullBlob) {
439        this.nullBlob = nullBlob;
440    }
441
442    /**
443     * Sets the value to return when a SQL null is encountered as the result of
444     * invoking a {@code getBoolean} method.
445     *
446     * @param nullBoolean the value
447     */
448    public void setNullBoolean(final boolean nullBoolean) {
449        this.nullBoolean = nullBoolean;
450    }
451
452    /**
453     * Sets the value to return when a SQL null is encountered as the result of
454     * invoking a {@code getByte} method.
455     *
456     * @param nullByte the value
457     */
458    public void setNullByte(final byte nullByte) {
459        this.nullByte = nullByte;
460    }
461
462    /**
463     * Sets the value to return when a SQL null is encountered as the result of
464     * invoking a {@code getBytes} method.
465     *
466     * @param nullBytes the value
467     */
468    public void setNullBytes(final byte[] nullBytes) {
469        if (nullBytes != null) {
470            final byte[] copy = new byte[nullBytes.length];
471            System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
472            this.nullBytes = copy;
473        } else {
474            this.nullBytes = null;
475        }
476    }
477
478    /**
479     * Sets the value to return when a SQL null is encountered as the result of
480     * invoking a {@code getCharacterStream} method.
481     *
482     * @param nullCharacterStream the value
483     */
484    public void setNullCharacterStream(final Reader nullCharacterStream) {
485        this.nullCharacterStream = nullCharacterStream;
486    }
487
488    /**
489     * Sets the value to return when a SQL null is encountered as the result of
490     * invoking a {@code getClob} method.
491     *
492     * @param nullClob the value
493     */
494    public void setNullClob(final Clob nullClob) {
495        this.nullClob = nullClob;
496    }
497
498    /**
499     * Sets the value to return when a SQL null is encountered as the result of
500     * invoking a {@code getDate} method.
501     *
502     * @param nullDate the value
503     */
504    public void setNullDate(final Date nullDate) {
505        this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;
506    }
507
508    /**
509     * Sets the value to return when a SQL null is encountered as the result of
510     * invoking a {@code getDouble} method.
511     *
512     * @param nullDouble the value
513     */
514    public void setNullDouble(final double nullDouble) {
515        this.nullDouble = nullDouble;
516    }
517
518    /**
519     * Sets the value to return when a SQL null is encountered as the result of
520     * invoking a {@code getFloat} method.
521     *
522     * @param nullFloat the value
523     */
524    public void setNullFloat(final float nullFloat) {
525        this.nullFloat = nullFloat;
526    }
527
528    /**
529     * Sets the value to return when a SQL null is encountered as the result of
530     * invoking a {@code getInt} method.
531     *
532     * @param nullInt the value
533     */
534    public void setNullInt(final int nullInt) {
535        this.nullInt = nullInt;
536    }
537
538    /**
539     * Sets the value to return when a SQL null is encountered as the result of
540     * invoking a {@code getLong} method.
541     *
542     * @param nullLong the value
543     */
544    public void setNullLong(final long nullLong) {
545        this.nullLong = nullLong;
546    }
547
548    /**
549     * Sets the value to return when a SQL null is encountered as the result of
550     * invoking a {@code getObject} method.
551     *
552     * @param nullObject the value
553     */
554    public void setNullObject(final Object nullObject) {
555        this.nullObject = nullObject;
556    }
557
558    /**
559     * Sets the value to return when a SQL null is encountered as the result of
560     * invoking a {@code getRef} method.
561     *
562     * @param nullRef the value
563     */
564    public void setNullRef(final Ref nullRef) {
565        this.nullRef = nullRef;
566    }
567
568    /**
569     * Sets the value to return when a SQL null is encountered as the result of
570     * invoking a {@code getShort} method.
571     *
572     * @param nullShort the value
573     */
574    public void setNullShort(final short nullShort) {
575        this.nullShort = nullShort;
576    }
577
578    /**
579     * Sets the value to return when a SQL null is encountered as the result of
580     * invoking a {@code getString} method.
581     *
582     * @param nullString the value
583     */
584    public void setNullString(final String nullString) {
585        this.nullString = nullString;
586    }
587
588    /**
589     * Sets the value to return when a SQL null is encountered as the result of
590     * invoking a {@code getTime} method.
591     *
592     * @param nullTime the value
593     */
594    public void setNullTime(final Time nullTime) {
595        this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
596    }
597
598    /**
599     * Sets the value to return when a SQL null is encountered as the result of
600     * invoking a {@code getTimestamp} method.
601     *
602     * @param nullTimestamp the value
603     */
604    public void setNullTimestamp(final Timestamp nullTimestamp) {
605        if (nullTimestamp != null) {
606            this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
607            this.nullTimestamp.setNanos(nullTimestamp.getNanos());
608        } else {
609            this.nullTimestamp = null;
610        }
611    }
612
613    /**
614     * Sets the value to return when a SQL null is encountered as the result of
615     * invoking a {@code getURL} method.
616     *
617     * @param nullURL the value
618     */
619    public void setNullURL(final URL nullURL) {
620        this.nullURL = nullURL;
621    }
622
623}