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 resultSet = stmt.executeQuery("SELECT col1, col2 FROM table1"); 052 * 053 * // Wrap the result set for SQL NULL checking 054 * SqlNullCheckedResultSet wrapper = new SqlNullCheckedResultSet(resultSet); 055 * wrapper.setNullString("---N/A---"); // Set null string 056 * wrapper.setNullInt(-999); // Set null integer 057 * resultSet = ProxyFactory.instance().createResultSet(wrapper); 058 * 059 * while (resultSet.next()) { 060 * // If col1 is SQL NULL, value returned will be "---N/A---" 061 * String col1 = resultSet.getString("col1"); 062 * // If col2 is SQL NULL, value returned will be -999 063 * int col2 = resultSet.getInt("col2"); 064 * } 065 * resultSet.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> NULL_METHODS = 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 (final 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 NULL_METHODS.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(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}