    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}