All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.ext.jdbc.spi.impl.JDBCDecoderImpl Maven / Gradle / Ivy

There is a newer version: 5.0.0.CR2
Show newest version
/*
 * Copyright (c) 2011-2014 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.ext.jdbc.spi.impl;

import io.vertx.core.buffer.Buffer;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.ext.jdbc.impl.actions.JDBCTypeWrapper;
import io.vertx.ext.jdbc.impl.actions.SQLValueProvider;
import io.vertx.ext.jdbc.spi.JDBCColumnDescriptorProvider;
import io.vertx.ext.jdbc.spi.JDBCDecoder;
import io.vertx.jdbcclient.impl.actions.JDBCColumnDescriptor;
import io.vertx.sqlclient.Tuple;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.Optional;

public class JDBCDecoderImpl implements JDBCDecoder {

  private static final Logger LOG = LoggerFactory.getLogger(JDBCDecoder.class);

  @Override
  public Object parse(ResultSet rs, int pos, JDBCColumnDescriptorProvider jdbcTypeLookup) throws SQLException {
    return decode(jdbcTypeLookup.apply(pos), cls -> cls == null ? rs.getObject(pos) : rs.getObject(pos, cls));
  }

  @Override
  public Object parse(CallableStatement cs, int pos, JDBCColumnDescriptorProvider jdbcTypeLookup) throws SQLException {
    return decode(jdbcTypeLookup.apply(pos), cls -> cls == null ? cs.getObject(pos) : cs.getObject(pos, cls));
  }

  @Override
  public Object decode(JDBCColumnDescriptor descriptor, SQLValueProvider valueProvider) throws SQLException {
    if (descriptor != null) {
      if (descriptor.isArray()) {
        return decodeArray(valueProvider, descriptor);
      }
      if (descriptor.jdbcType() == JDBCType.DATALINK) {
        return decodeLink(valueProvider, descriptor);
      }
      if (descriptor.jdbcType() == JDBCType.SQLXML) {
        return decodeXML(valueProvider, descriptor);
      }
      if (descriptor.jdbcType() == JDBCType.STRUCT) {
        return decodeStruct(valueProvider, descriptor);
      }
      if (descriptor.jdbcTypeWrapper().isBinaryType()) {
        return decodeBinary(valueProvider, descriptor);
      }
      if (descriptor.jdbcTypeWrapper().isNumberType()) {
        return decodeNumber(valueProvider, descriptor);
      }
      if (descriptor.jdbcTypeWrapper().isDateTimeType()) {
        return decodeDateTime(valueProvider, descriptor);
      }
      if (descriptor.jdbcTypeWrapper().isUnhandledType()) {
        return decodeUnhandledType(valueProvider, descriptor);
      }
      if (descriptor.jdbcTypeWrapper().isSpecificVendorType()) {
        return decodeSpecificVendorType(valueProvider, descriptor);
      }
      return cast(getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass()));
    } else {
      return cast(valueProvider.apply(null));
    }
  }

  @Override
  public Object cast(Object value) throws SQLException {
    if (value == null) {
      return null;
    }

    if (value instanceof Array) {
      Array array = (Array) value;
      return this.decodeArray(array, JDBCColumnDescriptor.create(() -> null, array::getBaseType, array::getBaseTypeName,
        () -> null));
    }

    if (value instanceof Blob) {
      Blob v = (Blob) value;
      return v.length() == 0L ? Buffer.buffer(0) : streamToBuffer(v.getBinaryStream(), Blob.class);
    }

    if (value instanceof Clob) {
      Clob v = (Clob) value;
      return v.length() == 0L ? "" : streamToBuffer(v.getAsciiStream(), Clob.class).toString();
    }

    if (value instanceof Ref) {
      return cast(((Ref) value).getObject());
    }

    // RowId
    if (value instanceof RowId) {
      return ((RowId) value).getBytes();
    }

    // Struct
    if (value instanceof Struct) {
      return Tuple.of(((Struct) value).getAttributes());
    }

    if (value instanceof Date) {
      return ((Date) value).toLocalDate();
    }

    if (value instanceof Time) {
      return ((Time) value).toLocalTime();
    }

    if (value instanceof Timestamp) {
      return ((Timestamp) value).toLocalDateTime();
    }

    return value;
  }

  protected Object decodeArray(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException {
    final Object value = getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass());
    if (value instanceof Array) {
      return decodeArray((Array) value, descriptor);
    }
    return cast(value);
  }

  /**
   * Convert a value from date time JDBCType to Java date time
   *
   * @see JDBCTypeWrapper#isDateTimeType()
   */
  protected Object decodeDateTime(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException {
    try {
      return cast(valueProvider.apply(descriptor.jdbcTypeWrapper().vendorTypeClass()));
    } catch (SQLException e) {
      LOG.debug("Error when convert SQL date time. Try coerce value", e);
      Object value = valueProvider.apply(null);
      if (value == null) {
        return null;
      }
      try {
        // Some JDBC drivers (PG driver) treats Timestamp with TimeZone/Time with TimeZone
        // to java.sql.timestamp/java.sql.time/String at system timezone
        // and handles date time data type internally
        // then this code will try parse to OffsetTime/OffsetDateTime at UTC timezone with ISO8601 format
        if (value instanceof Time) {
          return Instant.ofEpochMilli(((Time) value).getTime()).atOffset(ZoneOffset.UTC).toOffsetTime();
        }
        if (descriptor.jdbcType() == JDBCType.TIME) {
          return LocalTime.parse(value.toString()).atOffset(ZoneOffset.UTC);
        }
        if (descriptor.jdbcType() == JDBCType.TIME_WITH_TIMEZONE) {
          return OffsetTime.parse(value.toString()).withOffsetSameInstant(ZoneOffset.UTC);
        }

        if (value instanceof Timestamp) {
          return ((Timestamp) value).toInstant().atOffset(ZoneOffset.UTC);
        }
        if (descriptor.jdbcType() == JDBCType.TIMESTAMP) {
          return LocalDateTime.parse(value.toString()).atOffset(ZoneOffset.UTC);
        }
        if (descriptor.jdbcType() == JDBCType.TIMESTAMP_WITH_TIMEZONE) {
          return OffsetDateTime.parse(value.toString()).withOffsetSameInstant(ZoneOffset.UTC);
        }
      } catch (DateTimeParseException ex) {
        LOG.debug("Error when coerce date time value", ex);
      }
      return cast(value);
    }
  }

  /**
   * Convert a value from Number JDBCType to Number
   *
   * @see JDBCTypeWrapper#isNumberType()
   */
  protected Object decodeNumber(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException {
    try {
      return cast(valueProvider.apply(descriptor.jdbcTypeWrapper().vendorTypeClass()));
    } catch (SQLException e) {
      LOG.debug("Error when convert SQL number", e);
      return cast(valueProvider.apply(null));
    }
  }

  /**
   * Convert a value from {@link JDBCTypeWrapper#isBinaryType()} datatype to {@link Buffer}.
   * 

* Keep value as it is if the actual value's type is not {@code byte[]} * * @see JDBCTypeWrapper#isBinaryType() */ protected Object decodeBinary(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { Object v = getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass()); return v instanceof byte[] ? Buffer.buffer((byte[]) v) : cast(v); } /** * Convert a value from {@link JDBCType#STRUCT} datatype to {@link Tuple} *

* Fallback to {@link #decodeUnhandledType(SQLValueProvider, JDBCColumnDescriptor)} if the actual value's type is not * {@link Struct} */ protected Object decodeStruct(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { Object v = getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass()); if (v instanceof Struct) { return cast(v); } return decodeUnhandledType(valueProvider, descriptor); } /** * Convert a value from {@link JDBCType#DATALINK} datatype to {@link URL} *

* Keep value as it is if the actual value's type is not {@code URL} or {@code String} */ protected Object decodeLink(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { Object v = getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass()); if (v instanceof URL) { return v; } if (v instanceof String) { try { return new URL((String) v); } catch (MalformedURLException e) { throw new SQLException("Unable read data link", e); } } return cast(v); } /** * Convert a value from {@link JDBCType#SQLXML} datatype to {@link Buffer} *

* Fallback to {@link #decodeUnhandledType(SQLValueProvider, JDBCColumnDescriptor)}} if the actual value's type is not * {@link SQLXML} */ protected Object decodeXML(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { Object v = getCoerceObject(valueProvider, descriptor.jdbcTypeWrapper().vendorTypeClass()); if (v instanceof SQLXML) { return streamToBuffer(((SQLXML) v).getBinaryStream(), descriptor.jdbcTypeWrapper().vendorTypeClass()); } return decodeUnhandledType(valueProvider, descriptor); } /** * Convert a value from the unhandled data type *

* The default implementation converts any data type to a string value * * @return value * @see JDBCTypeWrapper#isUnhandledType() */ protected Object decodeUnhandledType(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { LOG.debug("Fallback to string when handling the unhandled JDBCType in Vertx " + descriptor); return Optional.ofNullable(cast(valueProvider.apply(null))).map(Object::toString).orElse(null); } /** * Convert a value from the {@code specific SQL vendor data type} to {@code Java} value *

* The default implementation converts any data type to a string value * * @return value * @see JDBCTypeWrapper#isSpecificVendorType() */ protected Object decodeSpecificVendorType(SQLValueProvider valueProvider, JDBCColumnDescriptor descriptor) throws SQLException { LOG.debug("Fallback to string when handling the specific SQL vendor data type " + descriptor); return Optional.ofNullable(cast(valueProvider.apply(null))).map(Object::toString).orElse(null); } protected Object decodeArray(Array value, JDBCColumnDescriptor baseType) throws SQLException { try { Object arr = value.getArray(); if (arr != null) { int len = java.lang.reflect.Array.getLength(arr); Object[] castedArray = new Object[len]; for (int i = 0; i < len; i++) { int index = i; castedArray[i] = decode(baseType, cls -> java.lang.reflect.Array.get(arr, index)); } return castedArray; } return value; } finally { value.free(); } } protected Object getCoerceObject(SQLValueProvider valueProvider, Class cls) throws SQLException { try { return valueProvider.apply(null); } catch (SQLException e) { return valueProvider.apply(cls); } } protected Buffer streamToBuffer(InputStream is, Class dataTypeClass) throws SQLException { try (InputStream in = is) { Buffer buffer = Buffer.buffer(1024); byte[] buf = new byte[1024]; int l; while ((l = in.read(buf)) > -1) { buffer.appendBytes(buf, 0, l); } return buffer; } catch (IOException ioe) { throw new SQLException("Unable to read binary stream from " + dataTypeClass.getName(), ioe); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy