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     */
017    package org.apache.commons.dbutils.wrappers;
018    
019    import java.io.InputStream;
020    import java.io.Reader;
021    import java.lang.reflect.InvocationHandler;
022    import java.lang.reflect.Method;
023    import java.math.BigDecimal;
024    import java.net.URL;
025    import java.sql.Blob;
026    import java.sql.Clob;
027    import java.sql.Date;
028    import java.sql.Ref;
029    import java.sql.ResultSet;
030    import java.sql.Time;
031    import java.sql.Timestamp;
032    import java.util.HashMap;
033    import java.util.Map;
034    
035    import org.apache.commons.dbutils.ProxyFactory;
036    
037    /**
038     * Decorates a <code>ResultSet</code> with checks for a SQL NULL value on each
039     * <code>getXXX</code> method. If a column value obtained by a
040     * <code>getXXX</code> 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</code> 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     * </p>
069     * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
070     */
071    public 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<String, Method>();
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            Method[] methods = SqlNullCheckedResultSet.class.getMethods();
087            for (int i = 0; i < methods.length; i++) {
088                String methodName = methods[i].getName();
089    
090                if (methodName.startsWith(GET_NULL_PREFIX)) {
091                    String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
092                    nullMethods.put(normalName, methods[i]);
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</code> 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</code> to wrap.
110         * @return wrapped ResultSet
111         */
112        public static ResultSet wrap(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</code>
146         * to wrap the specified <code>ResultSet</code>.
147         * @param rs ResultSet to wrap
148         */
149        public SqlNullCheckedResultSet(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</code> 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</code> 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</code> 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</code> 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</code> 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</code> 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</code> method.
217         *
218         * @return the value
219         */
220        public byte[] getNullBytes() {
221            if (this.nullBytes == null) {
222                return null;
223            }
224            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</code> 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</code> 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</code> method.
252         *
253         * @return the value
254         */
255        public Date getNullDate() {
256            return this.nullDate;
257        }
258    
259        /**
260         * Returns the value when a SQL null is encountered as the result of
261         * invoking a <code>getDouble</code> 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</code> 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</code> 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</code> 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</code> 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</code> 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</code> 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</code> 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</code> method.
342         *
343         * @return the value
344         */
345        public Time getNullTime() {
346            return this.nullTime;
347        }
348    
349        /**
350         * Returns the value when a SQL null is encountered as the result of
351         * invoking a <code>getTimestamp</code> method.
352         *
353         * @return the value
354         */
355        public Timestamp getNullTimestamp() {
356            return this.nullTimestamp;
357        }
358    
359        /**
360         * Returns the value when a SQL null is encountered as the result of
361         * invoking a <code>getURL</code> method.
362         *
363         * @return the value
364         */
365        public URL getNullURL() {
366            return this.nullURL;
367        }
368    
369        /**
370         * Intercepts calls to <code>get*</code> methods and calls the appropriate
371         * <code>getNull*</code> method if the <code>ResultSet</code> returned
372         * <code>null</code>.
373         *
374         *  @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
375         * @param proxy Not used; all method calls go to the internal result set
376         * @param method The method to invoke on the result set
377         * @param args The arguments to pass to the result set
378         * @return null checked result
379         * @throws Throwable error
380         */
381        @Override
382        public Object invoke(Object proxy, Method method, Object[] args)
383            throws Throwable {
384    
385            Object result = method.invoke(this.rs, args);
386    
387            Method nullMethod = nullMethods.get(method.getName());
388    
389            // Check nullMethod != null first so that we don't call wasNull()
390            // before a true getter method was invoked on the ResultSet.
391            return (nullMethod != null && this.rs.wasNull())
392                ? nullMethod.invoke(this, (Object[]) null)
393                : result;
394        }
395    
396        /**
397         * Sets the value to return when a SQL null is encountered as the result of
398         * invoking a <code>getAsciiStream</code> method.
399         *
400         * @param nullAsciiStream the value
401         */
402        public void setNullAsciiStream(InputStream nullAsciiStream) {
403            this.nullAsciiStream = nullAsciiStream;
404        }
405    
406        /**
407         * Sets the value to return when a SQL null is encountered as the result of
408         * invoking a <code>getBigDecimal</code> method.
409         *
410         * @param nullBigDecimal the value
411         */
412        public void setNullBigDecimal(BigDecimal nullBigDecimal) {
413            this.nullBigDecimal = nullBigDecimal;
414        }
415    
416        /**
417         * Sets the value to return when a SQL null is encountered as the result of
418         * invoking a <code>getBinaryStream</code> method.
419         *
420         * @param nullBinaryStream the value
421         */
422        public void setNullBinaryStream(InputStream nullBinaryStream) {
423            this.nullBinaryStream = nullBinaryStream;
424        }
425    
426        /**
427         * Sets the value to return when a SQL null is encountered as the result of
428         * invoking a <code>getBlob</code> method.
429         *
430         * @param nullBlob the value
431         */
432        public void setNullBlob(Blob nullBlob) {
433            this.nullBlob = nullBlob;
434        }
435    
436        /**
437         * Sets the value to return when a SQL null is encountered as the result of
438         * invoking a <code>getBoolean</code> method.
439         *
440         * @param nullBoolean the value
441         */
442        public void setNullBoolean(boolean nullBoolean) {
443            this.nullBoolean = nullBoolean;
444        }
445    
446        /**
447         * Sets the value to return when a SQL null is encountered as the result of
448         * invoking a <code>getByte</code> method.
449         *
450         * @param nullByte the value
451         */
452        public void setNullByte(byte nullByte) {
453            this.nullByte = nullByte;
454        }
455    
456        /**
457         * Sets the value to return when a SQL null is encountered as the result of
458         * invoking a <code>getBytes</code> method.
459         *
460         * @param nullBytes the value
461         */
462        public void setNullBytes(byte[] nullBytes) {
463            byte[] copy = new byte[nullBytes.length];
464            System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
465            this.nullBytes = copy;
466        }
467    
468        /**
469         * Sets the value to return when a SQL null is encountered as the result of
470         * invoking a <code>getCharacterStream</code> method.
471         *
472         * @param nullCharacterStream the value
473         */
474        public void setNullCharacterStream(Reader nullCharacterStream) {
475            this.nullCharacterStream = nullCharacterStream;
476        }
477    
478        /**
479         * Sets the value to return when a SQL null is encountered as the result of
480         * invoking a <code>getClob</code> method.
481         *
482         * @param nullClob the value
483         */
484        public void setNullClob(Clob nullClob) {
485            this.nullClob = nullClob;
486        }
487    
488        /**
489         * Sets the value to return when a SQL null is encountered as the result of
490         * invoking a <code>getDate</code> method.
491         *
492         * @param nullDate the value
493         */
494        public void setNullDate(Date nullDate) {
495            this.nullDate = nullDate;
496        }
497    
498        /**
499         * Sets the value to return when a SQL null is encountered as the result of
500         * invoking a <code>getDouble</code> method.
501         *
502         * @param nullDouble the value
503         */
504        public void setNullDouble(double nullDouble) {
505            this.nullDouble = nullDouble;
506        }
507    
508        /**
509         * Sets the value to return when a SQL null is encountered as the result of
510         * invoking a <code>getFloat</code> method.
511         *
512         * @param nullFloat the value
513         */
514        public void setNullFloat(float nullFloat) {
515            this.nullFloat = nullFloat;
516        }
517    
518        /**
519         * Sets the value to return when a SQL null is encountered as the result of
520         * invoking a <code>getInt</code> method.
521         *
522         * @param nullInt the value
523         */
524        public void setNullInt(int nullInt) {
525            this.nullInt = nullInt;
526        }
527    
528        /**
529         * Sets the value to return when a SQL null is encountered as the result of
530         * invoking a <code>getLong</code> method.
531         *
532         * @param nullLong the value
533         */
534        public void setNullLong(long nullLong) {
535            this.nullLong = nullLong;
536        }
537    
538        /**
539         * Sets the value to return when a SQL null is encountered as the result of
540         * invoking a <code>getObject</code> method.
541         *
542         * @param nullObject the value
543         */
544        public void setNullObject(Object nullObject) {
545            this.nullObject = nullObject;
546        }
547    
548        /**
549         * Sets the value to return when a SQL null is encountered as the result of
550         * invoking a <code>getRef</code> method.
551         *
552         * @param nullRef the value
553         */
554        public void setNullRef(Ref nullRef) {
555            this.nullRef = nullRef;
556        }
557    
558        /**
559         * Sets the value to return when a SQL null is encountered as the result of
560         * invoking a <code>getShort</code> method.
561         *
562         * @param nullShort the value
563         */
564        public void setNullShort(short nullShort) {
565            this.nullShort = nullShort;
566        }
567    
568        /**
569         * Sets the value to return when a SQL null is encountered as the result of
570         * invoking a <code>getString</code> method.
571         *
572         * @param nullString the value
573         */
574        public void setNullString(String nullString) {
575            this.nullString = nullString;
576        }
577    
578        /**
579         * Sets the value to return when a SQL null is encountered as the result of
580         * invoking a <code>getTime</code> method.
581         *
582         * @param nullTime the value
583         */
584        public void setNullTime(Time nullTime) {
585            this.nullTime = nullTime;
586        }
587    
588        /**
589         * Sets the value to return when a SQL null is encountered as the result of
590         * invoking a <code>getTimestamp</code> method.
591         *
592         * @param nullTimestamp the value
593         */
594        public void setNullTimestamp(Timestamp nullTimestamp) {
595            this.nullTimestamp = nullTimestamp;
596        }
597    
598        /**
599         * Sets the value to return when a SQL null is encountered as the result of
600         * invoking a <code>getURL</code> method.
601         *
602         * @param nullURL the value
603         */
604        public void setNullURL(URL nullURL) {
605            this.nullURL = nullURL;
606        }
607    
608    }