/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.hibernate.util;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.type.StringClobType;
import org.hibernate.usertype.UserType;
import com.mchange.v2.c3p0.impl.C3P0ResultSetPeeker;
/**
* A hibernate UserType that supports the Oracle XMLTYPE.
*
* Because we store the XMLTYPE as a String in our domain class it seems
* adequate to inherit {@link StringClobType}'s implementation of everything except:
* nullSafeGet() and nullSafeSet()
* <p>
* To use this class in your domain class add the following above the class
* declaration (where the @Entity is): <br>
* @TypeDef(name = "xmltype", typeClass = HibernateXmlType.class)
* <p>
* Then add this to the Java Strings that map Oracle XMLTYPEs in the DB: <br>
* @Type(type = "xmltype")
* <p>
* NOTE(rtobar): When we integrated Robert's class into ACS, we modified it to use reflection so that
* we get no errors if the Oracle jars are not on the classpath. Reflection is also used for inspecting
* for C3P0 classes. If classes are not found, then simply the old CLOB behavior will be used.
*
* @author rkurowsk, Jan 22, 2010
*/
public class HibernateXmlType extends StringClobType implements UserType, Serializable {
private static final String COM_MCHANGE_V2_C3P0 = "com.mchange.v2.c3p0";
private static final String ORACLE_JDBC = "oracle.jdbc";
private static final String GET_STRING_VAL = "getStringVal";
private static final String XML_TYPE = "oracle.xdb.XMLType";
private static final long serialVersionUID = 2838406736360323902L;
/* (non-Javadoc)
* @see org.hibernate.type.StringClobType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
if( rs.getClass().getName().startsWith(COM_MCHANGE_V2_C3P0) )
rs = C3P0ResultSetPeeker.getInnerFrom(rs);
if ( rs.getClass().getName().startsWith(ORACLE_JDBC) ) {
// result set comes from oracle thin jdbc driver, and thanks to the hibernate annotations
// we know that the column type is XMLTYPE, so we can simply cast...
try {
Class<?> clazz = Class.forName(XML_TYPE);
Method m = clazz.getMethod(GET_STRING_VAL);
Object xmlType = rs.getObject(names[0]);
return (xmlType != null) ? m.invoke(xmlType) : null;
} catch (Exception e) {
throw new SQLException("Failed to convert XMLTYPE String to Document for retrieval", e);
}
} else {
// If not Oracle (i.e. HSQLDB) use the hibernate StringClobType impl of nullSafeGet()
return super.nullSafeGet(rs, names, owner);
}
}
/* (non-Javadoc)
* @see org.hibernate.type.StringClobType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
JdbcNativeExtractor extractor = new JdbcNativeExtractor();
Connection connection = extractor.getNativeConnection(st.getConnection());
if ( connection.getClass().getName().startsWith(ORACLE_JDBC) ) {
try {
Object xmlType = null;
if (value != null) {
Class<?> clazz = Class.forName(XML_TYPE);
Constructor<?> con = clazz.getConstructor(Connection.class, String.class);
xmlType = con.newInstance(connection, (String)value);
st.setObject(index, xmlType);
} else {
/* TODO: 2007 is oracle.jdbc.OracleTypes.OPAQUE, use reflection */
st.setNull( index, 2007, "SYS.XMLTYPE");
}
}
catch (Exception e) {
throw new SQLException("Failed to convert Document to XMLTYPE String for storage", e);
}
} else {
// If not Oracle (i.e. HSQLDB) use the hibernate StringClobType impl of nullSafeSet()
super.nullSafeSet(st, value, index);
}
}
}