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        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            if (this.nullBytes == null) return null;
216            byte[] copy = new byte[this.nullBytes.length];
217            System.arraycopy(this.nullBytes, 0, copy, 0, this.nullBytes.length);
218            return copy;
219        }
220    
221        /**
222         * Returns the value when a SQL null is encountered as the result of
223         * invoking a <code>getCharacterStream</code> method.
224         *
225         * @return the value
226         */
227        public Reader getNullCharacterStream() {
228            return this.nullCharacterStream;
229        }
230    
231        /**
232         * Returns the value when a SQL null is encountered as the result of
233         * invoking a <code>getClob</code> method.
234         *
235         * @return the value
236         */
237        public Clob getNullClob() {
238            return this.nullClob;
239        }
240    
241        /**
242         * Returns the value when a SQL null is encountered as the result of
243         * invoking a <code>getDate</code> method.
244         *
245         * @return the value
246         */
247        public Date getNullDate() {
248            return this.nullDate;
249        }
250    
251        /**
252         * Returns the value when a SQL null is encountered as the result of
253         * invoking a <code>getDouble</code> method.
254         *
255         * @return the value
256         */
257        public double getNullDouble() {
258            return this.nullDouble;
259        }
260    
261        /**
262         * Returns the value when a SQL null is encountered as the result of
263         * invoking a <code>getFloat</code> method.
264         *
265         * @return the value
266         */
267        public float getNullFloat() {
268            return this.nullFloat;
269        }
270    
271        /**
272         * Returns the value when a SQL null is encountered as the result of
273         * invoking a <code>getInt</code> method.
274         *
275         * @return the value
276         */
277        public int getNullInt() {
278            return this.nullInt;
279        }
280    
281        /**
282         * Returns the value when a SQL null is encountered as the result of
283         * invoking a <code>getLong</code> method.
284         *
285         * @return the value
286         */
287        public long getNullLong() {
288            return this.nullLong;
289        }
290    
291        /**
292         * Returns the value when a SQL null is encountered as the result of
293         * invoking a <code>getObject</code> method.
294         *
295         * @return the value
296         */
297        public Object getNullObject() {
298            return this.nullObject;
299        }
300    
301        /**
302         * Returns the value when a SQL null is encountered as the result of
303         * invoking a <code>getRef</code> method.
304         *
305         * @return the value
306         */
307        public Ref getNullRef() {
308            return this.nullRef;
309        }
310    
311        /**
312         * Returns the value when a SQL null is encountered as the result of
313         * invoking a <code>getShort</code> method.
314         *
315         * @return the value
316         */
317        public short getNullShort() {
318            return this.nullShort;
319        }
320    
321        /**
322         * Returns the value when a SQL null is encountered as the result of
323         * invoking a <code>getString</code> method.
324         *
325         * @return the value
326         */
327        public String getNullString() {
328            return this.nullString;
329        }
330    
331        /**
332         * Returns the value when a SQL null is encountered as the result of
333         * invoking a <code>getTime</code> method.
334         *
335         * @return the value
336         */
337        public Time getNullTime() {
338            return this.nullTime;
339        }
340    
341        /**
342         * Returns the value when a SQL null is encountered as the result of
343         * invoking a <code>getTimestamp</code> method.
344         *
345         * @return the value
346         */
347        public Timestamp getNullTimestamp() {
348            return this.nullTimestamp;
349        }
350    
351        /**
352         * Returns the value when a SQL null is encountered as the result of
353         * invoking a <code>getURL</code> method.
354         *
355         * @return the value
356         */
357        public URL getNullURL() {
358            return this.nullURL;
359        }
360    
361        /**
362         * Intercepts calls to <code>get*</code> methods and calls the appropriate
363         * <code>getNull*</code> method if the <code>ResultSet</code> returned
364         * <code>null</code>.
365         *  
366         *  @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
367         * @param proxy Not used; all method calls go to the internal result set
368         * @param method The method to invoke on the result set
369         * @param args The arguments to pass to the result set
370         * @return null checked result
371         * @throws Throwable error
372         */
373        public Object invoke(Object proxy, Method method, Object[] args)
374            throws Throwable {
375    
376            Object result = method.invoke(this.rs, args);
377    
378            Method nullMethod = (Method) nullMethods.get(method.getName());
379    
380            // Check nullMethod != null first so that we don't call wasNull()
381            // before a true getter method was invoked on the ResultSet.
382            return (nullMethod != null && this.rs.wasNull())
383                ? nullMethod.invoke(this, (Object[]) null)
384                : result;
385        }
386    
387        /**
388         * Sets the value to return when a SQL null is encountered as the result of
389         * invoking a <code>getAsciiStream</code> method.
390         *
391         * @param nullAsciiStream the value
392         */
393        public void setNullAsciiStream(InputStream nullAsciiStream) {
394            this.nullAsciiStream = nullAsciiStream;
395        }
396    
397        /**
398         * Sets the value to return when a SQL null is encountered as the result of
399         * invoking a <code>getBigDecimal</code> method.
400         *
401         * @param nullBigDecimal the value
402         */
403        public void setNullBigDecimal(BigDecimal nullBigDecimal) {
404            this.nullBigDecimal = nullBigDecimal;
405        }
406    
407        /**
408         * Sets the value to return when a SQL null is encountered as the result of
409         * invoking a <code>getBinaryStream</code> method.
410         *
411         * @param nullBinaryStream the value
412         */
413        public void setNullBinaryStream(InputStream nullBinaryStream) {
414            this.nullBinaryStream = nullBinaryStream;
415        }
416    
417        /**
418         * Sets the value to return when a SQL null is encountered as the result of
419         * invoking a <code>getBlob</code> method.
420         *
421         * @param nullBlob the value
422         */
423        public void setNullBlob(Blob nullBlob) {
424            this.nullBlob = nullBlob;
425        }
426    
427        /**
428         * Sets the value to return when a SQL null is encountered as the result of
429         * invoking a <code>getBoolean</code> method.
430         *
431         * @param nullBoolean the value
432         */
433        public void setNullBoolean(boolean nullBoolean) {
434            this.nullBoolean = nullBoolean;
435        }
436    
437        /**
438         * Sets the value to return when a SQL null is encountered as the result of
439         * invoking a <code>getByte</code> method.
440         *
441         * @param nullByte the value
442         */
443        public void setNullByte(byte nullByte) {
444            this.nullByte = nullByte;
445        }
446    
447        /**
448         * Sets the value to return when a SQL null is encountered as the result of
449         * invoking a <code>getBytes</code> method.
450         *
451         * @param nullBytes the value
452         */
453        public void setNullBytes(byte[] nullBytes) {
454            byte[] copy = new byte[nullBytes.length];
455            System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
456            this.nullBytes = copy;
457        }
458    
459        /**
460         * Sets the value to return when a SQL null is encountered as the result of
461         * invoking a <code>getCharacterStream</code> method.
462         *
463         * @param nullCharacterStream the value
464         */
465        public void setNullCharacterStream(Reader nullCharacterStream) {
466            this.nullCharacterStream = nullCharacterStream;
467        }
468    
469        /**
470         * Sets the value to return when a SQL null is encountered as the result of
471         * invoking a <code>getClob</code> method.
472         *
473         * @param nullClob the value
474         */
475        public void setNullClob(Clob nullClob) {
476            this.nullClob = nullClob;
477        }
478    
479        /**
480         * Sets the value to return when a SQL null is encountered as the result of
481         * invoking a <code>getDate</code> method.
482         *
483         * @param nullDate the value
484         */
485        public void setNullDate(Date nullDate) {
486            this.nullDate = nullDate;
487        }
488    
489        /**
490         * Sets the value to return when a SQL null is encountered as the result of
491         * invoking a <code>getDouble</code> method.
492         *
493         * @param nullDouble the value
494         */
495        public void setNullDouble(double nullDouble) {
496            this.nullDouble = nullDouble;
497        }
498    
499        /**
500         * Sets the value to return when a SQL null is encountered as the result of
501         * invoking a <code>getFloat</code> method.
502         *
503         * @param nullFloat the value
504         */
505        public void setNullFloat(float nullFloat) {
506            this.nullFloat = nullFloat;
507        }
508    
509        /**
510         * Sets the value to return when a SQL null is encountered as the result of
511         * invoking a <code>getInt</code> method.
512         *
513         * @param nullInt the value
514         */
515        public void setNullInt(int nullInt) {
516            this.nullInt = nullInt;
517        }
518    
519        /**
520         * Sets the value to return when a SQL null is encountered as the result of
521         * invoking a <code>getLong</code> method.
522         *
523         * @param nullLong the value
524         */
525        public void setNullLong(long nullLong) {
526            this.nullLong = nullLong;
527        }
528    
529        /**
530         * Sets the value to return when a SQL null is encountered as the result of
531         * invoking a <code>getObject</code> method.
532         *
533         * @param nullObject the value
534         */
535        public void setNullObject(Object nullObject) {
536            this.nullObject = nullObject;
537        }
538    
539        /**
540         * Sets the value to return when a SQL null is encountered as the result of
541         * invoking a <code>getRef</code> method.
542         *
543         * @param nullRef the value
544         */
545        public void setNullRef(Ref nullRef) {
546            this.nullRef = nullRef;
547        }
548    
549        /**
550         * Sets the value to return when a SQL null is encountered as the result of
551         * invoking a <code>getShort</code> method.
552         *
553         * @param nullShort the value
554         */
555        public void setNullShort(short nullShort) {
556            this.nullShort = nullShort;
557        }
558    
559        /**
560         * Sets the value to return when a SQL null is encountered as the result of
561         * invoking a <code>getString</code> method.
562         *
563         * @param nullString the value
564         */
565        public void setNullString(String nullString) {
566            this.nullString = nullString;
567        }
568    
569        /**
570         * Sets the value to return when a SQL null is encountered as the result of
571         * invoking a <code>getTime</code> method.
572         *
573         * @param nullTime the value
574         */
575        public void setNullTime(Time nullTime) {
576            this.nullTime = nullTime;
577        }
578    
579        /**
580         * Sets the value to return when a SQL null is encountered as the result of
581         * invoking a <code>getTimestamp</code> method.
582         *
583         * @param nullTimestamp the value
584         */
585        public void setNullTimestamp(Timestamp nullTimestamp) {
586            this.nullTimestamp = nullTimestamp;
587        }
588    
589        /**
590         * Sets the value to return when a SQL null is encountered as the result of
591         * invoking a <code>getURL</code> method.
592         *
593         * @param nullURL the value
594         */
595        public void setNullURL(URL nullURL) {
596            this.nullURL = nullURL;
597        }
598    
599    }