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 }