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 resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");
052 *
053 * // Wrap the result set for SQL NULL checking
054 * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);
055 * wrapper.setNullString("---N/A---"); // Set null string
056 * wrapper.setNullInt(-999); // Set null integer
057 * resultSet = ProxyFactory.instance().createResultSet(wrapper);
058 *
059 * while (resultSet.next()) {
060 *     // If col1 is SQL NULL, value returned will be "---N/A---"
061 *     String col1 = resultSet.getString("col1");
062 *     // If col2 is SQL NULL, value returned will be -999
063 *     int col2 = resultSet.getInt("col2");
064 * }
065 * resultSet.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> NULL_METHODS = 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 (final 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                NULL_METHODS.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(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}