SqlNullCheckedResultSet.java

  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. import java.io.InputStream;
  19. import java.io.Reader;
  20. import java.lang.reflect.InvocationHandler;
  21. import java.lang.reflect.Method;
  22. import java.math.BigDecimal;
  23. import java.net.URL;
  24. import java.sql.Blob;
  25. import java.sql.Clob;
  26. import java.sql.Date;
  27. import java.sql.Ref;
  28. import java.sql.ResultSet;
  29. import java.sql.Time;
  30. import java.sql.Timestamp;
  31. import java.util.HashMap;
  32. import java.util.Map;

  33. import org.apache.commons.dbutils.ProxyFactory;

  34. /**
  35.  * Decorates a {@code ResultSet} with checks for a SQL NULL value on each
  36.  * {@code getXXX} method. If a column value obtained by a
  37.  * {@code getXXX} method is not SQL NULL, the column value is returned. If
  38.  * the column value is SQL null, an alternate value is returned. The alternate
  39.  * value defaults to the Java {@code null} value, which can be overridden
  40.  * for instances of the class.
  41.  *
  42.  * <p>
  43.  * Usage example:
  44.  * <blockquote>
  45.  * <pre>
  46.  * Connection conn = // somehow get a connection
  47.  * Statement stmt = conn.createStatement();
  48.  * ResultSet resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");
  49.  *
  50.  * // Wrap the result set for SQL NULL checking
  51.  * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);
  52.  * wrapper.setNullString("---N/A---"); // Set null string
  53.  * wrapper.setNullInt(-999); // Set null integer
  54.  * resultSet = ProxyFactory.instance().createResultSet(wrapper);
  55.  *
  56.  * while (resultSet.next()) {
  57.  *     // If col1 is SQL NULL, value returned will be "---N/A---"
  58.  *     String col1 = resultSet.getString("col1");
  59.  *     // If col2 is SQL NULL, value returned will be -999
  60.  *     int col2 = resultSet.getInt("col2");
  61.  * }
  62.  * resultSet.close();
  63.  * </pre>
  64.  * </blockquote>
  65.  * &lt;/p&gt;
  66.  * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
  67.  */
  68. public class SqlNullCheckedResultSet implements InvocationHandler {

  69.     /**
  70.      * Maps normal method names (ie. "getBigDecimal") to the corresponding null
  71.      * Method object (ie. getNullBigDecimal).
  72.      */
  73.     private static final Map<String, Method> NULL_METHODS = new HashMap<>();

  74.     /**
  75.      * The {@code getNull} string prefix.
  76.      * @since 1.4
  77.      */
  78.     private static final String GET_NULL_PREFIX = "getNull";

  79.     static {
  80.         final Method[] methods = SqlNullCheckedResultSet.class.getMethods();
  81.         for (final Method method : methods) {
  82.             final String methodName = method.getName();

  83.             if (methodName.startsWith(GET_NULL_PREFIX)) {
  84.                 final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
  85.                 NULL_METHODS.put(normalName, method);
  86.             }
  87.         }
  88.     }

  89.     /**
  90.      * The factory to create proxies with.
  91.      */
  92.     private static final ProxyFactory factory = ProxyFactory.instance();

  93.     /**
  94.      * Wraps the {@code ResultSet} in an instance of this class.  This is
  95.      * equivalent to:
  96.      * <pre>
  97.      * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(resultSet));
  98.      * </pre>
  99.      *
  100.      * @param resultSet The {@code ResultSet} to wrap.
  101.      * @return wrapped ResultSet
  102.      */
  103.     public static ResultSet wrap(final ResultSet resultSet) {
  104.         return factory.createResultSet(new SqlNullCheckedResultSet(resultSet));
  105.     }

  106.     private InputStream nullAsciiStream;
  107.     private BigDecimal nullBigDecimal;
  108.     private InputStream nullBinaryStream;
  109.     private Blob nullBlob;
  110.     private boolean nullBoolean;
  111.     private byte nullByte;
  112.     private byte[] nullBytes;
  113.     private Reader nullCharacterStream;
  114.     private Clob nullClob;
  115.     private Date nullDate;
  116.     private double nullDouble;
  117.     private float nullFloat;
  118.     private int nullInt;
  119.     private long nullLong;
  120.     private Object nullObject;
  121.     private Ref nullRef;
  122.     private short nullShort;
  123.     private String nullString;
  124.     private Time nullTime;
  125.     private Timestamp nullTimestamp;
  126.     private URL nullURL;

  127.     /**
  128.      * The wrapped result.
  129.      */
  130.     private final ResultSet resultSet;

  131.     /**
  132.      * Constructs a new instance of
  133.      * {@code SqlNullCheckedResultSet}
  134.      * to wrap the specified {@code ResultSet}.
  135.      * @param resultSet ResultSet to wrap
  136.      */
  137.     public SqlNullCheckedResultSet(final ResultSet resultSet) {
  138.         this.resultSet = resultSet;
  139.     }

  140.     /**
  141.      * Returns the value when a SQL null is encountered as the result of
  142.      * invoking a {@code getAsciiStream} method.
  143.      *
  144.      * @return the value
  145.      */
  146.     public InputStream getNullAsciiStream() {
  147.         return this.nullAsciiStream;
  148.     }

  149.     /**
  150.      * Returns the value when a SQL null is encountered as the result of
  151.      * invoking a {@code getBigDecimal} method.
  152.      *
  153.      * @return the value
  154.      */
  155.     public BigDecimal getNullBigDecimal() {
  156.         return this.nullBigDecimal;
  157.     }

  158.     /**
  159.      * Returns the value when a SQL null is encountered as the result of
  160.      * invoking a {@code getBinaryStream} method.
  161.      *
  162.      * @return the value
  163.      */
  164.     public InputStream getNullBinaryStream() {
  165.         return this.nullBinaryStream;
  166.     }

  167.     /**
  168.      * Returns the value when a SQL null is encountered as the result of
  169.      * invoking a {@code getBlob} method.
  170.      *
  171.      * @return the value
  172.      */
  173.     public Blob getNullBlob() {
  174.         return this.nullBlob;
  175.     }

  176.     /**
  177.      * Returns the value when a SQL null is encountered as the result of
  178.      * invoking a {@code getBoolean} method.
  179.      *
  180.      * @return the value
  181.      */
  182.     public boolean getNullBoolean() {
  183.         return this.nullBoolean;
  184.     }

  185.     /**
  186.      * Returns the value when a SQL null is encountered as the result of
  187.      * invoking a {@code getByte} method.
  188.      *
  189.      * @return the value
  190.      */
  191.     public byte getNullByte() {
  192.         return this.nullByte;
  193.     }

  194.     /**
  195.      * Returns the value when a SQL null is encountered as the result of
  196.      * invoking a {@code getBytes} method.
  197.      *
  198.      * @return the value
  199.      */
  200.     public byte[] getNullBytes() {
  201.         if (this.nullBytes == null) {
  202.             return null;
  203.         }
  204.         return this.nullBytes.clone();
  205.     }

  206.     /**
  207.      * Returns the value when a SQL null is encountered as the result of
  208.      * invoking a {@code getCharacterStream} method.
  209.      *
  210.      * @return the value
  211.      */
  212.     public Reader getNullCharacterStream() {
  213.         return this.nullCharacterStream;
  214.     }

  215.     /**
  216.      * Returns the value when a SQL null is encountered as the result of
  217.      * invoking a {@code getClob} method.
  218.      *
  219.      * @return the value
  220.      */
  221.     public Clob getNullClob() {
  222.         return this.nullClob;
  223.     }

  224.     /**
  225.      * Returns the value when a SQL null is encountered as the result of
  226.      * invoking a {@code getDate} method.
  227.      *
  228.      * @return the value
  229.      */
  230.     public Date getNullDate() {
  231.         return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;
  232.     }

  233.     /**
  234.      * Returns the value when a SQL null is encountered as the result of
  235.      * invoking a {@code getDouble} method.
  236.      *
  237.      * @return the value
  238.      */
  239.     public double getNullDouble() {
  240.         return this.nullDouble;
  241.     }

  242.     /**
  243.      * Returns the value when a SQL null is encountered as the result of
  244.      * invoking a {@code getFloat} method.
  245.      *
  246.      * @return the value
  247.      */
  248.     public float getNullFloat() {
  249.         return this.nullFloat;
  250.     }

  251.     /**
  252.      * Returns the value when a SQL null is encountered as the result of
  253.      * invoking a {@code getInt} method.
  254.      *
  255.      * @return the value
  256.      */
  257.     public int getNullInt() {
  258.         return this.nullInt;
  259.     }

  260.     /**
  261.      * Returns the value when a SQL null is encountered as the result of
  262.      * invoking a {@code getLong} method.
  263.      *
  264.      * @return the value
  265.      */
  266.     public long getNullLong() {
  267.         return this.nullLong;
  268.     }

  269.     /**
  270.      * Returns the value when a SQL null is encountered as the result of
  271.      * invoking a {@code getObject} method.
  272.      *
  273.      * @return the value
  274.      */
  275.     public Object getNullObject() {
  276.         return this.nullObject;
  277.     }

  278.     /**
  279.      * Returns the value when a SQL null is encountered as the result of
  280.      * invoking a {@code getRef} method.
  281.      *
  282.      * @return the value
  283.      */
  284.     public Ref getNullRef() {
  285.         return this.nullRef;
  286.     }

  287.     /**
  288.      * Returns the value when a SQL null is encountered as the result of
  289.      * invoking a {@code getShort} method.
  290.      *
  291.      * @return the value
  292.      */
  293.     public short getNullShort() {
  294.         return this.nullShort;
  295.     }

  296.     /**
  297.      * Returns the value when a SQL null is encountered as the result of
  298.      * invoking a {@code getString} method.
  299.      *
  300.      * @return the value
  301.      */
  302.     public String getNullString() {
  303.         return this.nullString;
  304.     }

  305.     /**
  306.      * Returns the value when a SQL null is encountered as the result of
  307.      * invoking a {@code getTime} method.
  308.      *
  309.      * @return the value
  310.      */
  311.     public Time getNullTime() {
  312.         return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;
  313.     }

  314.     /**
  315.      * Returns the value when a SQL null is encountered as the result of
  316.      * invoking a {@code getTimestamp} method.
  317.      *
  318.      * @return the value
  319.      */
  320.     public Timestamp getNullTimestamp() {
  321.         if (this.nullTimestamp == null) {
  322.             return null;
  323.         }

  324.         final Timestamp ts = new Timestamp(this.nullTimestamp.getTime());
  325.         ts.setNanos(this.nullTimestamp.getNanos());
  326.         return ts;
  327.     }

  328.     /**
  329.      * Returns the value when a SQL null is encountered as the result of
  330.      * invoking a {@code getURL} method.
  331.      *
  332.      * @return the value
  333.      */
  334.     public URL getNullURL() {
  335.         return this.nullURL;
  336.     }

  337.     /**
  338.      * Intercepts calls to {@code get*} methods and calls the appropriate
  339.      * {@code getNull*} method if the {@code ResultSet} returned
  340.      * {@code null}.
  341.      *
  342.      * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
  343.      * @param proxy Not used; all method calls go to the internal result set
  344.      * @param method The method to invoke on the result set
  345.      * @param args The arguments to pass to the result set
  346.      * @return null checked result
  347.      * @throws Throwable error
  348.      */
  349.     @Override
  350.     public Object invoke(final Object proxy, final Method method, final Object[] args)
  351.         throws Throwable {

  352.         final Object result = method.invoke(this.resultSet, args);

  353.         final Method nullMethod = NULL_METHODS.get(method.getName());

  354.         // Check nullMethod != null first so that we don't call wasNull()
  355.         // before a true getter method was invoked on the ResultSet.
  356.         return nullMethod != null && this.resultSet.wasNull()
  357.             ? nullMethod.invoke(this, (Object[]) null)
  358.             : result;
  359.     }

  360.     /**
  361.      * Sets the value to return when a SQL null is encountered as the result of
  362.      * invoking a {@code getAsciiStream} method.
  363.      *
  364.      * @param nullAsciiStream the value
  365.      */
  366.     public void setNullAsciiStream(final InputStream nullAsciiStream) {
  367.         this.nullAsciiStream = nullAsciiStream;
  368.     }

  369.     /**
  370.      * Sets the value to return when a SQL null is encountered as the result of
  371.      * invoking a {@code getBigDecimal} method.
  372.      *
  373.      * @param nullBigDecimal the value
  374.      */
  375.     public void setNullBigDecimal(final BigDecimal nullBigDecimal) {
  376.         this.nullBigDecimal = nullBigDecimal;
  377.     }

  378.     /**
  379.      * Sets the value to return when a SQL null is encountered as the result of
  380.      * invoking a {@code getBinaryStream} method.
  381.      *
  382.      * @param nullBinaryStream the value
  383.      */
  384.     public void setNullBinaryStream(final InputStream nullBinaryStream) {
  385.         this.nullBinaryStream = nullBinaryStream;
  386.     }

  387.     /**
  388.      * Sets the value to return when a SQL null is encountered as the result of
  389.      * invoking a {@code getBlob} method.
  390.      *
  391.      * @param nullBlob the value
  392.      */
  393.     public void setNullBlob(final Blob nullBlob) {
  394.         this.nullBlob = nullBlob;
  395.     }

  396.     /**
  397.      * Sets the value to return when a SQL null is encountered as the result of
  398.      * invoking a {@code getBoolean} method.
  399.      *
  400.      * @param nullBoolean the value
  401.      */
  402.     public void setNullBoolean(final boolean nullBoolean) {
  403.         this.nullBoolean = nullBoolean;
  404.     }

  405.     /**
  406.      * Sets the value to return when a SQL null is encountered as the result of
  407.      * invoking a {@code getByte} method.
  408.      *
  409.      * @param nullByte the value
  410.      */
  411.     public void setNullByte(final byte nullByte) {
  412.         this.nullByte = nullByte;
  413.     }

  414.     /**
  415.      * Sets the value to return when a SQL null is encountered as the result of
  416.      * invoking a {@code getBytes} method.
  417.      *
  418.      * @param nullBytes the value
  419.      */
  420.     public void setNullBytes(final byte[] nullBytes) {
  421.         if (nullBytes != null) {
  422.             this.nullBytes = nullBytes.clone();
  423.         } else {
  424.             this.nullBytes = null;
  425.         }
  426.     }

  427.     /**
  428.      * Sets the value to return when a SQL null is encountered as the result of
  429.      * invoking a {@code getCharacterStream} method.
  430.      *
  431.      * @param nullCharacterStream the value
  432.      */
  433.     public void setNullCharacterStream(final Reader nullCharacterStream) {
  434.         this.nullCharacterStream = nullCharacterStream;
  435.     }

  436.     /**
  437.      * Sets the value to return when a SQL null is encountered as the result of
  438.      * invoking a {@code getClob} method.
  439.      *
  440.      * @param nullClob the value
  441.      */
  442.     public void setNullClob(final Clob nullClob) {
  443.         this.nullClob = nullClob;
  444.     }

  445.     /**
  446.      * Sets the value to return when a SQL null is encountered as the result of
  447.      * invoking a {@code getDate} method.
  448.      *
  449.      * @param nullDate the value
  450.      */
  451.     public void setNullDate(final Date nullDate) {
  452.         this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;
  453.     }

  454.     /**
  455.      * Sets the value to return when a SQL null is encountered as the result of
  456.      * invoking a {@code getDouble} method.
  457.      *
  458.      * @param nullDouble the value
  459.      */
  460.     public void setNullDouble(final double nullDouble) {
  461.         this.nullDouble = nullDouble;
  462.     }

  463.     /**
  464.      * Sets the value to return when a SQL null is encountered as the result of
  465.      * invoking a {@code getFloat} method.
  466.      *
  467.      * @param nullFloat the value
  468.      */
  469.     public void setNullFloat(final float nullFloat) {
  470.         this.nullFloat = nullFloat;
  471.     }

  472.     /**
  473.      * Sets the value to return when a SQL null is encountered as the result of
  474.      * invoking a {@code getInt} method.
  475.      *
  476.      * @param nullInt the value
  477.      */
  478.     public void setNullInt(final int nullInt) {
  479.         this.nullInt = nullInt;
  480.     }

  481.     /**
  482.      * Sets the value to return when a SQL null is encountered as the result of
  483.      * invoking a {@code getLong} method.
  484.      *
  485.      * @param nullLong the value
  486.      */
  487.     public void setNullLong(final long nullLong) {
  488.         this.nullLong = nullLong;
  489.     }

  490.     /**
  491.      * Sets the value to return when a SQL null is encountered as the result of
  492.      * invoking a {@code getObject} method.
  493.      *
  494.      * @param nullObject the value
  495.      */
  496.     public void setNullObject(final Object nullObject) {
  497.         this.nullObject = nullObject;
  498.     }

  499.     /**
  500.      * Sets the value to return when a SQL null is encountered as the result of
  501.      * invoking a {@code getRef} method.
  502.      *
  503.      * @param nullRef the value
  504.      */
  505.     public void setNullRef(final Ref nullRef) {
  506.         this.nullRef = nullRef;
  507.     }

  508.     /**
  509.      * Sets the value to return when a SQL null is encountered as the result of
  510.      * invoking a {@code getShort} method.
  511.      *
  512.      * @param nullShort the value
  513.      */
  514.     public void setNullShort(final short nullShort) {
  515.         this.nullShort = nullShort;
  516.     }

  517.     /**
  518.      * Sets the value to return when a SQL null is encountered as the result of
  519.      * invoking a {@code getString} method.
  520.      *
  521.      * @param nullString the value
  522.      */
  523.     public void setNullString(final String nullString) {
  524.         this.nullString = nullString;
  525.     }

  526.     /**
  527.      * Sets the value to return when a SQL null is encountered as the result of
  528.      * invoking a {@code getTime} method.
  529.      *
  530.      * @param nullTime the value
  531.      */
  532.     public void setNullTime(final Time nullTime) {
  533.         this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
  534.     }

  535.     /**
  536.      * Sets the value to return when a SQL null is encountered as the result of
  537.      * invoking a {@code getTimestamp} method.
  538.      *
  539.      * @param nullTimestamp the value
  540.      */
  541.     public void setNullTimestamp(final Timestamp nullTimestamp) {
  542.         if (nullTimestamp != null) {
  543.             this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
  544.             this.nullTimestamp.setNanos(nullTimestamp.getNanos());
  545.         } else {
  546.             this.nullTimestamp = null;
  547.         }
  548.     }

  549.     /**
  550.      * Sets the value to return when a SQL null is encountered as the result of
  551.      * invoking a {@code getURL} method.
  552.      *
  553.      * @param nullURL the value
  554.      */
  555.     public void setNullURL(final URL nullURL) {
  556.         this.nullURL = nullURL;
  557.     }

  558. }