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