/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.client; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.ObjectStreamConstants; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Array; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.teiid.core.TeiidRuntimeException; import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.BinaryType; import org.teiid.core.types.BlobType; import org.teiid.core.types.ClobType; import org.teiid.core.types.DataTypeManager; import org.teiid.core.types.GeometryType; import org.teiid.core.types.XMLType; import org.teiid.jdbc.JDBCPlugin; /** * @since 4.2 * * <ul> * <li>version 0: starts with 7.1 and uses simple serialization too broadly * <li>version 1: starts with 8.0 uses better string, blob, clob, xml, etc. * add varbinary support. * however was possibly silently truncating date/time values that were * outside of jdbc allowed values * <li>version 2: starts with 8.2 and adds better array serialization and * uses a safer date/time serialization * <li>version 3: starts with 8.6 and adds better repeated string performance * <li>version 4: starts with 8.10 and adds the geometry type * </ul> */ public class BatchSerializer { public static final byte VERSION_GEOMETRY = (byte)4; static final byte CURRENT_VERSION = VERSION_GEOMETRY; private BatchSerializer() {} // Uninstantiable private static ColumnSerializer defaultSerializer = new ColumnSerializer(); private static final Map<String, ColumnSerializer[]> serializers = new HashMap<String, ColumnSerializer[]>(128); static { serializers.put(DataTypeManager.DefaultDataTypes.BIG_DECIMAL, new ColumnSerializer[] {new BigDecimalColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.BIG_INTEGER, new ColumnSerializer[] {new BigIntegerColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.BOOLEAN, new ColumnSerializer[] {new BooleanColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.BYTE, new ColumnSerializer[] {new ByteColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.CHAR, new ColumnSerializer[] {new CharColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.DATE, new ColumnSerializer[] {new DateColumnSerializer(), new DateColumnSerializer1(), new DateColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.DOUBLE, new ColumnSerializer[] {new DoubleColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.FLOAT, new ColumnSerializer[] {new FloatColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.INTEGER, new ColumnSerializer[] {new IntColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.LONG, new ColumnSerializer[] {new LongColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.SHORT, new ColumnSerializer[] {new ShortColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.TIME, new ColumnSerializer[] {new TimeColumnSerializer(), new TimeColumnSerializer1(), new TimeColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.TIMESTAMP, new ColumnSerializer[] {new TimestampColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.STRING, new ColumnSerializer[] {defaultSerializer, new StringColumnSerializer1(), new StringColumnSerializer1(), new StringColumnSerializer3()}); serializers.put(DataTypeManager.DefaultDataTypes.CLOB, new ColumnSerializer[] {defaultSerializer, new ClobColumnSerializer1()}); serializers.put(DataTypeManager.DefaultDataTypes.BLOB, new ColumnSerializer[] {defaultSerializer, new BlobColumnSerializer1()}); serializers.put(DataTypeManager.DefaultDataTypes.GEOMETRY, new ColumnSerializer[] {defaultSerializer, new GeometryColumnSerializer()}); serializers.put(DataTypeManager.DefaultDataTypes.XML, new ColumnSerializer[] {defaultSerializer, new XmlColumnSerializer1()}); serializers.put(DataTypeManager.DefaultDataTypes.NULL, new ColumnSerializer[] {defaultSerializer, new NullColumnSerializer1()}); serializers.put(DataTypeManager.DefaultDataTypes.OBJECT, new ColumnSerializer[] {defaultSerializer, new ObjectColumnSerializer((byte)1)}); serializers.put(DataTypeManager.DefaultDataTypes.VARBINARY, new ColumnSerializer[] {new BinaryColumnSerializer(), new BinaryColumnSerializer1()}); } private static ColumnSerializer arrayColumnSerializer = new ColumnSerializer() { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { try { super.writeObject(out, ((java.sql.Array)obj).getArray(), cache, version); } catch (SQLException e) { throw new IOException(e); } } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { return new ArrayImpl((Object[]) in.readObject()); } }; private static final ColumnSerializer arrayColumnSerialier2 = new ArrayColumnSerializer2(new ObjectColumnSerializer((byte)2)); private static final class ArrayColumnSerializer2 extends ColumnSerializer { ObjectColumnSerializer ser; public ArrayColumnSerializer2(ObjectColumnSerializer ser) { this.ser = ser; } @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { Object[] values = null; try { values = (Object[]) ((Array)obj).getArray(); } catch (SQLException e) { out.writeInt(-1); return; } out.writeInt(values.length); int code = DataTypeManager.getTypeCode(values.getClass().getComponentType()); if (code == DataTypeManager.DefaultTypeCodes.GEOMETRY && version < VERSION_GEOMETRY) { code = DataTypeManager.DefaultTypeCodes.BLOB; } out.writeByte((byte)code); for (int i = 0; i < values.length;) { writeIsNullData(out, i, values); int end = Math.min(values.length, i+8); for (; i < end; i++) { if (values[i] != null) { this.ser.writeObject(out, values[i], code, cache, version); } } } out.writeBoolean((obj instanceof ArrayImpl && ((ArrayImpl)obj).isZeroBased())); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { int length = in.readInt(); if (length == -1) { return new ArrayImpl(null); } int code = in.readByte(); Object[] vals = (Object[])java.lang.reflect.Array.newInstance(DataTypeManager.getClass(code), length); for (int i = 0; i < length;) { byte b = in.readByte(); int end = Math.min(length, i+8); for (; i < end; i++) { if (!isNullObject(i, b)) { vals[i] = this.ser.readObject(in, cache, code, version); } } } ArrayImpl result = new ArrayImpl(vals); result.setZeroBased(in.readBoolean()); return result; } @Override public boolean usesCache(byte version) { return version >= 3; } } static class BinaryColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { byte[] bytes = ((BinaryType)obj).getBytes(); out.writeInt(bytes.length); //in theory this could be a short, but we're not strictly enforcing the length out.write(bytes); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { int length = in.readInt(); byte[] bytes = new byte[length]; in.readFully(bytes); return new BinaryType(bytes); } } static class BinaryColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { //uses object serialization for compatibility with legacy clients super.writeObject(out, ((BinaryType)obj).getBytesDirect(), cache, version); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { //won't actually be used byte[] bytes = (byte[])super.readObject(in, cache, version); return new BinaryType(bytes); } } public static final class ObjectColumnSerializer extends ColumnSerializer { byte defaultVersion; public ObjectColumnSerializer(byte version) { this.defaultVersion = version; } @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { int code = DataTypeManager.getTypeCode(obj.getClass()); if (code == DataTypeManager.DefaultTypeCodes.GEOMETRY && version < VERSION_GEOMETRY) { code = DataTypeManager.DefaultTypeCodes.BLOB; } out.writeByte((byte)code); writeObject(out, obj, code, cache, version<VERSION_GEOMETRY?this.defaultVersion:version); } protected void writeObject(ObjectOutput out, Object obj, int code, Map<Object, Integer> cache, byte effectiveVersion) throws IOException { if (code == DataTypeManager.DefaultTypeCodes.BOOLEAN) { if (Boolean.TRUE.equals(obj)) { out.write((byte)1); } else { out.write((byte)0); } } else if (code == DataTypeManager.DefaultTypeCodes.OBJECT) { super.writeObject(out, obj, cache, effectiveVersion); } else { String name = DataTypeManager.getDataTypeName(obj.getClass()); ColumnSerializer s = getSerializer(name, effectiveVersion); s.writeObject(out, obj, cache, effectiveVersion); } } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { int code = in.readByte(); return readObject(in, cache, code, version<VERSION_GEOMETRY?this.defaultVersion:version); } private Object readObject(ObjectInput in, List<Object> cache, int code, byte effectiveVersion) throws IOException, ClassNotFoundException { if (code == DataTypeManager.DefaultTypeCodes.BOOLEAN) { if (in.readByte() == (byte)0) { return Boolean.FALSE; } return Boolean.TRUE; } if (code != DataTypeManager.DefaultTypeCodes.OBJECT) { ColumnSerializer s = getSerializer(DataTypeManager.getDataTypeName(DataTypeManager.getClass(code)), effectiveVersion); return s.readObject(in, cache, effectiveVersion); } return super.readObject(in, cache, effectiveVersion); } @Override public boolean usesCache(byte version) { return version >= 3; } } private static final int MAX_UTF = 0xFFFF/3; //this is greater than the expected max length of Teiid Strings private static class StringColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { String str = (String)obj; if (str.length() <= MAX_UTF) { //skip object serialization if we have a short string out.writeByte(ObjectStreamConstants.TC_STRING); out.writeUTF(str); } else { out.writeByte(ObjectStreamConstants.TC_LONGSTRING); out.writeObject(obj); } } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { if (in.readByte() == ObjectStreamConstants.TC_STRING) { return in.readUTF(); } return super.readObject(in, cache, version); } } private static class StringColumnSerializer3 extends StringColumnSerializer1 { private static final int MAX_INLINE_STRING_LENGTH = 5; private static final byte REPEATED_STRING = 0; @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { byte b = in.readByte(); if (b == ObjectStreamConstants.TC_STRING) { String val = in.readUTF(); if (val.length() > MAX_INLINE_STRING_LENGTH) { cache.add(val); } return val; } if (b == REPEATED_STRING) { Integer val = in.readInt(); return cache.get(val); } String val = (String) in.readObject(); if (val.length() > MAX_INLINE_STRING_LENGTH) { cache.add(val); } return val; } @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { String str = (String)obj; Integer val = cache.get(str); if (val != null) { out.writeByte(REPEATED_STRING); out.writeInt(val); return; } if (str.length() > MAX_INLINE_STRING_LENGTH) { cache.put(str, cache.size()); } super.writeObject(out, obj, cache, version); } @Override public boolean usesCache(byte version) { return true; } } private static class NullColumnSerializer1 extends ColumnSerializer { @Override public void writeColumn(ObjectOutput out, int col, List<? extends List<?>> batch, Map<Object, Integer> cache, byte version) throws IOException { } @Override public void readColumn(ObjectInput in, int col, List<List<Object>> batch, byte[] isNull, List<Object> cache, byte version) throws IOException, ClassNotFoundException { } } private static class ClobColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { ((Externalizable)obj).writeExternal(out); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { ClobType ct = new ClobType(); ct.readExternal(in); return ct; } } private static class BlobColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { ((Externalizable)obj).writeExternal(out); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { BlobType bt = new BlobType(); bt.readExternal(in); return bt; } } private static class GeometryColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { ((Externalizable)obj).writeExternal(out); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { if (version < 4) { BlobType bt = new BlobType(); bt.readExternal(in); return bt; } GeometryType bt = new GeometryType(); bt.readExternal(in); return bt; } } private static class XmlColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { ((XMLType)obj).writeExternal(out, (byte) 1); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { XMLType xt = new XMLType(); xt.readExternal(in, (byte)1); return xt; } } /** * Packs the (boolean) information about whether data values in the column are null * into bytes so that we send ~n/8 instead of n bytes. * @param out * @param col * @param batch * @throws IOException * @since 4.2 */ static void writeIsNullData(ObjectOutput out, int col, List<? extends List<?>> batch) throws IOException { int numBytes = batch.size() / 8, row = 0, currentByte = 0; for (int byteNum = 0; byteNum < numBytes; byteNum++, row+=8) { currentByte = (batch.get(row).get(col) == null) ? 0x80 : 0; if (batch.get(row+1).get(col) == null) { currentByte |= 0x40; } if (batch.get(row+2).get(col) == null) { currentByte |= 0x20; } if (batch.get(row+3).get(col) == null) { currentByte |= 0x10; } if (batch.get(row+4).get(col) == null) { currentByte |= 0x08; } if (batch.get(row+5).get(col) == null) { currentByte |= 0x04; } if (batch.get(row+6).get(col) == null) { currentByte |= 0x02; } if (batch.get(row+7).get(col) == null) { currentByte |= 0x01; } out.write(currentByte); } if (batch.size() % 8 > 0) { currentByte = 0; for (int mask = 0x80; row < batch.size(); row++, mask >>= 1) { if (batch.get(row).get(col) == null) { currentByte |= mask; } } out.write(currentByte); } } static void writeIsNullData(ObjectOutput out, int offset, Object[] batch) throws IOException { int currentByte = 0; for (int mask = 0x80; offset < batch.length; offset++, mask >>= 1) { if (batch[offset] == null) { currentByte |= mask; } } out.write(currentByte); } /** * Reads the isNull data into a byte array * @param in * @param isNullBytes * @throws IOException * @since 4.2 */ static void readIsNullData(ObjectInput in, byte[] isNullBytes) throws IOException { for (int i = 0; i < isNullBytes.length; i++) { isNullBytes[i] = in.readByte(); } } /** * Gets whether a data value is null based on a packed byte array containing boolean data * @param isNull * @param row * @return * @since 4.2 */ static final boolean isNullObject(byte[] isNull, int row) { // byte number mask bits to shift mask return (isNull [ row / 8 ] & (0x01 << (7 - (row % 8)))) != 0; } private static final boolean isNullObject(int row, byte b) { return (b & (0x01 << (7 - (row % 8)))) != 0; } /** * An abstract serializer for native types * @since 4.2 */ private static class ColumnSerializer { public void writeColumn(ObjectOutput out, int col, List<? extends List<?>> batch, Map<Object, Integer> cache, byte version) throws IOException { writeIsNullData(out, col, batch); Object obj = null; for (int i = 0; i < batch.size(); i++) { obj = batch.get(i).get(col); if (obj != null) { writeObject(out, obj, cache, version); } } } public void readColumn(ObjectInput in, int col, List<List<Object>> batch, byte[] isNull, List<Object> cache, byte version) throws IOException, ClassNotFoundException { readIsNullData(in, isNull); for (int i = 0; i < batch.size(); i++) { if (!isNullObject(isNull, i)) { batch.get(i).set(col, DataTypeManager.getCanonicalValue(readObject(in, cache, version))); } } } protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeObject(obj); } protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException { return in.readObject(); } public boolean usesCache(byte version) { return false; } } private static class IntColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeInt(((Integer)obj).intValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return Integer.valueOf(in.readInt()); } } private static class LongColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeLong(((Long)obj).longValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return Long.valueOf(in.readLong()); } } private static class FloatColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeFloat(((Float)obj).floatValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new Float(in.readFloat()); } } private static class DoubleColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeDouble(((Double)obj).doubleValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new Double(in.readDouble()); } } private static class ShortColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeShort(((Short)obj).shortValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return Short.valueOf(in.readShort()); } } private static class BooleanColumnSerializer extends ColumnSerializer { /* This implementation compacts the isNull and boolean data for non-null values into a byte[] * by using a 8 bit mask that is bit-shifted to mask each value. */ @Override public void writeColumn(ObjectOutput out, int col, List<? extends List<?>> batch, Map<Object, Integer> cache, byte version) throws IOException { int currentByte = 0; int mask = 0x80; Object obj; for (int row = 0; row < batch.size(); row++) { // Write the isNull value obj = batch.get(row).get(col); if (obj == null ) { currentByte |= mask; } mask >>= 1; // Shift the mask to the next bit if (mask == 0) { // If the current byte has been used up, write it and reset. out.write(currentByte); currentByte = 0; mask = 0x80; } if (obj != null) { // Write the boolean value if it's not null if (((Boolean)obj).booleanValue()) { currentByte |= mask; } mask >>= 1; if (mask == 0) { out.write(currentByte); currentByte = 0; mask = 0x80; } } } // Invariant mask != 0 // If we haven't reached the eight-row mark then the loop would not have written this byte // Write the final byte containing data for the extra rows, if it exists. if (mask != 0x80) { out.write(currentByte); } } @Override public void readColumn(ObjectInput in, int col, List<List<Object>> batch, byte[] isNull, List<Object> cache, byte version) throws IOException, ClassNotFoundException { int currentByte = 0, mask = 0; // Initialize the mask so that it is reset in the loop boolean isNullVal; for (int row = 0; row < batch.size(); row++) { if (mask == 0) { // If we used up the byte, read the next one, and reset the mask currentByte = in.read(); mask = 0x80; } isNullVal = (currentByte & mask) != 0; mask >>= 1; // Shift the mask to the next bit if (!isNullVal) { if (mask == 0) { currentByte = in.read(); mask = 0x80; } batch.get(row).set(col, ((currentByte & mask) == 0) ? Boolean.FALSE : Boolean.TRUE); mask >>= 1; } } } } private static class ByteColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeByte(((Byte)obj).byteValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return Byte.valueOf(in.readByte()); } } private static class CharColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeChar(((Character)obj).charValue()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return Character.valueOf(in.readChar()); } } private static class BigIntegerColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { BigInteger val = (BigInteger)obj; byte[] bytes = val.toByteArray(); out.writeInt(bytes.length); out.write(bytes); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { int length = in.readInt(); byte[] bytes = new byte[length]; in.readFully(bytes); return new BigInteger(bytes); } } private static class BigDecimalColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { BigDecimal val = (BigDecimal)obj; out.writeInt(val.scale()); BigInteger unscaled = val.unscaledValue(); byte[] bytes = unscaled.toByteArray(); out.writeInt(bytes.length); out.write(bytes); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { int scale = in.readInt(); int length = in.readInt(); byte[] bytes = new byte[length]; in.readFully(bytes); return new BigDecimal(new BigInteger(bytes), scale); } } private static class DateColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeLong(((java.sql.Date)obj).getTime()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new java.sql.Date(in.readLong()); } } private static class TimeColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { out.writeLong(((Time)obj).getTime()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new Time(in.readLong()); } } static int DATE_NORMALIZER = 0; public final static long MIN_DATE_32; public final static long MAX_DATE_32; public final static long MIN_TIME_32; public final static long MAX_TIME_32; static { Calendar c = Calendar.getInstance(); c.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ c.set(1900, 0, 1, 0, 0, 0); c.set(Calendar.MILLISECOND, 0); MIN_DATE_32 = c.getTimeInMillis(); MAX_DATE_32 = MIN_DATE_32 + ((1l<<32)-1)*60000; DATE_NORMALIZER = -(int)(MIN_DATE_32/60000); //support a 32 bit range starting at this value MAX_TIME_32 = Integer.MAX_VALUE*1000l; MIN_TIME_32 = Integer.MIN_VALUE*1000l; } private static class DateColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { long time = ((java.sql.Date)obj).getTime(); if (time < MIN_DATE_32 || time > MAX_DATE_32) { throw new IOException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20029, obj.getClass().getName())); } out.writeInt((int)(time/60000) + DATE_NORMALIZER); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new java.sql.Date(((in.readInt()&0xffffffffL) - DATE_NORMALIZER)*60000); } } private static class TimeColumnSerializer1 extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { long time = ((Time)obj).getTime(); if (time < MIN_TIME_32 || time > MAX_TIME_32) { throw new IOException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20029, obj.getClass().getName())); } out.writeInt((int)(time/1000)); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { return new Time((in.readInt()&0xffffffffL)*1000); } } private static class TimestampColumnSerializer extends ColumnSerializer { @Override protected void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException { Timestamp ts = (Timestamp)obj; out.writeLong(ts.getTime()); out.writeInt(ts.getNanos()); } @Override protected Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException { Timestamp ts = new Timestamp(in.readLong()); ts.setNanos(in.readInt()); return ts; } } private static ColumnSerializer getSerializer(String type, byte version) { ColumnSerializer[] sers = serializers.get(type); if (sers == null) { if (DataTypeManager.isArrayType(type)) { if (version < 2) { return arrayColumnSerializer; } return arrayColumnSerialier2; } return defaultSerializer; } return sers[Math.min(version, sers.length - 1)]; } public static void writeBatch(ObjectOutput out, String[] types, List<? extends List<?>> batch) throws IOException { writeBatch(out, types, batch, CURRENT_VERSION); } public static void writeBatch(ObjectOutput out, String[] types, List<? extends List<?>> batch, byte version) throws IOException { if (batch == null) { out.writeInt(-1); } else { if (version > 0 && batch.size() > 0) { out.writeInt(-batch.size() -1); out.writeByte(version); } else { out.writeInt(batch.size()); } if (batch.size() > 0) { int columns = types.length; out.writeInt(columns); Map<Object, Integer> cache = null; for(int i = 0; i < columns; i++) { ColumnSerializer serializer = getSerializer(types[i], version); if (cache == null && serializer.usesCache(version)) { cache = new HashMap<Object, Integer>(); } try { serializer.writeColumn(out, i, batch, cache, version); } catch (ClassCastException e) { Object obj = null; String objectClass = null; objectSearch: for (int row = 0; row < batch.size(); row++) { obj = batch.get(row).get(i); if (obj != null) { objectClass = obj.getClass().getName(); break objectSearch; } } throw new TeiidRuntimeException(JDBCPlugin.Event.TEIID20001, e, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20001, new Object[] {types[i], new Integer(i), objectClass})); } } } } } public static List<List<Object>> readBatch(ObjectInput in, String[] types) throws IOException, ClassNotFoundException { int rows = 0; try { rows = in.readInt(); } catch (IOException e) { //7.4 compatibility if (types == null || types.length == 0) { List<Object>[] result = (List[])in.readObject(); ArrayList<List<Object>> batch = new ArrayList<List<Object>>(); batch.addAll(Arrays.asList(result)); return batch; } throw e; } if (rows == 0) { return new ArrayList<List<Object>>(0); } if (rows == -1) { return null; } byte version = (byte)0; if (rows < 0) { rows = -(rows+1); version = in.readByte(); } int columns = in.readInt(); List<List<Object>> batch = new ResizingArrayList<List<Object>>(rows); int numBytes = rows/8; int extraRows = rows % 8; for (int currentRow = 0; currentRow < rows; currentRow++) { batch.add(currentRow, Arrays.asList(new Object[columns])); } byte[] isNullBuffer = new byte[(extraRows > 0) ? numBytes + 1: numBytes]; List<Object> cache = null; for (int col = 0; col < columns; col++) { ColumnSerializer serializer = getSerializer(types[col], version); if (cache == null && serializer.usesCache(version)) { cache = new ArrayList<Object>(); } serializer.readColumn(in, col, batch, isNullBuffer, cache, version); } return batch; } }