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
19 import java.io.InputStream;
20 import java.io.Reader;
21 import java.lang.reflect.InvocationHandler;
22 import java.lang.reflect.Method;
23 import java.math.BigDecimal;
24 import java.net.URL;
25 import java.sql.Blob;
26 import java.sql.Clob;
27 import java.sql.Date;
28 import java.sql.Ref;
29 import java.sql.ResultSet;
30 import java.sql.Time;
31 import java.sql.Timestamp;
32 import java.util.HashMap;
33 import java.util.Map;
34
35 import org.apache.commons.dbutils.ProxyFactory;
36
37 /**
38 * Decorates a {@code ResultSet} with checks for a SQL NULL value on each
39 * {@code getXXX} method. If a column value obtained by a
40 * {@code getXXX} method is not SQL NULL, the column value is returned. If
41 * the column value is SQL null, an alternate value is returned. The alternate
42 * value defaults to the Java {@code null} value, which can be overridden
43 * for instances of the class.
44 *
45 * <p>
46 * Usage example:
47 * <blockquote>
48 * <pre>
49 * Connection conn = // somehow get a connection
50 * Statement stmt = conn.createStatement();
51 * ResultSet resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1");
52 *
53 * // Wrap the result set for SQL NULL checking
54 * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet);
55 * wrapper.setNullString("---N/A---"); // Set null string
56 * wrapper.setNullInt(-999); // Set null integer
57 * resultSet = ProxyFactory.instance().createResultSet(wrapper);
58 *
59 * while (resultSet.next()) {
60 * // If col1 is SQL NULL, value returned will be "---N/A---"
61 * String col1 = resultSet.getString("col1");
62 * // If col2 is SQL NULL, value returned will be -999
63 * int col2 = resultSet.getInt("col2");
64 * }
65 * resultSet.close();
66 * </pre>
67 * </blockquote>
68 * </p>
69 * <p>Unlike some other classes in DbUtils, this class is NOT thread-safe.</p>
70 */
71 public class SqlNullCheckedResultSet implements InvocationHandler {
72
73 /**
74 * Maps normal method names (ie. "getBigDecimal") to the corresponding null
75 * Method object (ie. getNullBigDecimal).
76 */
77 private static final Map<String, Method> NULL_METHODS = new HashMap<>();
78
79 /**
80 * The {@code getNull} string prefix.
81 * @since 1.4
82 */
83 private static final String GET_NULL_PREFIX = "getNull";
84
85 static {
86 final Method[] methods = SqlNullCheckedResultSet.class.getMethods();
87 for (final Method method : methods) {
88 final String methodName = method.getName();
89
90 if (methodName.startsWith(GET_NULL_PREFIX)) {
91 final String normalName = "get" + methodName.substring(GET_NULL_PREFIX.length());
92 NULL_METHODS.put(normalName, method);
93 }
94 }
95 }
96
97 /**
98 * The factory to create proxies with.
99 */
100 private static final ProxyFactory factory = ProxyFactory.instance();
101
102 /**
103 * Wraps the {@code ResultSet} in an instance of this class. This is
104 * equivalent to:
105 * <pre>
106 * ProxyFactory.instance().createResultSet(new SqlNullCheckedResultSet(resultSet));
107 * </pre>
108 *
109 * @param resultSet The {@code ResultSet} to wrap.
110 * @return wrapped ResultSet
111 */
112 public static ResultSet wrap(final ResultSet resultSet) {
113 return factory.createResultSet(new SqlNullCheckedResultSet(resultSet));
114 }
115
116 private InputStream nullAsciiStream;
117 private BigDecimal nullBigDecimal;
118 private InputStream nullBinaryStream;
119 private Blob nullBlob;
120 private boolean nullBoolean;
121 private byte nullByte;
122 private byte[] nullBytes;
123 private Reader nullCharacterStream;
124 private Clob nullClob;
125 private Date nullDate;
126 private double nullDouble;
127 private float nullFloat;
128 private int nullInt;
129 private long nullLong;
130 private Object nullObject;
131 private Ref nullRef;
132 private short nullShort;
133 private String nullString;
134 private Time nullTime;
135 private Timestamp nullTimestamp;
136 private URL nullURL;
137
138 /**
139 * The wrapped result.
140 */
141 private final ResultSet resultSet;
142
143 /**
144 * Constructs a new instance of
145 * {@code SqlNullCheckedResultSet}
146 * to wrap the specified {@code ResultSet}.
147 * @param resultSet ResultSet to wrap
148 */
149 public SqlNullCheckedResultSet(final ResultSet resultSet) {
150 this.resultSet = resultSet;
151 }
152
153 /**
154 * Returns the value when a SQL null is encountered as the result of
155 * invoking a {@code getAsciiStream} method.
156 *
157 * @return the value
158 */
159 public InputStream getNullAsciiStream() {
160 return this.nullAsciiStream;
161 }
162
163 /**
164 * Returns the value when a SQL null is encountered as the result of
165 * invoking a {@code getBigDecimal} method.
166 *
167 * @return the value
168 */
169 public BigDecimal getNullBigDecimal() {
170 return this.nullBigDecimal;
171 }
172
173 /**
174 * Returns the value when a SQL null is encountered as the result of
175 * invoking a {@code getBinaryStream} method.
176 *
177 * @return the value
178 */
179 public InputStream getNullBinaryStream() {
180 return this.nullBinaryStream;
181 }
182
183 /**
184 * Returns the value when a SQL null is encountered as the result of
185 * invoking a {@code getBlob} method.
186 *
187 * @return the value
188 */
189 public Blob getNullBlob() {
190 return this.nullBlob;
191 }
192
193 /**
194 * Returns the value when a SQL null is encountered as the result of
195 * invoking a {@code getBoolean} method.
196 *
197 * @return the value
198 */
199 public boolean getNullBoolean() {
200 return this.nullBoolean;
201 }
202
203 /**
204 * Returns the value when a SQL null is encountered as the result of
205 * invoking a {@code getByte} method.
206 *
207 * @return the value
208 */
209 public byte getNullByte() {
210 return this.nullByte;
211 }
212
213 /**
214 * Returns the value when a SQL null is encountered as the result of
215 * invoking a {@code getBytes} method.
216 *
217 * @return the value
218 */
219 public byte[] getNullBytes() {
220 if (this.nullBytes == null) {
221 return null;
222 }
223 return this.nullBytes.clone();
224 }
225
226 /**
227 * Returns the value when a SQL null is encountered as the result of
228 * invoking a {@code getCharacterStream} method.
229 *
230 * @return the value
231 */
232 public Reader getNullCharacterStream() {
233 return this.nullCharacterStream;
234 }
235
236 /**
237 * Returns the value when a SQL null is encountered as the result of
238 * invoking a {@code getClob} method.
239 *
240 * @return the value
241 */
242 public Clob getNullClob() {
243 return this.nullClob;
244 }
245
246 /**
247 * Returns the value when a SQL null is encountered as the result of
248 * invoking a {@code getDate} method.
249 *
250 * @return the value
251 */
252 public Date getNullDate() {
253 return this.nullDate != null ? new Date(this.nullDate.getTime()) : null;
254 }
255
256 /**
257 * Returns the value when a SQL null is encountered as the result of
258 * invoking a {@code getDouble} method.
259 *
260 * @return the value
261 */
262 public double getNullDouble() {
263 return this.nullDouble;
264 }
265
266 /**
267 * Returns the value when a SQL null is encountered as the result of
268 * invoking a {@code getFloat} method.
269 *
270 * @return the value
271 */
272 public float getNullFloat() {
273 return this.nullFloat;
274 }
275
276 /**
277 * Returns the value when a SQL null is encountered as the result of
278 * invoking a {@code getInt} method.
279 *
280 * @return the value
281 */
282 public int getNullInt() {
283 return this.nullInt;
284 }
285
286 /**
287 * Returns the value when a SQL null is encountered as the result of
288 * invoking a {@code getLong} method.
289 *
290 * @return the value
291 */
292 public long getNullLong() {
293 return this.nullLong;
294 }
295
296 /**
297 * Returns the value when a SQL null is encountered as the result of
298 * invoking a {@code getObject} method.
299 *
300 * @return the value
301 */
302 public Object getNullObject() {
303 return this.nullObject;
304 }
305
306 /**
307 * Returns the value when a SQL null is encountered as the result of
308 * invoking a {@code getRef} method.
309 *
310 * @return the value
311 */
312 public Ref getNullRef() {
313 return this.nullRef;
314 }
315
316 /**
317 * Returns the value when a SQL null is encountered as the result of
318 * invoking a {@code getShort} method.
319 *
320 * @return the value
321 */
322 public short getNullShort() {
323 return this.nullShort;
324 }
325
326 /**
327 * Returns the value when a SQL null is encountered as the result of
328 * invoking a {@code getString} method.
329 *
330 * @return the value
331 */
332 public String getNullString() {
333 return this.nullString;
334 }
335
336 /**
337 * Returns the value when a SQL null is encountered as the result of
338 * invoking a {@code getTime} method.
339 *
340 * @return the value
341 */
342 public Time getNullTime() {
343 return this.nullTime != null ? new Time(this.nullTime.getTime()) : null;
344 }
345
346 /**
347 * Returns the value when a SQL null is encountered as the result of
348 * invoking a {@code getTimestamp} method.
349 *
350 * @return the value
351 */
352 public Timestamp getNullTimestamp() {
353 if (this.nullTimestamp == null) {
354 return null;
355 }
356
357 final Timestamp ts = new Timestamp(this.nullTimestamp.getTime());
358 ts.setNanos(this.nullTimestamp.getNanos());
359 return ts;
360 }
361
362 /**
363 * Returns the value when a SQL null is encountered as the result of
364 * invoking a {@code getURL} method.
365 *
366 * @return the value
367 */
368 public URL getNullURL() {
369 return this.nullURL;
370 }
371
372 /**
373 * Intercepts calls to {@code get*} methods and calls the appropriate
374 * {@code getNull*} method if the {@code ResultSet} returned
375 * {@code null}.
376 *
377 * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
378 * @param proxy Not used; all method calls go to the internal result set
379 * @param method The method to invoke on the result set
380 * @param args The arguments to pass to the result set
381 * @return null checked result
382 * @throws Throwable error
383 */
384 @Override
385 public Object invoke(final Object proxy, final Method method, final Object[] args)
386 throws Throwable {
387
388 final Object result = method.invoke(this.resultSet, args);
389
390 final Method nullMethod = NULL_METHODS.get(method.getName());
391
392 // Check nullMethod != null first so that we don't call wasNull()
393 // before a true getter method was invoked on the ResultSet.
394 return nullMethod != null && this.resultSet.wasNull()
395 ? nullMethod.invoke(this, (Object[]) null)
396 : result;
397 }
398
399 /**
400 * Sets the value to return when a SQL null is encountered as the result of
401 * invoking a {@code getAsciiStream} method.
402 *
403 * @param nullAsciiStream the value
404 */
405 public void setNullAsciiStream(final InputStream nullAsciiStream) {
406 this.nullAsciiStream = nullAsciiStream;
407 }
408
409 /**
410 * Sets the value to return when a SQL null is encountered as the result of
411 * invoking a {@code getBigDecimal} method.
412 *
413 * @param nullBigDecimal the value
414 */
415 public void setNullBigDecimal(final BigDecimal nullBigDecimal) {
416 this.nullBigDecimal = nullBigDecimal;
417 }
418
419 /**
420 * Sets the value to return when a SQL null is encountered as the result of
421 * invoking a {@code getBinaryStream} method.
422 *
423 * @param nullBinaryStream the value
424 */
425 public void setNullBinaryStream(final InputStream nullBinaryStream) {
426 this.nullBinaryStream = nullBinaryStream;
427 }
428
429 /**
430 * Sets the value to return when a SQL null is encountered as the result of
431 * invoking a {@code getBlob} method.
432 *
433 * @param nullBlob the value
434 */
435 public void setNullBlob(final Blob nullBlob) {
436 this.nullBlob = nullBlob;
437 }
438
439 /**
440 * Sets the value to return when a SQL null is encountered as the result of
441 * invoking a {@code getBoolean} method.
442 *
443 * @param nullBoolean the value
444 */
445 public void setNullBoolean(final boolean nullBoolean) {
446 this.nullBoolean = nullBoolean;
447 }
448
449 /**
450 * Sets the value to return when a SQL null is encountered as the result of
451 * invoking a {@code getByte} method.
452 *
453 * @param nullByte the value
454 */
455 public void setNullByte(final byte nullByte) {
456 this.nullByte = nullByte;
457 }
458
459 /**
460 * Sets the value to return when a SQL null is encountered as the result of
461 * invoking a {@code getBytes} method.
462 *
463 * @param nullBytes the value
464 */
465 public void setNullBytes(final byte[] nullBytes) {
466 if (nullBytes != null) {
467 this.nullBytes = nullBytes.clone();
468 } else {
469 this.nullBytes = null;
470 }
471 }
472
473 /**
474 * Sets the value to return when a SQL null is encountered as the result of
475 * invoking a {@code getCharacterStream} method.
476 *
477 * @param nullCharacterStream the value
478 */
479 public void setNullCharacterStream(final Reader nullCharacterStream) {
480 this.nullCharacterStream = nullCharacterStream;
481 }
482
483 /**
484 * Sets the value to return when a SQL null is encountered as the result of
485 * invoking a {@code getClob} method.
486 *
487 * @param nullClob the value
488 */
489 public void setNullClob(final Clob nullClob) {
490 this.nullClob = nullClob;
491 }
492
493 /**
494 * Sets the value to return when a SQL null is encountered as the result of
495 * invoking a {@code getDate} method.
496 *
497 * @param nullDate the value
498 */
499 public void setNullDate(final Date nullDate) {
500 this.nullDate = nullDate != null ? new Date(nullDate.getTime()) : null;
501 }
502
503 /**
504 * Sets the value to return when a SQL null is encountered as the result of
505 * invoking a {@code getDouble} method.
506 *
507 * @param nullDouble the value
508 */
509 public void setNullDouble(final double nullDouble) {
510 this.nullDouble = nullDouble;
511 }
512
513 /**
514 * Sets the value to return when a SQL null is encountered as the result of
515 * invoking a {@code getFloat} method.
516 *
517 * @param nullFloat the value
518 */
519 public void setNullFloat(final float nullFloat) {
520 this.nullFloat = nullFloat;
521 }
522
523 /**
524 * Sets the value to return when a SQL null is encountered as the result of
525 * invoking a {@code getInt} method.
526 *
527 * @param nullInt the value
528 */
529 public void setNullInt(final int nullInt) {
530 this.nullInt = nullInt;
531 }
532
533 /**
534 * Sets the value to return when a SQL null is encountered as the result of
535 * invoking a {@code getLong} method.
536 *
537 * @param nullLong the value
538 */
539 public void setNullLong(final long nullLong) {
540 this.nullLong = nullLong;
541 }
542
543 /**
544 * Sets the value to return when a SQL null is encountered as the result of
545 * invoking a {@code getObject} method.
546 *
547 * @param nullObject the value
548 */
549 public void setNullObject(final Object nullObject) {
550 this.nullObject = nullObject;
551 }
552
553 /**
554 * Sets the value to return when a SQL null is encountered as the result of
555 * invoking a {@code getRef} method.
556 *
557 * @param nullRef the value
558 */
559 public void setNullRef(final Ref nullRef) {
560 this.nullRef = nullRef;
561 }
562
563 /**
564 * Sets the value to return when a SQL null is encountered as the result of
565 * invoking a {@code getShort} method.
566 *
567 * @param nullShort the value
568 */
569 public void setNullShort(final short nullShort) {
570 this.nullShort = nullShort;
571 }
572
573 /**
574 * Sets the value to return when a SQL null is encountered as the result of
575 * invoking a {@code getString} method.
576 *
577 * @param nullString the value
578 */
579 public void setNullString(final String nullString) {
580 this.nullString = nullString;
581 }
582
583 /**
584 * Sets the value to return when a SQL null is encountered as the result of
585 * invoking a {@code getTime} method.
586 *
587 * @param nullTime the value
588 */
589 public void setNullTime(final Time nullTime) {
590 this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
591 }
592
593 /**
594 * Sets the value to return when a SQL null is encountered as the result of
595 * invoking a {@code getTimestamp} method.
596 *
597 * @param nullTimestamp the value
598 */
599 public void setNullTimestamp(final Timestamp nullTimestamp) {
600 if (nullTimestamp != null) {
601 this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
602 this.nullTimestamp.setNanos(nullTimestamp.getNanos());
603 } else {
604 this.nullTimestamp = null;
605 }
606 }
607
608 /**
609 * Sets the value to return when a SQL null is encountered as the result of
610 * invoking a {@code getURL} method.
611 *
612 * @param nullURL the value
613 */
614 public void setNullURL(final URL nullURL) {
615 this.nullURL = nullURL;
616 }
617
618 }