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 nullMethods = new HashMap();
078    
079        static {
080            Method[] methods = SqlNullCheckedResultSet.class.getMethods();
081            for (int i = 0; i < methods.length; i++) {
082                String methodName = methods[i].getName();
083    
084                if (methodName.startsWith("getNull")) {
085                    String normalName = "get" + methodName.substring(7);
086                    nullMethods.put(normalName, methods[i]);
087                }
088            }
089        }
090    
091        /**
092         * The factory to create proxies with.
093         */
094        private static final ProxyFactory factory = ProxyFactory.instance();
095    
096        /**
097         * Wraps the <code>ResultSet</code> in an instance of this class.  This is
098         * equivalent to:
099         * <pre>
100         * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(rs));
101         * </pre>
102         * 
103         * @param rs The <code>ResultSet</code> to wrap.
104         * @return wrapped ResultSet
105         */
106        public static ResultSet wrap(ResultSet rs) {
107            return factory.createResultSet(new SqlNullCheckedResultSet(rs));
108        }
109    
110        private InputStream nullAsciiStream = null;
111        private BigDecimal nullBigDecimal = null;
112        private InputStream nullBinaryStream = null;
113        private Blob nullBlob = null;
114        private boolean nullBoolean = false;
115        private byte nullByte = 0;
116        private byte[] nullBytes = null;
117        private Reader nullCharacterStream = null;
118        private Clob nullClob = null;
119        private Date nullDate = null;
120        private double nullDouble = 0.0;
121        private float nullFloat = 0.0f;
122        private int nullInt = 0;
123        private long nullLong = 0;
124        private Object nullObject = null;
125        private Ref nullRef = null;
126        private short nullShort = 0;
127        private String nullString = null;
128        private Time nullTime = null;
129        private Timestamp nullTimestamp = null;
130        private URL nullURL = null;
131    
132        /**
133         * The wrapped result. 
134         */
135        private final ResultSet rs;
136    
137        /**
138         * Constructs a new instance of
139         * <code>SqlNullCheckedResultSet</code>
140         * to wrap the specified <code>ResultSet</code>.
141         * @param rs ResultSet to wrap
142         */
143        public SqlNullCheckedResultSet(ResultSet rs) {
144            super();
145            this.rs = rs;
146        }
147    
148        /**
149         * Returns the value when a SQL null is encountered as the result of
150         * invoking a <code>getAsciiStream</code> method.
151         *
152         * @return the value
153         */
154        public InputStream getNullAsciiStream() {
155            return this.nullAsciiStream;
156        }
157    
158        /**
159         * Returns the value when a SQL null is encountered as the result of
160         * invoking a <code>getBigDecimal</code> method.
161         *
162         * @return the value
163         */
164        public BigDecimal getNullBigDecimal() {
165            return this.nullBigDecimal;
166        }
167    
168        /**
169         * Returns the value when a SQL null is encountered as the result of
170         * invoking a <code>getBinaryStream</code> method.
171         *
172         * @return the value
173         */
174        public InputStream getNullBinaryStream() {
175            return this.nullBinaryStream;
176        }
177    
178        /**
179         * Returns the value when a SQL null is encountered as the result of
180         * invoking a <code>getBlob</code> method.
181         *
182         * @return the value
183         */
184        public Blob getNullBlob() {
185            return this.nullBlob;
186        }
187    
188        /**
189         * Returns the value when a SQL null is encountered as the result of
190         * invoking a <code>getBoolean</code> method.
191         *
192         * @return the value
193         */
194        public boolean getNullBoolean() {
195            return this.nullBoolean;
196        }
197    
198        /**
199         * Returns the value when a SQL null is encountered as the result of
200         * invoking a <code>getByte</code> method.
201         *
202         * @return the value
203         */
204        public byte getNullByte() {
205            return this.nullByte;
206        }
207    
208        /**
209         * Returns the value when a SQL null is encountered as the result of
210         * invoking a <code>getBytes</code> method.
211         *
212         * @return the value
213         */
214        public byte[] getNullBytes() {
215            return this.nullBytes;
216        }
217    
218        /**
219         * Returns the value when a SQL null is encountered as the result of
220         * invoking a <code>getCharacterStream</code> method.
221         *
222         * @return the value
223         */
224        public Reader getNullCharacterStream() {
225            return this.nullCharacterStream;
226        }
227    
228        /**
229         * Returns the value when a SQL null is encountered as the result of
230         * invoking a <code>getClob</code> method.
231         *
232         * @return the value
233         */
234        public Clob getNullClob() {
235            return this.nullClob;
236        }
237    
238        /**
239         * Returns the value when a SQL null is encountered as the result of
240         * invoking a <code>getDate</code> method.
241         *
242         * @return the value
243         */
244        public Date getNullDate() {
245            return this.nullDate;
246        }
247    
248        /**
249         * Returns the value when a SQL null is encountered as the result of
250         * invoking a <code>getDouble</code> method.
251         *
252         * @return the value
253         */
254        public double getNullDouble() {
255            return this.nullDouble;
256        }
257    
258        /**
259         * Returns the value when a SQL null is encountered as the result of
260         * invoking a <code>getFloat</code> method.
261         *
262         * @return the value
263         */
264        public float getNullFloat() {
265            return this.nullFloat;
266        }
267    
268        /**
269         * Returns the value when a SQL null is encountered as the result of
270         * invoking a <code>getInt</code> method.
271         *
272         * @return the value
273         */
274        public int getNullInt() {
275            return this.nullInt;
276        }
277    
278        /**
279         * Returns the value when a SQL null is encountered as the result of
280         * invoking a <code>getLong</code> method.
281         *
282         * @return the value
283         */
284        public long getNullLong() {
285            return this.nullLong;
286        }
287    
288        /**
289         * Returns the value when a SQL null is encountered as the result of
290         * invoking a <code>getObject</code> method.
291         *
292         * @return the value
293         */
294        public Object getNullObject() {
295            return this.nullObject;
296        }
297    
298        /**
299         * Returns the value when a SQL null is encountered as the result of
300         * invoking a <code>getRef</code> method.
301         *
302         * @return the value
303         */
304        public Ref getNullRef() {
305            return this.nullRef;
306        }
307    
308        /**
309         * Returns the value when a SQL null is encountered as the result of
310         * invoking a <code>getShort</code> method.
311         *
312         * @return the value
313         */
314        public short getNullShort() {
315            return this.nullShort;
316        }
317    
318        /**
319         * Returns the value when a SQL null is encountered as the result of
320         * invoking a <code>getString</code> method.
321         *
322         * @return the value
323         */
324        public String getNullString() {
325            return this.nullString;
326        }
327    
328        /**
329         * Returns the value when a SQL null is encountered as the result of
330         * invoking a <code>getTime</code> method.
331         *
332         * @return the value
333         */
334        public Time getNullTime() {
335            return this.nullTime;
336        }
337    
338        /**
339         * Returns the value when a SQL null is encountered as the result of
340         * invoking a <code>getTimestamp</code> method.
341         *
342         * @return the value
343         */
344        public Timestamp getNullTimestamp() {
345            return this.nullTimestamp;
346        }
347    
348        /**
349         * Returns the value when a SQL null is encountered as the result of
350         * invoking a <code>getURL</code> method.
351         *
352         * @return the value
353         */
354        public URL getNullURL() {
355            return this.nullURL;
356        }
357    
358        /**
359         * Intercepts calls to <code>get*</code> methods and calls the appropriate
360         * <code>getNull*</code> method if the <code>ResultSet</code> returned
361         * <code>null</code>.
362         * 
363         * @throws Throwable
364         * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
365         */
366        public Object invoke(Object proxy, Method method, Object[] args)
367            throws Throwable {
368    
369            Object result = method.invoke(this.rs, args);
370    
371            Method nullMethod = (Method) nullMethods.get(method.getName());
372    
373            // Check nullMethod != null first so that we don't call wasNull()
374            // before a true getter method was invoked on the ResultSet.
375            return (nullMethod != null && this.rs.wasNull())
376                ? nullMethod.invoke(this, (Object[]) null)
377                : result;
378        }
379    
380        /**
381         * Sets the value to return when a SQL null is encountered as the result of
382         * invoking a <code>getAsciiStream</code> method.
383         *
384         * @param nullAsciiStream the value
385         */
386        public void setNullAsciiStream(InputStream nullAsciiStream) {
387            this.nullAsciiStream = nullAsciiStream;
388        }
389    
390        /**
391         * Sets the value to return when a SQL null is encountered as the result of
392         * invoking a <code>getBigDecimal</code> method.
393         *
394         * @param nullBigDecimal the value
395         */
396        public void setNullBigDecimal(BigDecimal nullBigDecimal) {
397            this.nullBigDecimal = nullBigDecimal;
398        }
399    
400        /**
401         * Sets the value to return when a SQL null is encountered as the result of
402         * invoking a <code>getBinaryStream</code> method.
403         *
404         * @param nullBinaryStream the value
405         */
406        public void setNullBinaryStream(InputStream nullBinaryStream) {
407            this.nullBinaryStream = nullBinaryStream;
408        }
409    
410        /**
411         * Sets the value to return when a SQL null is encountered as the result of
412         * invoking a <code>getBlob</code> method.
413         *
414         * @param nullBlob the value
415         */
416        public void setNullBlob(Blob nullBlob) {
417            this.nullBlob = nullBlob;
418        }
419    
420        /**
421         * Sets the value to return when a SQL null is encountered as the result of
422         * invoking a <code>getBoolean</code> method.
423         *
424         * @param nullBoolean the value
425         */
426        public void setNullBoolean(boolean nullBoolean) {
427            this.nullBoolean = nullBoolean;
428        }
429    
430        /**
431         * Sets the value to return when a SQL null is encountered as the result of
432         * invoking a <code>getByte</code> method.
433         *
434         * @param nullByte the value
435         */
436        public void setNullByte(byte nullByte) {
437            this.nullByte = nullByte;
438        }
439    
440        /**
441         * Sets the value to return when a SQL null is encountered as the result of
442         * invoking a <code>getBytes</code> method.
443         *
444         * @param nullBytes the value
445         */
446        public void setNullBytes(byte[] nullBytes) {
447            this.nullBytes = nullBytes;
448        }
449    
450        /**
451         * Sets the value to return when a SQL null is encountered as the result of
452         * invoking a <code>getCharacterStream</code> method.
453         *
454         * @param nullCharacterStream the value
455         */
456        public void setNullCharacterStream(Reader nullCharacterStream) {
457            this.nullCharacterStream = nullCharacterStream;
458        }
459    
460        /**
461         * Sets the value to return when a SQL null is encountered as the result of
462         * invoking a <code>getClob</code> method.
463         *
464         * @param nullClob the value
465         */
466        public void setNullClob(Clob nullClob) {
467            this.nullClob = nullClob;
468        }
469    
470        /**
471         * Sets the value to return when a SQL null is encountered as the result of
472         * invoking a <code>getDate</code> method.
473         *
474         * @param nullDate the value
475         */
476        public void setNullDate(Date nullDate) {
477            this.nullDate = nullDate;
478        }
479    
480        /**
481         * Sets the value to return when a SQL null is encountered as the result of
482         * invoking a <code>getDouble</code> method.
483         *
484         * @param nullDouble the value
485         */
486        public void setNullDouble(double nullDouble) {
487            this.nullDouble = nullDouble;
488        }
489    
490        /**
491         * Sets the value to return when a SQL null is encountered as the result of
492         * invoking a <code>getFloat</code> method.
493         *
494         * @param nullFloat the value
495         */
496        public void setNullFloat(float nullFloat) {
497            this.nullFloat = nullFloat;
498        }
499    
500        /**
501         * Sets the value to return when a SQL null is encountered as the result of
502         * invoking a <code>getInt</code> method.
503         *
504         * @param nullInt the value
505         */
506        public void setNullInt(int nullInt) {
507            this.nullInt = nullInt;
508        }
509    
510        /**
511         * Sets the value to return when a SQL null is encountered as the result of
512         * invoking a <code>getLong</code> method.
513         *
514         * @param nullLong the value
515         */
516        public void setNullLong(long nullLong) {
517            this.nullLong = nullLong;
518        }
519    
520        /**
521         * Sets the value to return when a SQL null is encountered as the result of
522         * invoking a <code>getObject</code> method.
523         *
524         * @param nullObject the value
525         */
526        public void setNullObject(Object nullObject) {
527            this.nullObject = nullObject;
528        }
529    
530        /**
531         * Sets the value to return when a SQL null is encountered as the result of
532         * invoking a <code>getRef</code> method.
533         *
534         * @param nullRef the value
535         */
536        public void setNullRef(Ref nullRef) {
537            this.nullRef = nullRef;
538        }
539    
540        /**
541         * Sets the value to return when a SQL null is encountered as the result of
542         * invoking a <code>getShort</code> method.
543         *
544         * @param nullShort the value
545         */
546        public void setNullShort(short nullShort) {
547            this.nullShort = nullShort;
548        }
549    
550        /**
551         * Sets the value to return when a SQL null is encountered as the result of
552         * invoking a <code>getString</code> method.
553         *
554         * @param nullString the value
555         */
556        public void setNullString(String nullString) {
557            this.nullString = nullString;
558        }
559    
560        /**
561         * Sets the value to return when a SQL null is encountered as the result of
562         * invoking a <code>getTime</code> method.
563         *
564         * @param nullTime the value
565         */
566        public void setNullTime(Time nullTime) {
567            this.nullTime = nullTime;
568        }
569    
570        /**
571         * Sets the value to return when a SQL null is encountered as the result of
572         * invoking a <code>getTimestamp</code> method.
573         *
574         * @param nullTimestamp the value
575         */
576        public void setNullTimestamp(Timestamp nullTimestamp) {
577            this.nullTimestamp = nullTimestamp;
578        }
579    
580        /**
581         * Sets the value to return when a SQL null is encountered as the result of
582         * invoking a <code>getURL</code> method.
583         *
584         * @param nullURL the value
585         */
586        public void setNullURL(URL nullURL) {
587            this.nullURL = nullURL;
588        }
589    
590    }