--- /dev/null
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements. See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to you under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.calcite.avatica;\r
+\r
+import org.apache.calcite.avatica.remote.TypedValue;\r
+import org.apache.calcite.avatica.util.Cursor;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.Reader;\r
+import java.math.BigDecimal;\r
+import java.math.BigInteger;\r
+import java.net.URL;\r
+import java.nio.charset.StandardCharsets;\r
+import java.sql.Array;\r
+import java.sql.Blob;\r
+import java.sql.Clob;\r
+import java.sql.Date;\r
+import java.sql.NClob;\r
+import java.sql.Ref;\r
+import java.sql.RowId;\r
+import java.sql.SQLException;\r
+import java.sql.SQLXML;\r
+import java.sql.Time;\r
+import java.sql.Timestamp;\r
+import java.sql.Types;\r
+import java.util.Calendar;\r
+\r
+/**\r
+ * A location that a value can be written to or read from.\r
+ */\r
+public class AvaticaSite {\r
+ final AvaticaParameter parameter;\r
+\r
+ /** Calendar is not thread-safe. But calendar is only used from within one\r
+ * thread, and we have to trust that clients are not modifying calendars\r
+ * that they pass to us in a method such as\r
+ * {@link java.sql.PreparedStatement#setTime(int, Time, Calendar)}, so we do\r
+ * not need to synchronize access. */\r
+ final Calendar calendar;\r
+ private final int index;\r
+ final TypedValue[] slots;\r
+\r
+ /** Value that means the parameter has been set to null.\r
+ * If value is null, parameter has not been set. */\r
+ public static final Object DUMMY_VALUE = Dummy.INSTANCE;\r
+\r
+ public AvaticaSite(AvaticaParameter parameter, Calendar calendar, int index,\r
+ TypedValue[] slots) {\r
+ assert calendar != null;\r
+ assert parameter != null;\r
+ assert slots != null;\r
+ this.parameter = parameter;\r
+ this.calendar = calendar;\r
+ this.index = index;\r
+ this.slots = slots;\r
+ }\r
+\r
+ private TypedValue wrap(ColumnMetaData.Rep rep, Object o,\r
+ Calendar calendar) {\r
+ return TypedValue.ofJdbc(rep, o, calendar);\r
+ }\r
+\r
+ private TypedValue wrap(ColumnMetaData.Rep rep, Object o) {\r
+ return TypedValue.ofJdbc(rep, o, calendar);\r
+ }\r
+\r
+ public boolean isSet(int index) {\r
+ return slots[index] != null;\r
+ }\r
+\r
+ public void setByte(byte o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.BYTE, o);\r
+ }\r
+\r
+ public void setChar(char o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.CHARACTER, o);\r
+ }\r
+\r
+ public void setShort(short o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.SHORT, o);\r
+ }\r
+\r
+ public void setInt(int o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.INTEGER, o);\r
+ }\r
+\r
+ public void setLong(long o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.LONG, o);\r
+ }\r
+\r
+ public void setBoolean(boolean o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.BOOLEAN, o);\r
+ }\r
+\r
+ public void setRowId(RowId x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.OBJECT, x);\r
+ }\r
+\r
+ public void setNString(String o) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.STRING, o);\r
+ }\r
+\r
+ public void setNCharacterStream(Reader value, long length) {\r
+ }\r
+\r
+ public void setNClob(NClob value) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.OBJECT, value);\r
+ }\r
+\r
+ public void setClob(Reader reader, long length) {\r
+ }\r
+\r
+ public void setBlob(InputStream inputStream, long length) {\r
+ }\r
+\r
+ public void setNClob(Reader reader, long length) {\r
+ }\r
+\r
+ public void setSQLXML(SQLXML xmlObject) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.OBJECT, xmlObject);\r
+ }\r
+\r
+ public void setAsciiStream(InputStream x, long length) {\r
+ }\r
+\r
+ public void setBinaryStream(InputStream x, long length) {\r
+ }\r
+\r
+ public void setCharacterStream(Reader reader, long length) {\r
+ }\r
+\r
+ public void setAsciiStream(InputStream x) {\r
+ }\r
+\r
+ public void setBinaryStream(InputStream x) {\r
+ }\r
+\r
+ public void setCharacterStream(Reader reader) {\r
+ }\r
+\r
+ public void setNCharacterStream(Reader value) {\r
+ }\r
+\r
+ public void setClob(Reader reader) {\r
+ }\r
+ \r
+ public void setClob(InputStream inputStream) {\r
+ }\r
+\r
+ public void setBlob(InputStream inputStream) {\r
+ }\r
+\r
+ public void setNClob(Reader reader) {\r
+ }\r
+\r
+ public void setUnicodeStream(InputStream x, int length) {\r
+ }\r
+\r
+ public void setFloat(float x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.FLOAT, x);\r
+ }\r
+\r
+ public void setDouble(double x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.DOUBLE, x);\r
+ }\r
+\r
+ public void setBigDecimal(BigDecimal x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.NUMBER, x);\r
+ }\r
+\r
+ public void setString(String x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.STRING, x);\r
+ }\r
+\r
+ public void setBytes(byte[] x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.BYTE_STRING, x);\r
+ }\r
+\r
+ public void setTimestamp(Timestamp x, Calendar calendar) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_TIMESTAMP, x, calendar);\r
+ }\r
+\r
+ public void setTime(Time x, Calendar calendar) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_TIME, x, calendar);\r
+ }\r
+\r
+ public void setDate(Date x, Calendar calendar) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_DATE, x, calendar);\r
+ }\r
+\r
+ public void setObject(Object x, int targetSqlType) {\r
+ if (x == null || Types.NULL == targetSqlType) {\r
+ setNull(targetSqlType);\r
+ return;\r
+ }\r
+ switch (targetSqlType) {\r
+ case Types.CLOB:\r
+ if (x instanceof Clob) {\r
+ setClob((Clob) x);\r
+ break;\r
+ } else if (x instanceof InputStream) {\r
+ setClob((InputStream) x);\r
+ }\r
+ throw unsupportedCast(x.getClass(), Blob.class);\r
+ case Types.DATALINK:\r
+ case Types.NCLOB:\r
+ case Types.OTHER:\r
+ case Types.REF:\r
+ case Types.SQLXML:\r
+ case Types.STRUCT:\r
+ throw notImplemented();\r
+ case Types.ARRAY:\r
+ setArray(toArray(x));\r
+ break;\r
+ case Types.BIGINT:\r
+ setLong(toLong(x));\r
+ break;\r
+ case Types.BINARY:\r
+ case Types.LONGVARBINARY:\r
+ case Types.VARBINARY:\r
+ setBytes(toBytes(x));\r
+ break;\r
+ case Types.BIT:\r
+ case Types.BOOLEAN:\r
+ setBoolean(toBoolean(x));\r
+ break;\r
+ case Types.BLOB:\r
+ if (x instanceof Blob) {\r
+ setBlob((Blob) x);\r
+ break;\r
+ } else if (x instanceof InputStream) {\r
+ setBlob((InputStream) x);\r
+ }\r
+ throw unsupportedCast(x.getClass(), Blob.class);\r
+ case Types.DATE:\r
+ setDate(toDate(x), calendar);\r
+ break;\r
+ case Types.DECIMAL:\r
+ case Types.NUMERIC:\r
+ setBigDecimal(toBigDecimal(x));\r
+ break;\r
+ case Types.DISTINCT:\r
+ throw notImplemented();\r
+ case Types.DOUBLE:\r
+ case Types.FLOAT: // yes really; SQL FLOAT is up to 8 bytes\r
+ setDouble(toDouble(x));\r
+ break;\r
+ case Types.INTEGER:\r
+ setInt(toInt(x));\r
+ break;\r
+ case Types.JAVA_OBJECT:\r
+ setObject(x);\r
+ break;\r
+ case Types.LONGNVARCHAR:\r
+ case Types.LONGVARCHAR:\r
+ case Types.NVARCHAR:\r
+ case Types.VARCHAR:\r
+ case Types.CHAR:\r
+ case Types.NCHAR:\r
+ setString(toString(x));\r
+ break;\r
+ case Types.REAL:\r
+ setFloat(toFloat(x));\r
+ break;\r
+ case Types.ROWID:\r
+ if (x instanceof RowId) {\r
+ setRowId((RowId) x);\r
+ break;\r
+ }\r
+ throw unsupportedCast(x.getClass(), RowId.class);\r
+ case Types.SMALLINT:\r
+ setShort(toShort(x));\r
+ break;\r
+ case Types.TIME:\r
+ setTime(toTime(x), calendar);\r
+ break;\r
+ case Types.TIMESTAMP:\r
+ setTimestamp(toTimestamp(x), calendar);\r
+ break;\r
+ case Types.TINYINT:\r
+ setByte(toByte(x));\r
+ break;\r
+ default:\r
+ throw notImplemented();\r
+ }\r
+ }\r
+\r
+ /** Similar logic to {@link #setObject}. */\r
+ public static Object get(Cursor.Accessor accessor, int targetSqlType,\r
+ Calendar localCalendar) throws SQLException {\r
+ switch (targetSqlType) {\r
+ case Types.CLOB:\r
+ return accessor.getClob();\r
+ case Types.DATALINK:\r
+ case Types.NCLOB:\r
+ case Types.REF:\r
+ case Types.SQLXML:\r
+ case Types.STRUCT:\r
+ throw notImplemented();\r
+ case Types.ARRAY:\r
+ return accessor.getArray();\r
+ case Types.BIGINT:\r
+ final long aLong = accessor.getLong();\r
+ if (aLong == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aLong;\r
+ case Types.BINARY:\r
+ case Types.LONGVARBINARY:\r
+ case Types.VARBINARY:\r
+ return accessor.getBytes();\r
+ case Types.BIT:\r
+ case Types.BOOLEAN:\r
+ final boolean aBoolean = accessor.getBoolean();\r
+ if (!aBoolean && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aBoolean;\r
+ case Types.BLOB:\r
+ return accessor.getBlob();\r
+ case Types.DATE:\r
+ return accessor.getDate(localCalendar);\r
+ case Types.DECIMAL:\r
+ case Types.NUMERIC:\r
+ return accessor.getBigDecimal();\r
+ case Types.DISTINCT:\r
+ throw notImplemented();\r
+ case Types.DOUBLE:\r
+ case Types.FLOAT: // yes really; SQL FLOAT is up to 8 bytes\r
+ final double aDouble = accessor.getDouble();\r
+ if (aDouble == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aDouble;\r
+ case Types.INTEGER:\r
+ final int anInt = accessor.getInt();\r
+ if (anInt == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return anInt;\r
+ case Types.JAVA_OBJECT:\r
+ case Types.OTHER:\r
+ return accessor.getObject();\r
+ case Types.LONGNVARCHAR:\r
+ case Types.LONGVARCHAR:\r
+ case Types.NVARCHAR:\r
+ case Types.VARCHAR:\r
+ case Types.CHAR:\r
+ case Types.NCHAR:\r
+ return accessor.getString();\r
+ case Types.REAL:\r
+ final float aFloat = accessor.getFloat();\r
+ if (aFloat == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aFloat;\r
+ case Types.ROWID:\r
+ throw notImplemented();\r
+ case Types.SMALLINT:\r
+ final short aShort = accessor.getShort();\r
+ if (aShort == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aShort;\r
+ case Types.TIME:\r
+ return accessor.getTime(localCalendar);\r
+ case Types.TIMESTAMP:\r
+ return accessor.getTimestamp(localCalendar);\r
+ case Types.TINYINT:\r
+ final byte aByte = accessor.getByte();\r
+ if (aByte == 0 && accessor.wasNull()) {\r
+ return null;\r
+ }\r
+ return aByte;\r
+ default:\r
+ throw notImplemented();\r
+ }\r
+ }\r
+\r
+ public void setObject(Object x) {\r
+ slots[index] = TypedValue.ofJdbc(x, calendar);\r
+ }\r
+\r
+ public void setNull(int sqlType) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.OBJECT, null);\r
+ }\r
+\r
+ public void setRef(Ref x) {\r
+ }\r
+\r
+ public void setBlob(Blob x) {\r
+ InputStream iStream;\r
+ try {\r
+ iStream = x.getBinaryStream();\r
+ int length =0;\r
+ while(iStream.read() != -1)\r
+ length ++;\r
+ setBytes(x.getBytes(1, length));\r
+ \r
+ } catch (SQLException | IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ \r
+ }\r
+\r
+ public void setClob(Clob x) {\r
+ }\r
+\r
+ public void setArray(Array x) {\r
+ slots[index] = wrap(ColumnMetaData.Rep.ARRAY, x);\r
+ }\r
+\r
+ public void setNull(int sqlType, String typeName) {\r
+ }\r
+\r
+ public void setURL(URL x) {\r
+ }\r
+\r
+ public void setObject(Object x, int targetSqlType,\r
+ int scaleOrLength) {\r
+ }\r
+\r
+ private static RuntimeException unsupportedCast(Class<?> from, Class<?> to) {\r
+ return new UnsupportedOperationException("Cannot convert from "\r
+ + from.getCanonicalName() + " to " + to.getCanonicalName());\r
+ }\r
+\r
+ private static RuntimeException notImplemented() {\r
+ return new RuntimeException("not implemented");\r
+ }\r
+\r
+ private static Array toArray(Object x) {\r
+ if (x instanceof Array) {\r
+ return (Array) x;\r
+ }\r
+ throw unsupportedCast(x.getClass(), Array.class);\r
+ }\r
+\r
+ public static BigDecimal toBigDecimal(Object x) {\r
+ if (x instanceof BigDecimal) {\r
+ return (BigDecimal) x;\r
+ } else if (x instanceof BigInteger) {\r
+ return new BigDecimal((BigInteger) x);\r
+ } else if (x instanceof Number) {\r
+ if (x instanceof Double || x instanceof Float) {\r
+ return new BigDecimal(((Number) x).doubleValue());\r
+ } else {\r
+ return new BigDecimal(((Number) x).longValue());\r
+ }\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? BigDecimal.ONE : BigDecimal.ZERO;\r
+ } else if (x instanceof String) {\r
+ return new BigDecimal((String) x);\r
+ }\r
+ throw unsupportedCast(x.getClass(), BigDecimal.class);\r
+ }\r
+\r
+ private static boolean toBoolean(Object x) {\r
+ if (x instanceof Boolean) {\r
+ return (Boolean) x;\r
+ } else if (x instanceof Number) {\r
+ return ((Number) x).intValue() != 0;\r
+ } else if (x instanceof String) {\r
+ String s = (String) x;\r
+ if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("yes")) {\r
+ return true;\r
+ } else if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("no")) {\r
+ return false;\r
+ }\r
+ }\r
+ throw unsupportedCast(x.getClass(), Boolean.TYPE);\r
+ }\r
+\r
+ private static byte toByte(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).byteValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? (byte) 1 : (byte) 0;\r
+ } else if (x instanceof String) {\r
+ return Byte.parseByte((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Byte.TYPE);\r
+ }\r
+ }\r
+\r
+ private static byte[] toBytes(Object x) {\r
+ if (x instanceof byte[]) {\r
+ return (byte[]) x;\r
+ }\r
+ if (x instanceof String) {\r
+ return ((String) x).getBytes(StandardCharsets.UTF_8);\r
+ }\r
+ throw unsupportedCast(x.getClass(), byte[].class);\r
+ }\r
+\r
+ private static Date toDate(Object x) {\r
+ if (x instanceof String) {\r
+ return Date.valueOf((String) x);\r
+ }\r
+ return new Date(toLong(x));\r
+ }\r
+\r
+ private static Time toTime(Object x) {\r
+ if (x instanceof String) {\r
+ return Time.valueOf((String) x);\r
+ }\r
+ return new Time(toLong(x));\r
+ }\r
+\r
+ private static Timestamp toTimestamp(Object x) {\r
+ if (x instanceof String) {\r
+ return Timestamp.valueOf((String) x);\r
+ }\r
+ return new Timestamp(toLong(x));\r
+ }\r
+\r
+ private static double toDouble(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).doubleValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? 1D : 0D;\r
+ } else if (x instanceof String) {\r
+ return Double.parseDouble((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Double.TYPE);\r
+ }\r
+ }\r
+\r
+ private static float toFloat(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).floatValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? 1F : 0F;\r
+ } else if (x instanceof String) {\r
+ return Float.parseFloat((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Float.TYPE);\r
+ }\r
+ }\r
+\r
+ private static int toInt(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).intValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? 1 : 0;\r
+ } else if (x instanceof String) {\r
+ return Integer.parseInt((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Integer.TYPE);\r
+ }\r
+ }\r
+\r
+ private static long toLong(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).longValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? 1L : 0L;\r
+ } else if (x instanceof String) {\r
+ return Long.parseLong((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Long.TYPE);\r
+ }\r
+ }\r
+\r
+ private static short toShort(Object x) {\r
+ if (x instanceof Number) {\r
+ return ((Number) x).shortValue();\r
+ } else if (x instanceof Boolean) {\r
+ return (Boolean) x ? (short) 1 : (short) 0;\r
+ } else if (x instanceof String) {\r
+ return Short.parseShort((String) x);\r
+ } else {\r
+ throw unsupportedCast(x.getClass(), Short.TYPE);\r
+ }\r
+ }\r
+\r
+ private static String toString(Object x) {\r
+ if (x instanceof String) {\r
+ return (String) x;\r
+ } else if (x instanceof Character\r
+ || x instanceof Boolean) {\r
+ return x.toString();\r
+ }\r
+ throw unsupportedCast(x.getClass(), String.class);\r
+ }\r
+\r
+ /** Singleton value to denote parameters that have been set to null (as\r
+ * opposed to not set).\r
+ *\r
+ * <p>Not a valid value for a parameter.\r
+ *\r
+ * <p>As an enum, it is serializable by Jackson. */\r
+ private enum Dummy {\r
+ INSTANCE\r
+ }\r
+}\r
+\r
+// End AvaticaSite.java\r
--- /dev/null
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements. See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to you under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.calcite.avatica.util;\r
+\r
+import java.io.InputStream;\r
+import java.io.Reader;\r
+import java.lang.reflect.Field;\r
+import java.math.BigDecimal;\r
+import java.math.RoundingMode;\r
+import java.net.URL;\r
+import java.nio.charset.StandardCharsets;\r
+import java.sql.Array;\r
+import java.sql.Blob;\r
+import java.sql.Clob;\r
+import java.sql.Date;\r
+import java.sql.NClob;\r
+import java.sql.Ref;\r
+import java.sql.SQLDataException;\r
+import java.sql.SQLException;\r
+import java.sql.SQLXML;\r
+import java.sql.Struct;\r
+import java.sql.Time;\r
+import java.sql.Timestamp;\r
+import java.sql.Types;\r
+import java.util.ArrayList;\r
+import java.util.Calendar;\r
+import java.util.List;\r
+import java.util.Map;\r
+import javax.sql.rowset.serial.SerialBlob;\r
+import org.apache.calcite.avatica.AvaticaSite;\r
+import org.apache.calcite.avatica.AvaticaUtils;\r
+import org.apache.calcite.avatica.ColumnMetaData;\r
+\r
+/**\r
+ * Base class for implementing a cursor.\r
+ *\r
+ * <p>Derived class needs to provide {@link Getter} and can override\r
+ * {@link org.apache.calcite.avatica.util.Cursor.Accessor} implementations if it\r
+ * wishes.</p>\r
+ */\r
+public abstract class AbstractCursor implements Cursor {\r
+ /**\r
+ * Slot into which each accessor should write whether the\r
+ * value returned was null.\r
+ */\r
+ protected final boolean[] wasNull = {false};\r
+\r
+ protected AbstractCursor() {\r
+ }\r
+\r
+ public boolean wasNull() {\r
+ return wasNull[0];\r
+ }\r
+\r
+ public List<Accessor> createAccessors(List<ColumnMetaData> types,\r
+ Calendar localCalendar, ArrayImpl.Factory factory) {\r
+ List<Accessor> accessors = new ArrayList<>();\r
+ for (ColumnMetaData type : types) {\r
+ accessors.add(\r
+ createAccessor(type, accessors.size(), localCalendar, factory));\r
+ }\r
+ return accessors;\r
+ }\r
+\r
+ protected Accessor createAccessor(ColumnMetaData columnMetaData, int ordinal,\r
+ Calendar localCalendar, ArrayImpl.Factory factory) {\r
+ // Create an accessor appropriate to the underlying type; the accessor\r
+ // can convert to any type in the same family.\r
+ Getter getter = createGetter(ordinal);\r
+ return createAccessor(columnMetaData, getter, localCalendar, factory);\r
+ }\r
+\r
+ protected Accessor createAccessor(ColumnMetaData columnMetaData,\r
+ Getter getter, Calendar localCalendar, ArrayImpl.Factory factory) {\r
+ switch (columnMetaData.type.rep) {\r
+ case NUMBER:\r
+ switch (columnMetaData.type.id) {\r
+ case Types.TINYINT:\r
+ case Types.SMALLINT:\r
+ case Types.INTEGER:\r
+ case Types.BIGINT:\r
+ case Types.REAL:\r
+ case Types.FLOAT:\r
+ case Types.DOUBLE:\r
+ case Types.NUMERIC:\r
+ case Types.DECIMAL:\r
+ return new NumberAccessor(getter, columnMetaData.scale);\r
+ }\r
+ }\r
+ switch (columnMetaData.type.id) {\r
+ case Types.TINYINT:\r
+ return new ByteAccessor(getter);\r
+ case Types.SMALLINT:\r
+ return new ShortAccessor(getter);\r
+ case Types.INTEGER:\r
+ return new IntAccessor(getter);\r
+ case Types.BIGINT:\r
+ return new LongAccessor(getter);\r
+ case Types.BOOLEAN:\r
+ return new BooleanAccessor(getter);\r
+ case Types.REAL:\r
+ return new FloatAccessor(getter);\r
+ case Types.FLOAT:\r
+ case Types.DOUBLE:\r
+ return new DoubleAccessor(getter);\r
+ case Types.DECIMAL:\r
+ return new BigDecimalAccessor(getter);\r
+ case Types.CHAR:\r
+ switch (columnMetaData.type.rep) {\r
+ case PRIMITIVE_CHAR:\r
+ case CHARACTER:\r
+ return new StringFromCharAccessor(getter, columnMetaData.displaySize);\r
+ default:\r
+ return new FixedStringAccessor(getter, columnMetaData.displaySize);\r
+ }\r
+ case Types.VARCHAR:\r
+ return new StringAccessor(getter);\r
+ case Types.BINARY:\r
+ case Types.VARBINARY:\r
+ switch (columnMetaData.type.rep) {\r
+ case STRING:\r
+ return new BinaryFromStringAccessor(getter);\r
+ default:\r
+ return new BinaryAccessor(getter);\r
+ }\r
+ case Types.DATE:\r
+ switch (columnMetaData.type.rep) {\r
+ case PRIMITIVE_INT:\r
+ case INTEGER:\r
+ case NUMBER:\r
+ return new DateFromNumberAccessor(getter, localCalendar);\r
+ case JAVA_SQL_DATE:\r
+ return new DateAccessor(getter);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case Types.TIME:\r
+ switch (columnMetaData.type.rep) {\r
+ case PRIMITIVE_INT:\r
+ case INTEGER:\r
+ case NUMBER:\r
+ return new TimeFromNumberAccessor(getter, localCalendar);\r
+ case JAVA_SQL_TIME:\r
+ return new TimeAccessor(getter);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case Types.TIMESTAMP:\r
+ switch (columnMetaData.type.rep) {\r
+ case PRIMITIVE_LONG:\r
+ case LONG:\r
+ case NUMBER:\r
+ return new TimestampFromNumberAccessor(getter, localCalendar);\r
+ case JAVA_SQL_TIMESTAMP:\r
+ return new TimestampAccessor(getter);\r
+ case JAVA_UTIL_DATE:\r
+ return new TimestampFromUtilDateAccessor(getter, localCalendar);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case 2013: // TIME_WITH_TIMEZONE\r
+ switch (columnMetaData.type.rep) {\r
+ case STRING:\r
+ return new StringAccessor(getter);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case 2014: // TIMESTAMP_WITH_TIMEZONE\r
+ switch (columnMetaData.type.rep) {\r
+ case STRING:\r
+ return new StringAccessor(getter);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case Types.ARRAY:\r
+ final ColumnMetaData.ArrayType arrayType =\r
+ (ColumnMetaData.ArrayType) columnMetaData.type;\r
+ final SlotGetter componentGetter = new SlotGetter();\r
+ final Accessor componentAccessor =\r
+ createAccessor(ColumnMetaData.dummy(arrayType.getComponent(), true),\r
+ componentGetter, localCalendar, factory);\r
+ return new ArrayAccessor(getter, arrayType.getComponent(), componentAccessor,\r
+ componentGetter, factory);\r
+ case Types.STRUCT:\r
+ switch (columnMetaData.type.rep) {\r
+ case OBJECT:\r
+ final ColumnMetaData.StructType structType =\r
+ (ColumnMetaData.StructType) columnMetaData.type;\r
+ List<Accessor> accessors = new ArrayList<>();\r
+ for (ColumnMetaData column : structType.columns) {\r
+ final Getter fieldGetter =\r
+ structType.columns.size() == 1\r
+ ? getter\r
+ : new StructGetter(getter, column);\r
+ accessors.add(\r
+ createAccessor(column, fieldGetter, localCalendar, factory));\r
+ }\r
+ return new StructAccessor(getter, accessors);\r
+ default:\r
+ throw new AssertionError("bad " + columnMetaData.type.rep);\r
+ }\r
+ case Types.JAVA_OBJECT:\r
+ case Types.OTHER: // e.g. map\r
+ if (columnMetaData.type.getName().startsWith("INTERVAL_")) {\r
+ int end = columnMetaData.type.getName().indexOf("(");\r
+ if (end < 0) {\r
+ end = columnMetaData.type.getName().length();\r
+ }\r
+ TimeUnitRange range =\r
+ TimeUnitRange.valueOf(\r
+ columnMetaData.type.getName().substring("INTERVAL_".length(), end));\r
+ if (range.monthly()) {\r
+ return new IntervalYearMonthAccessor(getter, range);\r
+ } else {\r
+ return new IntervalDayTimeAccessor(getter, range,\r
+ columnMetaData.scale);\r
+ }\r
+ }\r
+ return new ObjectAccessor(getter);\r
+ default:\r
+ throw new RuntimeException("unknown type " + columnMetaData.type.id);\r
+ }\r
+ }\r
+\r
+ protected abstract Getter createGetter(int ordinal);\r
+\r
+ public abstract boolean next();\r
+\r
+ /** Accesses a timestamp value as a string.\r
+ * The timestamp is in SQL format (e.g. "2013-09-22 22:30:32"),\r
+ * not Java format ("2013-09-22 22:30:32.123"). */\r
+ private static String timestampAsString(long v, Calendar calendar) {\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return DateTimeUtils.unixTimestampToString(v);\r
+ }\r
+\r
+ /** Accesses a date value as a string, e.g. "2013-09-22". */\r
+ private static String dateAsString(int v, Calendar calendar) {\r
+ AvaticaUtils.discard(calendar); // time zone shift doesn't make sense\r
+ return DateTimeUtils.unixDateToString(v);\r
+ }\r
+\r
+ /** Accesses a time value as a string, e.g. "22:30:32". */\r
+ private static String timeAsString(int v, Calendar calendar) {\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return DateTimeUtils.unixTimeToString(v);\r
+ }\r
+\r
+ private static Date longToDate(long v, Calendar calendar) {\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return new Date(v);\r
+ }\r
+\r
+ static Time intToTime(int v, Calendar calendar) {\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return new Time(v);\r
+ }\r
+\r
+ static Timestamp longToTimestamp(long v, Calendar calendar) {\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return new Timestamp(v);\r
+ }\r
+\r
+ /** Implementation of {@link Cursor.Accessor}. */\r
+ static class AccessorImpl implements Accessor {\r
+ protected final Getter getter;\r
+\r
+ AccessorImpl(Getter getter) {\r
+ assert getter != null;\r
+ this.getter = getter;\r
+ }\r
+\r
+ public boolean wasNull() throws SQLException {\r
+ return getter.wasNull();\r
+ }\r
+\r
+ public String getString() throws SQLException {\r
+ final Object o = getObject();\r
+ return o == null ? null : o.toString();\r
+ }\r
+\r
+ public boolean getBoolean() throws SQLException {\r
+ return getLong() != 0L;\r
+ }\r
+\r
+ public byte getByte() throws SQLException {\r
+ return (byte) getLong();\r
+ }\r
+\r
+ public short getShort() throws SQLException {\r
+ return (short) getLong();\r
+ }\r
+\r
+ public int getInt() throws SQLException {\r
+ return (int) getLong();\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ throw cannotConvert("long");\r
+ }\r
+\r
+ public float getFloat() throws SQLException {\r
+ return (float) getDouble();\r
+ }\r
+\r
+ public double getDouble() throws SQLException {\r
+ throw cannotConvert("double");\r
+ }\r
+\r
+ public BigDecimal getBigDecimal() throws SQLException {\r
+ throw cannotConvert("BigDecimal");\r
+ }\r
+\r
+ public BigDecimal getBigDecimal(int scale) throws SQLException {\r
+ throw cannotConvert("BigDecimal with scale");\r
+ }\r
+\r
+ public byte[] getBytes() throws SQLException {\r
+ throw cannotConvert("byte[]");\r
+ }\r
+\r
+ public InputStream getAsciiStream() throws SQLException {\r
+ throw cannotConvert("InputStream (ascii)");\r
+ }\r
+\r
+ public InputStream getUnicodeStream() throws SQLException {\r
+ throw cannotConvert("InputStream (unicode)");\r
+ }\r
+\r
+ public InputStream getBinaryStream() throws SQLException {\r
+ throw cannotConvert("InputStream (binary)");\r
+ }\r
+\r
+ public Object getObject() throws SQLException {\r
+ return getter.getObject();\r
+ }\r
+\r
+ public Reader getCharacterStream() throws SQLException {\r
+ throw cannotConvert("Reader");\r
+ }\r
+\r
+ private SQLException cannotConvert(String targetType) throws SQLException {\r
+ return new SQLDataException("cannot convert to " + targetType + " ("\r
+ + this + ")");\r
+ }\r
+\r
+ public Object getObject(Map<String, Class<?>> map) throws SQLException {\r
+ throw cannotConvert("Object (with map)");\r
+ }\r
+\r
+ public Ref getRef() throws SQLException {\r
+ throw cannotConvert("Ref");\r
+ }\r
+\r
+ public Blob getBlob() throws SQLException {\r
+ throw cannotConvert("Blob");\r
+ }\r
+\r
+ public Clob getClob() throws SQLException {\r
+ throw cannotConvert("Clob");\r
+ }\r
+\r
+ public Array getArray() throws SQLException {\r
+ throw cannotConvert("Array");\r
+ }\r
+\r
+ public Struct getStruct() throws SQLException {\r
+ throw cannotConvert("Struct");\r
+ }\r
+\r
+ public Date getDate(Calendar calendar) throws SQLException {\r
+ throw cannotConvert("Date");\r
+ }\r
+\r
+ public Time getTime(Calendar calendar) throws SQLException {\r
+ throw cannotConvert("Time");\r
+ }\r
+\r
+ public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ throw cannotConvert("Timestamp");\r
+ }\r
+\r
+ public URL getURL() throws SQLException {\r
+ throw cannotConvert("URL");\r
+ }\r
+\r
+ public NClob getNClob() throws SQLException {\r
+ throw cannotConvert("NClob");\r
+ }\r
+\r
+ public SQLXML getSQLXML() throws SQLException {\r
+ throw cannotConvert("SQLXML");\r
+ }\r
+\r
+ public String getNString() throws SQLException {\r
+ throw cannotConvert("NString");\r
+ }\r
+\r
+ public Reader getNCharacterStream() throws SQLException {\r
+ throw cannotConvert("NCharacterStream");\r
+ }\r
+\r
+ public <T> T getObject(Class<T> type) throws SQLException {\r
+ throw cannotConvert("Object (with type)");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor of exact numeric values. The subclass must implement the\r
+ * {@link #getLong()} method.\r
+ */\r
+ private abstract static class ExactNumericAccessor extends AccessorImpl {\r
+ private ExactNumericAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public BigDecimal getBigDecimal(int scale) throws SQLException {\r
+ final long v = getLong();\r
+ if (v == 0 && getter.wasNull()) {\r
+ return null;\r
+ }\r
+ return BigDecimal.valueOf(v).setScale(scale, RoundingMode.DOWN);\r
+ }\r
+\r
+ public BigDecimal getBigDecimal() throws SQLException {\r
+ final long val = getLong();\r
+ if (val == 0 && getter.wasNull()) {\r
+ return null;\r
+ }\r
+ return BigDecimal.valueOf(val);\r
+ }\r
+\r
+ public double getDouble() throws SQLException {\r
+ return getLong();\r
+ }\r
+\r
+ public float getFloat() throws SQLException {\r
+ return getLong();\r
+ }\r
+\r
+ public abstract long getLong() throws SQLException;\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Boolean};\r
+ * corresponds to {@link java.sql.Types#BOOLEAN}.\r
+ */\r
+ private static class BooleanAccessor extends ExactNumericAccessor {\r
+ private BooleanAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public boolean getBoolean() throws SQLException {\r
+ Boolean o = (Boolean) getObject();\r
+ return o != null && o;\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ return getBoolean() ? 1 : 0;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Byte};\r
+ * corresponds to {@link java.sql.Types#TINYINT}.\r
+ */\r
+ private static class ByteAccessor extends ExactNumericAccessor {\r
+ private ByteAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public byte getByte() throws SQLException {\r
+ Object obj = getObject();\r
+ if (null == obj) {\r
+ return 0;\r
+ } else if (obj instanceof Integer) {\r
+ return ((Integer) obj).byteValue();\r
+ }\r
+ return (Byte) obj;\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ return getByte();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Short};\r
+ * corresponds to {@link java.sql.Types#SMALLINT}.\r
+ */\r
+ private static class ShortAccessor extends ExactNumericAccessor {\r
+ private ShortAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public short getShort() throws SQLException {\r
+ Object obj = getObject();\r
+ if (null == obj) {\r
+ return 0;\r
+ } else if (obj instanceof Integer) {\r
+ return ((Integer) obj).shortValue();\r
+ }\r
+ return (Short) obj;\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ return getShort();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is an {@link Integer};\r
+ * corresponds to {@link java.sql.Types#INTEGER}.\r
+ */\r
+ private static class IntAccessor extends ExactNumericAccessor {\r
+ private IntAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public int getInt() throws SQLException {\r
+ Integer o = (Integer) super.getObject();\r
+ return o == null ? 0 : o;\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ return getInt();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Long};\r
+ * corresponds to {@link java.sql.Types#BIGINT}.\r
+ */\r
+ private static class LongAccessor extends ExactNumericAccessor {\r
+ private LongAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ Long o = (Long) super.getObject();\r
+ return o == null ? 0 : o;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor of values that are {@link Double} or null.\r
+ */\r
+ private abstract static class ApproximateNumericAccessor\r
+ extends AccessorImpl {\r
+ private ApproximateNumericAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public BigDecimal getBigDecimal(int scale) throws SQLException {\r
+ final double v = getDouble();\r
+ if (v == 0d && getter.wasNull()) {\r
+ return null;\r
+ }\r
+ return BigDecimal.valueOf(v).setScale(scale, RoundingMode.DOWN);\r
+ }\r
+\r
+ public BigDecimal getBigDecimal() throws SQLException {\r
+ final double v = getDouble();\r
+ if (v == 0 && getter.wasNull()) {\r
+ return null;\r
+ }\r
+ return BigDecimal.valueOf(v);\r
+ }\r
+\r
+ public abstract double getDouble() throws SQLException;\r
+\r
+ public long getLong() throws SQLException {\r
+ return (long) getDouble();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Float};\r
+ * corresponds to {@link java.sql.Types#FLOAT}.\r
+ */\r
+ private static class FloatAccessor extends ApproximateNumericAccessor {\r
+ private FloatAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public float getFloat() throws SQLException {\r
+ Float o = (Float) getObject();\r
+ return o == null ? 0f : o;\r
+ }\r
+\r
+ public double getDouble() throws SQLException {\r
+ return getFloat();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Double};\r
+ * corresponds to {@link java.sql.Types#DOUBLE}.\r
+ */\r
+ private static class DoubleAccessor extends ApproximateNumericAccessor {\r
+ private DoubleAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public double getDouble() throws SQLException {\r
+ Object obj = getObject();\r
+ if (null == obj) {\r
+ return 0d;\r
+ } else if (obj instanceof BigDecimal) {\r
+ return ((BigDecimal) obj).doubleValue();\r
+ }\r
+ return (Double) obj;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor of exact numeric values. The subclass must implement the\r
+ * {@link #getLong()} method.\r
+ */\r
+ private abstract static class BigNumberAccessor extends AccessorImpl {\r
+ private BigNumberAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ protected abstract Number getNumber() throws SQLException;\r
+\r
+ public double getDouble() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0d : number.doubleValue();\r
+ }\r
+\r
+ public float getFloat() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0f : number.floatValue();\r
+ }\r
+\r
+ public long getLong() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0L : number.longValue();\r
+ }\r
+\r
+ public int getInt() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0 : number.intValue();\r
+ }\r
+\r
+ public short getShort() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0 : number.shortValue();\r
+ }\r
+\r
+ public byte getByte() throws SQLException {\r
+ Number number = getNumber();\r
+ return number == null ? 0 : number.byteValue();\r
+ }\r
+\r
+ public boolean getBoolean() throws SQLException {\r
+ Number number = getNumber();\r
+ return number != null && number.doubleValue() != 0;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link BigDecimal};\r
+ * corresponds to {@link java.sql.Types#DECIMAL}.\r
+ */\r
+ private static class BigDecimalAccessor extends BigNumberAccessor {\r
+ private BigDecimalAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ protected Number getNumber() throws SQLException {\r
+ return (Number) getObject();\r
+ }\r
+\r
+ public BigDecimal getBigDecimal(int scale) throws SQLException {\r
+ return (BigDecimal) getObject();\r
+ }\r
+\r
+ public BigDecimal getBigDecimal() throws SQLException {\r
+ return (BigDecimal) getObject();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link Number};\r
+ * corresponds to {@link java.sql.Types#NUMERIC}.\r
+ *\r
+ * <p>This is useful when numbers have been translated over JSON. JSON\r
+ * converts a 0L (0 long) value to the string "0" and back to 0 (0 int).\r
+ * So you cannot be sure that the source and target type are the same.\r
+ */\r
+ static class NumberAccessor extends BigNumberAccessor {\r
+ private final int scale;\r
+\r
+ NumberAccessor(Getter getter, int scale) {\r
+ super(getter);\r
+ this.scale = scale;\r
+ }\r
+\r
+ protected Number getNumber() throws SQLException {\r
+ return (Number) super.getObject();\r
+ }\r
+\r
+ public BigDecimal getBigDecimal(int scale) throws SQLException {\r
+ Number n = getNumber();\r
+ if (n == null) {\r
+ return null;\r
+ }\r
+ BigDecimal decimal = AvaticaSite.toBigDecimal(n);\r
+ if (0 != scale) {\r
+ return decimal.setScale(scale, RoundingMode.UNNECESSARY);\r
+ }\r
+ return decimal;\r
+ }\r
+\r
+ public BigDecimal getBigDecimal() throws SQLException {\r
+ return getBigDecimal(scale);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link String};\r
+ * corresponds to {@link java.sql.Types#CHAR}\r
+ * and {@link java.sql.Types#VARCHAR}.\r
+ */\r
+ private static class StringAccessor extends AccessorImpl {\r
+ private StringAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ public String getString() throws SQLException {\r
+ final Object obj = getObject();\r
+ if (obj instanceof String) {\r
+ return (String) obj;\r
+ }\r
+ return null == obj ? null : obj.toString();\r
+ }\r
+\r
+ @Override public byte[] getBytes() throws SQLException {\r
+ return super.getBytes();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link String};\r
+ * corresponds to {@link java.sql.Types#CHAR}.\r
+ */\r
+ private static class FixedStringAccessor extends StringAccessor {\r
+ protected final Spacer spacer;\r
+\r
+ private FixedStringAccessor(Getter getter, int length) {\r
+ super(getter);\r
+ this.spacer = new Spacer(length);\r
+ }\r
+\r
+ public String getString() throws SQLException {\r
+ String s = super.getString();\r
+ if (s == null) {\r
+ return null;\r
+ }\r
+ return spacer.padRight(s);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link String};\r
+ * corresponds to {@link java.sql.Types#CHAR}.\r
+ */\r
+ private static class StringFromCharAccessor extends FixedStringAccessor {\r
+ private StringFromCharAccessor(Getter getter, int length) {\r
+ super(getter, length);\r
+ }\r
+\r
+ public String getString() throws SQLException {\r
+ Character s = (Character) super.getObject();\r
+ if (s == null) {\r
+ return null;\r
+ }\r
+ return spacer.padRight(s.toString());\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is an array of\r
+ * {@link org.apache.calcite.avatica.util.ByteString} values;\r
+ * corresponds to {@link java.sql.Types#BINARY}\r
+ * and {@link java.sql.Types#VARBINARY}.\r
+ */\r
+ private static class BinaryAccessor extends AccessorImpl {\r
+ private BinaryAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ //FIXME: Protobuf gets byte[]\r
+ @Override public byte[] getBytes() throws SQLException {\r
+ Object obj = getObject();\r
+ if (null == obj) {\r
+ return null;\r
+ }\r
+ if (obj instanceof ByteString) {\r
+ return ((ByteString) obj).getBytes();\r
+ } else if (obj instanceof String) {\r
+ // Need to unwind the base64 for JSON\r
+ return ByteString.parseBase64((String) obj);\r
+ } else if (obj instanceof byte[]) {\r
+ // Protobuf would have a byte array\r
+ return (byte[]) obj;\r
+ } else {\r
+ throw new RuntimeException("Cannot handle " + obj.getClass() + " as bytes");\r
+ }\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ Object o = getObject();\r
+ if (null == o) {\r
+ return null;\r
+ }\r
+ if (o instanceof byte[]) {\r
+ return new String((byte[]) o, StandardCharsets.UTF_8);\r
+ } else if (o instanceof ByteString) {\r
+ return ((ByteString) o).toString();\r
+ }\r
+ throw new IllegalStateException("Unhandled value type: " + o.getClass());\r
+ }\r
+ \r
+ \r
+ @Override public Blob getBlob() throws SQLException {\r
+ \r
+ Object o = getObject();\r
+ if (null == o) {\r
+ return null;\r
+ }\r
+ if (o instanceof byte[]) {\r
+ byte[] byteArr = (byte[] )o;\r
+ //System.out.println(new String(byteArr, StandardCharsets.UTF_8));\r
+ return new SerialBlob(byteArr);\r
+ } \r
+ \r
+ throw new IllegalStateException("Unhandled value type: " + o.getClass());\r
+ \r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@link String},\r
+ * encoding {@link java.sql.Types#BINARY}\r
+ * and {@link java.sql.Types#VARBINARY} values in Base64 format.\r
+ */\r
+ private static class BinaryFromStringAccessor extends StringAccessor {\r
+ private BinaryFromStringAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ return super.getObject();\r
+ }\r
+\r
+ @Override public byte[] getBytes() throws SQLException {\r
+ // JSON sends this as a base64-enc string, protobuf can do binary.\r
+ Object obj = getObject();\r
+\r
+ if (obj instanceof byte[]) {\r
+ // If we already have bytes, just send them back.\r
+ return (byte[]) obj;\r
+ }\r
+\r
+ return getBase64Decoded();\r
+ }\r
+\r
+ private byte[] getBase64Decoded() throws SQLException {\r
+ final String string = super.getString();\r
+ if (null == string) {\r
+ return null;\r
+ }\r
+ // Need to base64 decode the string.\r
+ return ByteString.parseBase64(string);\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final byte[] bytes = getBase64Decoded();\r
+ if (null == bytes) {\r
+ return null;\r
+ }\r
+ // Need to base64 decode the string.\r
+ return new String(bytes, StandardCharsets.UTF_8);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a DATE,\r
+ * in its default representation {@code int};\r
+ * corresponds to {@link java.sql.Types#DATE}.\r
+ */\r
+ private static class DateFromNumberAccessor extends NumberAccessor {\r
+ private final Calendar localCalendar;\r
+\r
+ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) {\r
+ super(getter, 0);\r
+ this.localCalendar = localCalendar;\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ return getDate(localCalendar);\r
+ }\r
+\r
+ @Override public Date getDate(Calendar calendar) throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return longToDate(v.longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar);\r
+ }\r
+\r
+ @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return longToTimestamp(v.longValue() * DateTimeUtils.MILLIS_PER_DAY,\r
+ calendar);\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return dateAsString(v.intValue(), null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a Time,\r
+ * in its default representation {@code int};\r
+ * corresponds to {@link java.sql.Types#TIME}.\r
+ */\r
+ private static class TimeFromNumberAccessor extends NumberAccessor {\r
+ private final Calendar localCalendar;\r
+\r
+ private TimeFromNumberAccessor(Getter getter, Calendar localCalendar) {\r
+ super(getter, 0);\r
+ this.localCalendar = localCalendar;\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ return getTime(localCalendar);\r
+ }\r
+\r
+ @Override public Time getTime(Calendar calendar) throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return intToTime(v.intValue(), calendar);\r
+ }\r
+\r
+ @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return longToTimestamp(v.longValue(), calendar);\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return timeAsString(v.intValue(), null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a TIMESTAMP,\r
+ * in its default representation {@code long};\r
+ * corresponds to {@link java.sql.Types#TIMESTAMP}.\r
+ */\r
+ private static class TimestampFromNumberAccessor extends NumberAccessor {\r
+ private final Calendar localCalendar;\r
+\r
+ private TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) {\r
+ super(getter, 0);\r
+ this.localCalendar = localCalendar;\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ return getTimestamp(localCalendar);\r
+ }\r
+\r
+ @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return longToTimestamp(v.longValue(), calendar);\r
+ }\r
+\r
+ @Override public Date getDate(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Date(timestamp.getTime());\r
+ }\r
+\r
+ @Override public Time getTime(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Time(\r
+ DateTimeUtils.floorMod(timestamp.getTime(),\r
+ DateTimeUtils.MILLIS_PER_DAY));\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final Number v = getNumber();\r
+ if (v == null) {\r
+ return null;\r
+ }\r
+ return timestampAsString(v.longValue(), null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a DATE,\r
+ * represented as a java.sql.Date;\r
+ * corresponds to {@link java.sql.Types#DATE}.\r
+ */\r
+ private static class DateAccessor extends ObjectAccessor {\r
+ private DateAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ @Override public Date getDate(Calendar calendar) throws SQLException {\r
+ java.sql.Date date = (Date) getObject();\r
+ if (date == null) {\r
+ return null;\r
+ }\r
+ if (calendar != null) {\r
+ long v = date.getTime();\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ date = new Date(v);\r
+ }\r
+ return date;\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final int v = getInt();\r
+ if (v == 0 && wasNull()) {\r
+ return null;\r
+ }\r
+ return dateAsString(v, null);\r
+ }\r
+\r
+ @Override public long getLong() throws SQLException {\r
+ Date date = getDate(null);\r
+ return date == null\r
+ ? 0L\r
+ : (date.getTime() / DateTimeUtils.MILLIS_PER_DAY);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a TIME,\r
+ * represented as a java.sql.Time;\r
+ * corresponds to {@link java.sql.Types#TIME}.\r
+ */\r
+ private static class TimeAccessor extends ObjectAccessor {\r
+ private TimeAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ @Override public Time getTime(Calendar calendar) throws SQLException {\r
+ Time date = (Time) getObject();\r
+ if (date == null) {\r
+ return null;\r
+ }\r
+ if (calendar != null) {\r
+ long v = date.getTime();\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ date = new Time(v);\r
+ }\r
+ return date;\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final int v = getInt();\r
+ if (v == 0 && wasNull()) {\r
+ return null;\r
+ }\r
+ return timeAsString(v, null);\r
+ }\r
+\r
+ @Override public long getLong() throws SQLException {\r
+ Time time = getTime(null);\r
+ return time == null ? 0L\r
+ : (time.getTime() % DateTimeUtils.MILLIS_PER_DAY);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a TIMESTAMP,\r
+ * represented as a java.sql.Timestamp;\r
+ * corresponds to {@link java.sql.Types#TIMESTAMP}.\r
+ */\r
+ private static class TimestampAccessor extends ObjectAccessor {\r
+ private TimestampAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+\r
+ @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ Timestamp timestamp = (Timestamp) getObject();\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ if (calendar != null) {\r
+ long v = timestamp.getTime();\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ timestamp = new Timestamp(v);\r
+ }\r
+ return timestamp;\r
+ }\r
+\r
+ @Override public Date getDate(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Date(timestamp.getTime());\r
+ }\r
+\r
+ @Override public Time getTime(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Time(\r
+ DateTimeUtils.floorMod(timestamp.getTime(),\r
+ DateTimeUtils.MILLIS_PER_DAY));\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final long v = getLong();\r
+ if (v == 0 && wasNull()) {\r
+ return null;\r
+ }\r
+ return timestampAsString(v, null);\r
+ }\r
+\r
+ @Override public long getLong() throws SQLException {\r
+ Timestamp timestamp = getTimestamp(null);\r
+ return timestamp == null ? 0 : timestamp.getTime();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a TIMESTAMP,\r
+ * represented as a java.util.Date;\r
+ * corresponds to {@link java.sql.Types#TIMESTAMP}.\r
+ */\r
+ private static class TimestampFromUtilDateAccessor extends ObjectAccessor {\r
+ private final Calendar localCalendar;\r
+\r
+ private TimestampFromUtilDateAccessor(Getter getter,\r
+ Calendar localCalendar) {\r
+ super(getter);\r
+ this.localCalendar = localCalendar;\r
+ }\r
+\r
+ @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException {\r
+ java.util.Date date = (java.util.Date) getObject();\r
+ if (date == null) {\r
+ return null;\r
+ }\r
+ long v = date.getTime();\r
+ if (calendar != null) {\r
+ v -= calendar.getTimeZone().getOffset(v);\r
+ }\r
+ return new Timestamp(v);\r
+ }\r
+\r
+ @Override public Date getDate(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Date(timestamp.getTime());\r
+ }\r
+\r
+ @Override public Time getTime(Calendar calendar) throws SQLException {\r
+ final Timestamp timestamp = getTimestamp(calendar);\r
+ if (timestamp == null) {\r
+ return null;\r
+ }\r
+ return new Time(\r
+ DateTimeUtils.floorMod(timestamp.getTime(),\r
+ DateTimeUtils.MILLIS_PER_DAY));\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ java.util.Date date = (java.util.Date) getObject();\r
+ if (date == null) {\r
+ return null;\r
+ }\r
+ return timestampAsString(date.getTime(), null);\r
+ }\r
+\r
+ @Override public long getLong() throws SQLException {\r
+ Timestamp timestamp = getTimestamp(localCalendar);\r
+ return timestamp == null ? 0 : timestamp.getTime();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@code int};\r
+ * corresponds to {@link java.sql.Types#OTHER}.\r
+ */\r
+ private static class IntervalYearMonthAccessor extends IntAccessor {\r
+ private final TimeUnitRange range;\r
+\r
+ private IntervalYearMonthAccessor(Getter getter, TimeUnitRange range) {\r
+ super(getter);\r
+ this.range = range;\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final int v = getInt();\r
+ if (v == 0 && wasNull()) {\r
+ return null;\r
+ }\r
+ return DateTimeUtils.intervalYearMonthToString(v, range);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a {@code long};\r
+ * corresponds to {@link java.sql.Types#OTHER}.\r
+ */\r
+ private static class IntervalDayTimeAccessor extends LongAccessor {\r
+ private final TimeUnitRange range;\r
+ private final int scale;\r
+\r
+ private IntervalDayTimeAccessor(Getter getter, TimeUnitRange range,\r
+ int scale) {\r
+ super(getter);\r
+ this.range = range;\r
+ this.scale = scale;\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final long v = getLong();\r
+ if (v == 0 && wasNull()) {\r
+ return null;\r
+ }\r
+ return DateTimeUtils.intervalDayTimeToString(v, range, scale);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is an ARRAY;\r
+ * corresponds to {@link java.sql.Types#ARRAY}.\r
+ */\r
+ public static class ArrayAccessor extends AccessorImpl {\r
+ final ColumnMetaData.AvaticaType componentType;\r
+ final Accessor componentAccessor;\r
+ final SlotGetter componentSlotGetter;\r
+ final ArrayImpl.Factory factory;\r
+\r
+ public ArrayAccessor(Getter getter,\r
+ ColumnMetaData.AvaticaType componentType, Accessor componentAccessor,\r
+ SlotGetter componentSlotGetter, ArrayImpl.Factory factory) {\r
+ super(getter);\r
+ this.componentType = componentType;\r
+ this.componentAccessor = componentAccessor;\r
+ this.componentSlotGetter = componentSlotGetter;\r
+ this.factory = factory;\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ final Object object = super.getObject();\r
+ if (object == null || object instanceof ArrayImpl) {\r
+ return object;\r
+ } else if (object instanceof List) {\r
+ List<?> list = (List<?>) object;\r
+ // Run the array values through the component accessor\r
+ List<Object> convertedValues = new ArrayList<>(list.size());\r
+ for (Object val : list) {\r
+ if (null == val) {\r
+ convertedValues.add(null);\r
+ } else {\r
+ // Set the current value in the SlotGetter so we can use the Accessor to coerce it.\r
+ componentSlotGetter.slot = val;\r
+ convertedValues.add(convertValue());\r
+ }\r
+ }\r
+ return convertedValues;\r
+ }\r
+ // The object can be java array in case of user-provided class for row storage.\r
+ return AvaticaUtils.primitiveList(object);\r
+ }\r
+\r
+ private Object convertValue() throws SQLException {\r
+ switch (componentType.id) {\r
+ case Types.BOOLEAN:\r
+ case Types.BIT:\r
+ return componentAccessor.getBoolean();\r
+ case Types.TINYINT:\r
+ return componentAccessor.getByte();\r
+ case Types.SMALLINT:\r
+ return componentAccessor.getShort();\r
+ case Types.INTEGER:\r
+ return componentAccessor.getInt();\r
+ case Types.BIGINT:\r
+ return componentAccessor.getLong();\r
+ case Types.FLOAT:\r
+ return componentAccessor.getFloat();\r
+ case Types.DOUBLE:\r
+ return componentAccessor.getDouble();\r
+ case Types.ARRAY:\r
+ return componentAccessor.getArray();\r
+ case Types.CHAR:\r
+ case Types.VARCHAR:\r
+ case Types.LONGVARCHAR:\r
+ case Types.NCHAR:\r
+ case Types.LONGNVARCHAR:\r
+ return componentAccessor.getString();\r
+ case Types.BINARY:\r
+ case Types.VARBINARY:\r
+ case Types.LONGVARBINARY:\r
+ return componentAccessor.getBytes();\r
+ case Types.DECIMAL:\r
+ return componentAccessor.getBigDecimal();\r
+ case Types.DATE:\r
+ case Types.TIME:\r
+ case Types.TIMESTAMP:\r
+ case Types.STRUCT:\r
+ case Types.JAVA_OBJECT:\r
+ return componentAccessor.getObject();\r
+ default:\r
+ throw new IllegalStateException("Unhandled ARRAY component type: " + componentType.rep\r
+ + ", id: " + componentType.id);\r
+ }\r
+ }\r
+\r
+ @SuppressWarnings("unchecked") @Override public Array getArray() throws SQLException {\r
+ final Object o = getObject();\r
+ if (o == null) {\r
+ return null;\r
+ }\r
+ if (o instanceof ArrayImpl) {\r
+ return (ArrayImpl) o;\r
+ }\r
+ // If it's not an Array already, assume it is a List.\r
+ return new ArrayImpl((List<Object>) o, this);\r
+ }\r
+\r
+ @Override public String getString() throws SQLException {\r
+ final Array array = getArray();\r
+ return array == null ? null : array.toString();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is a STRUCT;\r
+ * corresponds to {@link java.sql.Types#STRUCT}.\r
+ */\r
+ private static class StructAccessor extends AccessorImpl {\r
+ private final List<Accessor> fieldAccessors;\r
+\r
+ private StructAccessor(Getter getter, List<Accessor> fieldAccessors) {\r
+ super(getter);\r
+ this.fieldAccessors = fieldAccessors;\r
+ }\r
+\r
+ @Override public Object getObject() throws SQLException {\r
+ return getStruct();\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override public <T> T getObject(Class<T> clz) throws SQLException {\r
+ // getStruct() is not exposed on Accessor, only AccessorImpl. getObject(Class) is exposed,\r
+ // so we can make it do the right thing (call getStruct()).\r
+ if (clz.equals(Struct.class)) {\r
+ return (T) getStruct();\r
+ }\r
+ return super.getObject(clz);\r
+ }\r
+\r
+ @Override public Struct getStruct() throws SQLException {\r
+ final Object o = super.getObject();\r
+ if (o == null) {\r
+ return null;\r
+ } else if (o instanceof StructImpl) {\r
+ return (StructImpl) o;\r
+ } else if (o instanceof List) {\r
+ return new StructImpl((List) o);\r
+ } else {\r
+ final List<Object> list = new ArrayList<>();\r
+ for (Accessor fieldAccessor : fieldAccessors) {\r
+ list.add(fieldAccessor.getObject());\r
+ }\r
+ return new StructImpl(list);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Accessor that assumes that the underlying value is an OBJECT;\r
+ * corresponds to {@link java.sql.Types#JAVA_OBJECT}.\r
+ */\r
+ private static class ObjectAccessor extends AccessorImpl {\r
+ private ObjectAccessor(Getter getter) {\r
+ super(getter);\r
+ }\r
+ }\r
+\r
+ /** Gets a value from a particular field of the current record of this\r
+ * cursor. */\r
+ protected interface Getter {\r
+ Object getObject() throws SQLException;\r
+\r
+ boolean wasNull() throws SQLException;\r
+ }\r
+\r
+ /** Abstract implementation of {@link Getter}. */\r
+ protected abstract class AbstractGetter implements Getter {\r
+ public boolean wasNull() throws SQLException {\r
+ return wasNull[0];\r
+ }\r
+ }\r
+\r
+ /** Implementation of {@link Getter} that returns the current contents of\r
+ * a mutable slot. */\r
+ public class SlotGetter implements Getter {\r
+ public Object slot;\r
+\r
+ public Object getObject() throws SQLException {\r
+ return slot;\r
+ }\r
+\r
+ public boolean wasNull() throws SQLException {\r
+ return slot == null;\r
+ }\r
+ }\r
+\r
+ /** Implementation of {@link Getter} that returns the value of a given field\r
+ * of the current contents of another getter. */\r
+ public class StructGetter implements Getter {\r
+ public final Getter getter;\r
+ private final ColumnMetaData columnMetaData;\r
+\r
+ public StructGetter(Getter getter, ColumnMetaData columnMetaData) {\r
+ this.getter = getter;\r
+ this.columnMetaData = columnMetaData;\r
+ }\r
+\r
+ public Object getObject() throws SQLException {\r
+ try {\r
+ final Object o = getter.getObject();\r
+ if (o instanceof Object[]) {\r
+ Object[] objects = (Object[]) o;\r
+ return objects[columnMetaData.ordinal];\r
+ }\r
+ final Field field = o.getClass().getField(columnMetaData.label);\r
+ return field.get(getter.getObject());\r
+ } catch (IllegalAccessException | NoSuchFieldException e) {\r
+ throw new SQLException(e);\r
+ }\r
+ }\r
+\r
+ public boolean wasNull() throws SQLException {\r
+ return getObject() == null;\r
+ }\r
+ }\r
+}\r
+\r
+// End AbstractCursor.java\r
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
package org.onap.music.logging;
+import static com.att.eelf.configuration.Configuration.MDC_ALERT_SEVERITY;
+import static com.att.eelf.configuration.Configuration.MDC_INSTANCE_UUID;
+import static com.att.eelf.configuration.Configuration.MDC_KEY_REQUEST_ID;
import static com.att.eelf.configuration.Configuration.MDC_SERVER_FQDN;
import static com.att.eelf.configuration.Configuration.MDC_SERVER_IP_ADDRESS;
import static com.att.eelf.configuration.Configuration.MDC_SERVICE_INSTANCE_ID;
import static com.att.eelf.configuration.Configuration.MDC_SERVICE_NAME;
-
+import java.io.IOException;
+import java.io.InputStream;
import java.net.InetAddress;
import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-
import javax.servlet.http.HttpServletRequest;
-
import org.slf4j.MDC;
-
import com.att.eelf.configuration.EELFLogger;
import com.att.eelf.configuration.EELFManager;
import com.att.eelf.configuration.SLF4jWrapper;
public class EELFLoggerDelegate extends SLF4jWrapper implements EELFLogger {
+
public static final EELFLogger errorLogger = EELFManager.getInstance().getErrorLogger();
public static final EELFLogger applicationLogger = EELFManager.getInstance().getApplicationLogger();
public static final EELFLogger auditLogger = EELFManager.getInstance().getAuditLogger();
public static final EELFLogger metricsLogger = EELFManager.getInstance().getMetricsLogger();
public static final EELFLogger debugLogger = EELFManager.getInstance().getDebugLogger();
+ // DateTime Format according to the ECOMP Application Logging Guidelines.
+ private static final SimpleDateFormat ecompLogDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
private String className;
- private static ConcurrentMap<String, EELFLoggerDelegate> classMap = new ConcurrentHashMap<>();
+ private static ConcurrentMap<String, EELFLoggerDelegate> classMap = new ConcurrentHashMap<String, EELFLoggerDelegate>();
public EELFLoggerDelegate(final String className) {
super(className);
*/
public void trace(EELFLogger logger, String msg) {
if (logger.isTraceEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.trace(msg);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
*/
public void trace(EELFLogger logger, String msg, Object... arguments) {
if (logger.isTraceEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.trace(msg, arguments);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
*/
public void trace(EELFLogger logger, String msg, Throwable th) {
if (logger.isTraceEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.trace(msg, th);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
*/
public void debug(EELFLogger logger, String msg) {
if (logger.isDebugEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.debug(msg);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
*/
public void debug(EELFLogger logger, String msg, Object... arguments) {
if (logger.isDebugEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.debug(msg, arguments);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
*/
public void debug(EELFLogger logger, String msg, Throwable th) {
if (logger.isDebugEnabled()) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.debug(msg, th);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
}
* @param msg
*/
public void info(EELFLogger logger, String msg) {
- logger.info(className + " - "+msg);
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
+ logger.info(msg);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param arguments
*/
public void info(EELFLogger logger, String msg, Object... arguments) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.info(msg, arguments);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param th
*/
public void info(EELFLogger logger, String msg, Throwable th) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.info(msg, th);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param msg
*/
public void warn(EELFLogger logger, String msg) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.warn(msg);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param arguments
*/
public void warn(EELFLogger logger, String msg, Object... arguments) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.warn(msg, arguments);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param th
*/
public void warn(EELFLogger logger, String msg, Throwable th) {
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
logger.warn(msg, th);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param msg
*/
public void error(EELFLogger logger, String msg) {
- logger.error(className+ " - " + msg);
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
+ logger.error(msg);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param arguments
*/
public void error(EELFLogger logger, String msg, Object... arguments) {
- logger.error(msg, arguments);
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
+ logger.warn(msg, arguments);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
/**
* @param th
*/
public void error(EELFLogger logger, String msg, Throwable th) {
- logger.error(msg, th);
+ MDC.put(LoggerProperties.MDC_CLASS_NAME, className);
+ logger.warn(msg, th);
+ MDC.remove(LoggerProperties.MDC_CLASS_NAME);
}
- /**
- * Logs a message with the associated alarm severity at error level.
- *
- * @param logger
- * @param msg
- * @param severtiy
- */
- public void error(EELFLogger logger, String msg, Object /*AlarmSeverityEnum*/ severtiy) {
- logger.error(msg);
- }
/**
* Initializes the logger context.
info(applicationLogger, msg);
error(errorLogger, msg);
debug(debugLogger, msg);
- info(auditLogger, msg);
+ // Audit and metrics logger must be told start AND stop times
+ final String currentDateTime = getCurrentDateTimeUTC();
+ // Set the MDC with audit properties
+ MDC.put(LoggerProperties.AUDITLOG_BEGIN_TIMESTAMP, currentDateTime);
+ MDC.put(LoggerProperties.AUDITLOG_END_TIMESTAMP, currentDateTime);
+ info(auditLogger, msg);
+ MDC.remove(LoggerProperties.AUDITLOG_BEGIN_TIMESTAMP);
+ MDC.remove(LoggerProperties.AUDITLOG_END_TIMESTAMP);
+ // Set the MDC with metrics properties
+ MDC.put(LoggerProperties.METRICSLOG_BEGIN_TIMESTAMP, currentDateTime);
+ MDC.put(LoggerProperties.METRICSLOG_END_TIMESTAMP, currentDateTime);
info(metricsLogger, msg);
+ MDC.remove(LoggerProperties.METRICSLOG_BEGIN_TIMESTAMP);
+ MDC.remove(LoggerProperties.METRICSLOG_END_TIMESTAMP);
+ }
+
+
+ public static String getCurrentDateTimeUTC() {
+ String currentDateTime = ecompLogDateFormat.format(new Date());
+ return currentDateTime;
}
+
/**
* Builds a message using a template string and the arguments.
*
* @param args
* @return
*/
- @SuppressWarnings("unused")
private String formatMessage(String message, Object... args) {
StringBuilder sbFormattedMessage = new StringBuilder();
if (args != null && args.length > 0 && message != null && message != "") {
*/
private void setGlobalLoggingContext() {
MDC.put(MDC_SERVICE_INSTANCE_ID, "");
+ MDC.put(MDC_ALERT_SEVERITY, AlarmSeverityEnum.INFORMATIONAL.toString());
try {
MDC.put(MDC_SERVER_FQDN, InetAddress.getLocalHost().getHostName());
MDC.put(MDC_SERVER_IP_ADDRESS, InetAddress.getLocalHost().getHostAddress());
+ MDC.put(MDC_INSTANCE_UUID, LoggerProperties.getProperty(LoggerProperties.INSTANCE_UUID));
} catch (Exception e) {
errorLogger.error("setGlobalLoggingContext failed", e);
}
* @param req
* @param appName
*/
- public void setRequestBasedDefaultsIntoGlobalLoggingContext(HttpServletRequest req, String appName) {
+ public void setRequestBasedDefaultsIntoGlobalLoggingContext(HttpServletRequest req, String appName,String reqId,String loginId) {// Load the default fields
// Load the default fields
- setGlobalLoggingContext();
+ setGlobalLoggingContext();
+
+ // Load the request based fields
+ if (req != null) {
+ // Load the Request into MDC context.
+
+ MDC.put(MDC_KEY_REQUEST_ID, reqId);
+
+ // Load user agent into MDC context, if available.
+ String accessingClient = req.getHeader(LoggerProperties.USERAGENT_NAME);
+ if (accessingClient != null && !"".equals(accessingClient) && (accessingClient.contains("Mozilla")
+ || accessingClient.contains("Chrome") || accessingClient.contains("Safari"))) {
+ accessingClient = appName + "_FE";
+ }
+ MDC.put(LoggerProperties.PARTNER_NAME, accessingClient);
+
+ // Protocol, Rest URL & Rest Path
+ MDC.put(LoggerProperties.FULL_URL, LoggerProperties.UNKNOWN);
+ MDC.put(LoggerProperties.PROTOCOL, LoggerProperties.HTTP);
+ String restURL = getFullURL(req);
+ if (restURL != null && restURL != "") {
+ MDC.put(LoggerProperties.FULL_URL, restURL);
+ if (restURL.toLowerCase().contains("https")) {
+ MDC.put(LoggerProperties.PROTOCOL, LoggerProperties.HTTPS);
+ }
+ }
+
+ // Rest Path
+ MDC.put(MDC_SERVICE_NAME, req.getServletPath());
+
+ // Client IPAddress i.e. IPAddress of the remote host who is making
+ // this request.
+ String clientIPAddress = req.getHeader("X-FORWARDED-FOR");
+ if (clientIPAddress == null) {
+ clientIPAddress = req.getRemoteAddr();
+ }
+ MDC.put(LoggerProperties.CLIENT_IP_ADDRESS, clientIPAddress);
+
+ // Load loginId into MDC context.
+ MDC.put(LoggerProperties.MDC_LOGIN_ID, "Unknown");
+
+
+
+ if (loginId != null && loginId != "") {
+ MDC.put(LoggerProperties.MDC_LOGIN_ID, loginId);
+ }
+ }
+ }
+
+
+
+
+ public static String getFullURL(HttpServletRequest request) {
+ if (request != null) {
+ StringBuffer requestURL = request.getRequestURL();
+ String queryString = request.getQueryString();
+
+ if (queryString == null) {
+ return requestURL.toString();
+ } else {
+ return requestURL.append('?').append(queryString).toString();
+ }
+ }
+ return "";
+ }
+
+
- // Load the request based fields
- if (req != null) {
+}
+enum AlarmSeverityEnum {
+ CRITICAL("1"),
+ MAJOR("2"),
+ MINOR("3"),
+ INFORMATIONAL("4"),
+ NONE("0");
- // Rest Path
- MDC.put(MDC_SERVICE_NAME, req.getServletPath());
+ private final String severity;
- // Client IPAddress i.e. IPAddress of the remote host who is making
- // this request.
- String clientIPAddress = req.getHeader("X-FORWARDED-FOR");
- if (clientIPAddress == null) {
- clientIPAddress = req.getRemoteAddr();
- }
- }
- }
+ AlarmSeverityEnum(String severity) {
+ this.severity = severity;
+ }
+
+ public String severity() {
+ return severity;
+ }
+}
+
+class LoggerProperties {
+
+
+public static final String MDC_APPNAME = "AppName";
+public static final String MDC_REST_PATH = "RestPath";
+public static final String MDC_REST_METHOD = "RestMethod";
+public static final String INSTANCE_UUID = "instance_uuid";
+public static final String MDC_CLASS_NAME = "class";
+public static final String MDC_LOGIN_ID = "LoginId";
+public static final String MDC_TIMER = "Timer";
+public static final String PARTNER_NAME = "PartnerName";
+public static final String FULL_URL = "Full-URL";
+public static final String AUDITLOG_BEGIN_TIMESTAMP = "AuditLogBeginTimestamp";
+public static final String AUDITLOG_END_TIMESTAMP = "AuditLogEndTimestamp";
+public static final String METRICSLOG_BEGIN_TIMESTAMP = "MetricsLogBeginTimestamp";
+public static final String METRICSLOG_END_TIMESTAMP = "MetricsLogEndTimestamp";
+public static final String CLIENT_IP_ADDRESS = "ClientIPAddress";
+public static final String STATUS_CODE = "StatusCode";
+public static final String RESPONSE_CODE = "ResponseCode";
+
+public static final String HTTP = "HTTP";
+public static final String HTTPS = "HTTPS";
+public static final String UNKNOWN = "Unknown";
+public static final String PROTOCOL = "PROTOCOL";
+public static final String USERAGENT_NAME = "user-agent";
+public static final String USER_ATTRIBUTE_NAME = "user_attribute_name";
+
+
+private LoggerProperties(){}
+
+private static Properties properties;
+
+private static String propertyFileName = "logger.properties";
+
+private static final Object lockObject = new Object();
+
+//private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(LoggerProperties.class);
+
+/**
+ * Gets the property value for the specified key. If a value is found, leading
+ * and trailing space is trimmed.
+ *
+ * @param property
+ * Property key
+ * @return Value for the named property; null if the property file was not
+ * loaded or the key was not found.
+ */
+public static String getProperty(String property) {
+ if (properties == null) {
+ synchronized (lockObject) {
+ try {
+ if (!initialize()) {
+// logger.error(EELFLoggerDelegate.errorLogger, "Failed to read property file " + propertyFileName);
+ return null;
+ }
+ } catch (IOException e) {
+// logger.error(EELFLoggerDelegate.errorLogger, "Failed to read property file " + propertyFileName ,e);
+ return null;
+ }
+ }
+ }
+ String value = properties.getProperty(property);
+ if (value != null)
+ value = value.trim();
+ return value;
+}
+
+/**
+ * Reads properties from a portal.properties file on the classpath.
+ *
+ * Clients do NOT need to call this method. Clients MAY call this method to test
+ * whether the properties file can be loaded successfully.
+ *
+ * @return True if properties were successfully loaded, else false.
+ * @throws IOException
+ * On failure
+ */
+private static boolean initialize() throws IOException {
+ if (properties != null)
+ return true;
+ InputStream in = LoggerProperties.class.getClassLoader().getResourceAsStream(propertyFileName);
+ if (in == null)
+ return false;
+ properties = new Properties();
+ try {
+ properties.load(in);
+ } finally {
+ in.close();
+ }
+ return true;
}
+}
\ No newline at end of file