/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * 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 * * 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. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.persistence.proxy; import java.io.IOException; import java.io.Reader; import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import net.ontopia.utils.OntopiaRuntimeException; /** * INTERNAL: Utility class for handling access to columns by proper * SQL type. Note that all type integers are the same as the ones in * java.sql.Types. */ public class SQLTypes { public static final int SIZE_THRESHOLD = 32768; // 65536; //! /** //! * INTERNAL: Gets the SQL type mapped to the given java class //! * referenced by the field descriptor. //! */ //! public static int getType(FieldDescriptor fdesc) { //! return getType(fdesc.getValueClass()); //! } /** * INTERNAL: Gets the SQL type mapped to the given java class. */ public static int getType(Class klass) { // FIXME: the SQL type retrieval should be configurable, since it // is often database specific. See also the TypeConverterIF // interface. // Figure out the SQL type if (klass.equals(java.lang.String.class)) return Types.VARCHAR; else if (klass.equals(java.lang.Long.class)) return Types.BIGINT; else if (klass.equals(java.lang.Integer.class)) return Types.INTEGER; else if (klass.equals(java.lang.Float.class)) return Types.FLOAT; else if (klass.equals(java.lang.Double.class)) return Types.DOUBLE; else if (klass.equals(java.lang.Boolean.class)) return Types.SMALLINT; else if (klass.equals(java.lang.Character.class)) return Types.CHAR; else if (klass.equals(java.lang.Short.class)) return Types.BIT; else if (klass.equals(java.lang.Byte.class)) return Types.TINYINT; else if (klass.equals(java.io.Reader.class)) return Types.CLOB; else throw new OntopiaRuntimeException("Cannot map value type " + klass + " to SQL type."); } /** * INTERNAL: Gets the java class mapped to by the given SQL type. */ public static Class getType(int sql_type) { // FIXME: the SQL type retrieval should be configurable, since it // is often database specific. See also the TypeConverterIF // interface. // Figure out the SQL type switch (sql_type) { case Types.VARCHAR: return java.lang.String.class; case Types.BIGINT: return java.lang.Long.class; case Types.INTEGER: return java.lang.Integer.class; case Types.FLOAT: return java.lang.Float.class; case Types.DOUBLE: return java.lang.Double.class; case Types.SMALLINT: return java.lang.Boolean.class; case Types.CHAR: return java.lang.Character.class; case Types.BIT: return java.lang.Short.class; case Types.TINYINT: return java.lang.Byte.class; case Types.CLOB: return java.io.Reader.class; default: throw new OntopiaRuntimeException("Cannot map SQL type " + sql_type + " to value type."); } } /** * INTERNAL: Reads the object of the given type at the specified * index from the result set. */ public static Object getObject(ResultSet rs, int index, int sql_type, boolean direct) throws SQLException { Object value; long longVal; int intVal; boolean boolVal; double doubleVal; float floatVal; short shortVal; byte byteVal; switch (sql_type) { case Types.BIGINT: longVal = rs.getLong(index); return (rs.wasNull() ? null : new Long(longVal)); case Types.VARCHAR: case Types.LONGVARCHAR: case Types.CHAR: return rs.getString(index); case Types.DECIMAL: case Types.NUMERIC: return rs.getBigDecimal(index); case Types.INTEGER: intVal = rs.getInt(index); return (rs.wasNull() ? null : new Integer(intVal)); case Types.TIME: return rs.getTime(index); case Types.DATE: return rs.getDate(index); case Types.TIMESTAMP: return rs.getTimestamp(index); case Types.DOUBLE: doubleVal = rs.getDouble(index); return (rs.wasNull() ? null : new Double(doubleVal)); case Types.FLOAT: case Types.REAL: floatVal = rs.getFloat(index); return (rs.wasNull() ? null : new Float(floatVal)); case Types.SMALLINT: shortVal = rs.getShort(index); return (rs.wasNull() ? null : new Short(shortVal)); case Types.TINYINT: byteVal = rs.getByte(index); return (rs.wasNull() ? null : new Byte(byteVal)); case Types.LONGVARBINARY: case Types.VARBINARY: case Types.BINARY: return rs.getBytes(index); case Types.BLOB: { try { if (direct) { return rs.getBinaryStream(index); } else { // attempt to turn into a string InputStream reader = rs.getBinaryStream(index); if (reader == null) return null; byte[] bytes = new byte[SQLTypes.SIZE_THRESHOLD]; int lengthRead = reader.read(bytes); if (lengthRead <= SQLTypes.SIZE_THRESHOLD && reader.read() == -1) { if (lengthRead > 0) { byte[] result = new byte[lengthRead]; System.arraycopy(bytes, 0, result, 0, result.length); return result; } else if (lengthRead == 0) return new byte[] { }; else return null; } else { return new OnDemandValue(); } } } catch (IOException e) { throw new OntopiaRuntimeException(e); } } case Types.CLOB: { try { if (direct) { return rs.getCharacterStream(index); } else { // attempt to turn into a string Reader reader = rs.getCharacterStream(index); if (reader == null) return null; char[] chars = new char[SQLTypes.SIZE_THRESHOLD]; int lengthRead = reader.read(chars); if (lengthRead <= SQLTypes.SIZE_THRESHOLD && reader.read() == -1) { if (lengthRead > 0) { return new String(chars, 0, lengthRead); } else { return ""; } } else { return new OnDemandValue(); } } } catch (IOException e) { throw new OntopiaRuntimeException(e); } } case Types.BIT: boolVal = rs.getBoolean(index); return (rs.wasNull() ? null : (boolVal ? Boolean.TRUE : Boolean.FALSE)); default: value = rs.getObject(index); return (rs.wasNull()? null : value); } } /** * INTERNAL: Binds the object of the given type at the specified * index in the prepared statement. */ public static void setObject(PreparedStatement stmt, int index, Object value, int sql_type) throws SQLException { if (value == null) { stmt.setNull(index, sql_type); } else { // Special processing for BLOB and CLOB types, because they // should be mapped to java.io.InputStream and java.io.Reader, // respectively, while JDBC driver expects java.sql.Blob and // java.sql.Clob. switch (sql_type) { case Types.BIGINT: stmt.setLong(index, ((Long)value).longValue()); break; case Types.VARCHAR: case Types.LONGVARCHAR: case Types.CHAR: stmt.setString(index, (String)value); break; case Types.INTEGER: stmt.setInt(index, ((Integer)value).intValue()); break; case Types.DOUBLE: stmt.setDouble(index, ((Double)value).doubleValue()); break; case Types.FLOAT: case Types.REAL: stmt.setFloat(index, ((Float)value).floatValue()); break; case Types.SMALLINT: stmt.setShort(index, ((Short)value).shortValue()); break; case Types.BLOB: if (value instanceof byte[]) { //stmt.setString(index, (String)value); byte[] b = (byte[])value; stmt.setBinaryStream(index, new java.io.ByteArrayInputStream(b), b.length); } else if (value instanceof OnDemandValue) { OnDemandValue odv = (OnDemandValue)value; ContentInputStream blob = (ContentInputStream)odv.getValue(); if (blob != null) stmt.setBinaryStream(index, blob, (int)blob.getLength()); else stmt.setNull(index, sql_type); } break; case Types.CLOB: if (value instanceof String) { //stmt.setString(index, (String)value); String s = (String)value; stmt.setCharacterStream(index, new java.io.StringReader(s), s.length()); } else if (value instanceof OnDemandValue) { OnDemandValue odv = (OnDemandValue)value; ContentReader clob = (ContentReader)odv.getValue(); if (clob != null) stmt.setCharacterStream(index, clob, (int)clob.getLength()); else stmt.setNull(index, sql_type); } else { throw new OntopiaRuntimeException("Unsupported CLOB value: " + value); } break; default: stmt.setObject(index, value, sql_type); break; } } } }