// Copyright (c) 2007, Oracle. All rights reserved.
package oracle.toplink.essentials.extension.spatial;
import java.io.Writer;
import java.sql.*;
import oracle.spatial.geometry.JGeometry;
import oracle.sql.STRUCT;
import oracle.toplink.essentials.internal.databaseaccess.BindCallCustomParameter;
import oracle.toplink.essentials.internal.helper.DatabaseField;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.platform.database.oracle.OraclePlatform;
import oracle.toplink.essentials.queryframework.Call;
/**
* This custom database platform extends the base OraclePlatform shipped in
* TopLink essentials providing support for reading and writing Oracle Spatial
* oracle.spatial.geometry.JGeometry types. These types are generally mapped to
* MDSYS.SDO_GEOMETRY columns
*
* @author Doug Clarke
* @since TopLink Essentials v2b41
*/
public class Oracle10SpatialPlatform extends OraclePlatform {
private static final Class ORACLE_STRUCT = STRUCT.class;
private static final String JGEOMETRY_DB_TYPE = "MDSYS.SDO_GEOMETRY";
private static final Class JGEOMETRY_CLASS = JGeometry.class;
/**
* After retrieving the object from the ResultSet the geometry STRUCT's are
* identified and immediately converted into JGeometry instances. This must
* be done here as an active JDBC connection is required.
*/
public Object getObjectFromResultSet(ResultSet resultSet, int columnNumber,
int type) throws SQLException {
Object result =
super.getObjectFromResultSet(resultSet, columnNumber, type);
if (result != null && type == Types.STRUCT &&
result.getClass() == ORACLE_STRUCT &&
((STRUCT)result).getDescriptor().getSQLName().getName().equalsIgnoreCase(JGEOMETRY_DB_TYPE)) {
return JGeometry.load((STRUCT)result);
}
return result;
}
/**
* Overriding this method allows the platform to perform special processing
* of types. In this case the usage of JGeometry types requires the values
* to always be bound. In the case of a NULL JGeometry instance a special
* place-holder (NullSTRUCT) is used. This ensure that the null value is
* properly bound into the JDBC statement.
*
* @return the value or a custom modify call
*/
public Object getCustomModifyValueForCall(Call call, Object value,
DatabaseField field,
boolean shouldBind) {
if (field.getType() == JGEOMETRY_CLASS) {
Object bindValue = value;
if (bindValue == null) {
bindValue = new NullSTRUCT(JGEOMETRY_DB_TYPE);
}
return new BindCallCustomParameter(bindValue);
}
return super.getCustomModifyValueForCall(call, value, field,
shouldBind);
}
/**
* Determine if a custom modify call should be used for a specific database
* field.
*
* @see #getCustomModifyValueForCall
*/
public boolean shouldUseCustomModifyForCall(DatabaseField field) {
return field.getType() == JGEOMETRY_CLASS ||
super.shouldUseCustomModifyForCall(field);
}
/**
* When binding values into a PreparedStatement this method allows a custom
* platform to provide special type support. Here the NullSTRUCT and
* JGeometry->Struct binding logic is supported.
*/
public void setParameterValueInDatabaseCall(Object parameter,
PreparedStatement statement,
int index,
AbstractSession session) throws SQLException {
Object param = parameter;
if (param != null && parameter.getClass() == NullSTRUCT.class) {
((NullSTRUCT)param).setNull(statement, index);
return;
}
if (param != null && parameter.getClass() == JGEOMETRY_CLASS) {
param =
JGeometry.store((JGeometry)parameter, statement.getConnection());
}
super.setParameterValueInDatabaseCall(param, statement, index,
session);
}
/**
* When TopLink Essentials is generating SQL the platform can provide custom
* formatting for various types. Here the JGeometry instances being written
* are forced into a binding scenario instead.
*/
public void appendParameter(Call call, Writer writer, Object parameter) {
Object param = parameter;
if (param != null && param.getClass() == JGEOMETRY_CLASS) {
param = new BindCallCustomParameter(param);
}
super.appendParameter(call, writer, param);
}
/**
* Place holder for null be bound for a STRUCT column. By default TopLink
* binds all null values as a String. This fails for STRUCT columns. This
* placeholder is used to wrap the database column type so that it can also
* be used in the statement.setNull call.
*/
private class NullSTRUCT {
String dbType;
NullSTRUCT(String dbTypeName) {
this.dbType = dbTypeName;
}
void setNull(PreparedStatement statement,
int index) throws SQLException {
statement.setNull(index, Types.STRUCT, this.dbType);
}
public String toString() {
return "NULL (" + this.dbType + ")";
}
}
}