/* * Copyright (C) 2009 eXo Platform SAS. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.etk.component.database.impl; import org.hibernate.HibernateException; import org.hibernate.usertype.UserType; import java.io.IOException; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; /** * Created by The eXo Platform SAS . Author : Travis Gregg Date: Oct 25, 2004 * Time: 1:12:22 PM <br> * Custom Hibernate type to be used instead of the built in type 'TEXT'. This * custom type was created to handle Oracle Clobs differently. Using the 'TEXT' * type, Oracle Clobs can not participate in transactions, nor can they be * pulled into batches. For some reason, handling them this way, using oracle * temporaries gets around this. <BR> * <BR> * Thanks to reflection (and code on the Hibernate forum) this class can be * compiled without having the oracle drivers (classes12.zip) in your classpath. <br> * This is based on some code found in the Hibernate user forum: * http://www.hibernate.org/56.html <br> * http://www.hibernate.org/73.html * * @author tgregg */ public class TextClobType implements UserType { public TextClobType() { } public int[] sqlTypes() { return new int[]{Types.CLOB}; } public Class returnedClass() { return String.class; } public boolean equals(Object x, Object y) throws HibernateException { return (x == y) || (x != null && x.equals(y)); } // TODO cleanup, fix of COR-129. [06.03.2009] // private void nullSafeSetOld(PreparedStatement stmt, Object value, int index) throws HibernateException, // SQLException { // // // if this is a PreparedStatement wrapper, get the underlying // // PreparedStatement // PreparedStatement realStatement = getRealStatement(stmt); // // DatabaseMetaData dbMetaData = realStatement.getConnection().getMetaData(); // // if (value == null) { // stmt.setNull(index, sqlTypes()[0]); // } else if (ORACLE_DRIVER_NAME.equals(dbMetaData.getDriverName())) { // if ((dbMetaData.getDriverMajorVersion() >= ORACLE_DRIVER_MAJOR_VERSION) // && (dbMetaData.getDriverMinorVersion() >= ORACLE_DRIVER_MINOR_VERSION)) { // try { // // Code compliments of Scott Miller // // support oracle clobs without requiring oracle libraries // // at compile time // // Note this assumes that if you are using the Oracle // // Driver. // // then you have access to the oracle.sql.CLOB class // // // First get the oracle clob class // Class oracleClobClass = Class.forName("oracle.sql.CLOB"); // // // Get the oracle connection class for checking // Class oracleConnectionClass = Class.forName("oracle.jdbc.OracleConnection"); // // // now get the static factory method // Class partypes[] = new Class[3]; // partypes[0] = Connection.class; // partypes[1] = Boolean.TYPE; // partypes[2] = Integer.TYPE; // Method createTemporaryMethod = oracleClobClass.getDeclaredMethod("createTemporary", // partypes); // // now get ready to call the factory method // Field durationSessionField = oracleClobClass.getField("DURATION_SESSION"); // Object arglist[] = new Object[3]; // Connection conn = realStatement.getConnection(); // // // Make sure connection object is right type // if (!oracleConnectionClass.isAssignableFrom(conn.getClass())) { // throw new HibernateException("JDBC connection object must be a oracle.jdbc.OracleConnection. " // + "Connection class is " + conn.getClass().getName()); // } // // arglist[0] = conn; // arglist[1] = Boolean.TRUE; // arglist[2] = durationSessionField.get(null); // null is // // valid // // because of // // static field // // // Create our CLOB // Object tempClob = createTemporaryMethod.invoke(null, arglist); // null // // is // // valid // // because // // of // // static // // method // // // get the open method // partypes = new Class[1]; // partypes[0] = Integer.TYPE; // Method openMethod = oracleClobClass.getDeclaredMethod("open", partypes); // // // prepare to call the method // Field modeReadWriteField = oracleClobClass.getField("MODE_READWRITE"); // arglist = new Object[1]; // arglist[0] = modeReadWriteField.get(null); // null is valid // // because of // // static field // // // call open(CLOB.MODE_READWRITE); // openMethod.invoke(tempClob, arglist); // // // get the getCharacterOutputStream method // // Method getCharacterOutputStreamMethod = // // oracleClobClass.getDeclaredMethod( // // "getCharacterOutputStream", null ); // // // use getAsciiOutputStream for special characters, // // using the Writer obtained from 'getCharacterOutputStream" // // causes // // 'No more data to read from socket' when inserting special // // characters // Method getAsciiOutputStreamMethod = oracleClobClass.getDeclaredMethod("getAsciiOutputStream", // null); // // // call the getCharacterOutpitStream method // OutputStream tempClobOutputStream = (OutputStream) getAsciiOutputStreamMethod.invoke(tempClob, // null); // // // write the string to the clob // tempClobOutputStream.write(((String) value).getBytes()); // tempClobOutputStream.flush(); // tempClobOutputStream.close(); // // // get the close method // Method closeMethod = oracleClobClass.getDeclaredMethod("close", null); // // // call the close method // closeMethod.invoke(tempClob, null); // // // add the clob to the statement // realStatement.setClob(index, (Clob) tempClob); // } catch (ClassNotFoundException e) { // // could not find the class with reflection // throw new HibernateException("Unable to find a required class.\n" + e.getMessage()); // } catch (NoSuchMethodException e) { // // could not find the metho with reflection // throw new HibernateException("Unable to find a required method.\n" + e.getMessage()); // } catch (NoSuchFieldException e) { // // could not find the field with reflection // throw new HibernateException("Unable to find a required field.\n" + e.getMessage()); // } catch (IllegalAccessException e) { // throw new HibernateException("Unable to access a required method or field.\n" // + e.getMessage()); // } catch (InvocationTargetException e) { // throw new HibernateException(e.getMessage()); // } catch (IOException e) { // throw new HibernateException(e.getMessage()); // } // } else { // throw new HibernateException("No CLOBS support. Use driver version " // + ORACLE_DRIVER_MAJOR_VERSION + ", minor " + ORACLE_DRIVER_MINOR_VERSION); // } // } else { // // this is the default way to handle Clobs that seems to work with // // most Databases // String str = (String) value; // StringReader r = new StringReader(str); // stmt.setCharacterStream(index, r, str.length()); // } // } /** * {@inheritDoc} */ public void nullSafeSet(PreparedStatement stmt, Object value, int index) throws HibernateException, SQLException { if (value == null) { stmt.setNull(index, sqlTypes()[0]); } else { final String str = value instanceof String ? (String)value : value.toString(); stmt.setCharacterStream(index, new StringReader(str), str.length()); } } /** * This method tries to determine if the passed PreparedStatement is a Wrapper * for an actual PreparedStatement. Database objects are often wrapped in * pooling implementations to handle connection clean up, and EJB type * transaction participation. We need to get at the real PreparedStatement to * determin if it is an Oracle PreparedStatement that is being wrapped. This * allows us to handle Oracle differently, since Oracle LOB types work * differently in JDBC than all other databases. * * @param stmt * @return The passed statement, or the PreparedStatement that the passed stmt * is wrapping. * @throws HibernateException */ PreparedStatement getRealStatement(PreparedStatement stmt) throws HibernateException { Method[] methods = stmt.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { String returnType = methods[i].getReturnType().getName(); // if the method has no parameters and the return type is either // Statement or PreparedStatement // then reflectively call this method to get the underlying // PreparedStatement // (JBoss returns a Statement that we must cast) if (((Statement.class.getName().equals(returnType)) || (PreparedStatement.class.getName().equals(returnType))) && methods[i].getParameterTypes().length == 0) { Statement s = null; try { s = (Statement)methods[i].invoke(stmt, null); } catch (SecurityException e) { throw new HibernateException("Security Error getting method [getDelegate] on [" + stmt.getClass().getName() + "::" + methods[i].getName() + "]", e); } catch (IllegalArgumentException e) { throw new HibernateException("Error calling method [getDelegate] on [" + stmt.getClass().getName() + "::" + methods[i].getName() + "]", e); } catch (IllegalAccessException e) { throw new HibernateException("Error calling method [getDelegate] on [" + stmt.getClass().getName() + "::" + methods[i].getName() + "]", e); } catch (InvocationTargetException e) { throw new HibernateException("Error calling method [getDelegate] on [" + stmt.getClass().getName() + "::" + methods[i].getName() + "]", e); } return (PreparedStatement)s; } } // Did not find a parameterless method that returned a Statement, return // what we were passed return stmt; } /** * @throws HibernateException * @see net.sf.hibernate.UserType#deepCopy(java.lang.Object) */ public Object deepCopy(Object value) throws HibernateException { if (value == null) { return null; } String stringValue = (String)value; return new String(stringValue); } /** * @see net.sf.hibernate.UserType#isMutable() */ public boolean isMutable() { return false; } /** * Impl copied from net.sf.hibernate.type.TextType Generated 10:08:31 AM May * 21, 2004 * * @param rs * @param names * @param owner * @return * @throws HibernateException * @throws SQLException * @see net.sf.hibernate.UserType#nullSafeGet(java.sql.ResultSet, * java.lang.String[], java.lang.Object) */ public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { // Retrieve the value of the designated column in the current row of // this // ResultSet object as a java.io.Reader object Reader charReader = rs.getCharacterStream(names[0]); // if the corresponding SQL value is NULL, the reader we got is NULL as // well if (charReader == null) return null; // Fetch Reader content up to the end - and put characters in a // StringBuffer StringBuffer sb = new StringBuffer(); try { char[] buffer = new char[2048]; while (true) { int amountRead = charReader.read(buffer, 0, buffer.length); if (amountRead == -1) break; sb.append(buffer, 0, amountRead); } } catch (IOException ioe) { throw new HibernateException("IOException occurred reading text", ioe); } finally { try { charReader.close(); } catch (IOException e) { throw new HibernateException("IOException occurred closing stream", e); } } // Return StringBuffer content as a large String return sb.toString(); } // Hibernate3 methods ! /* * Reconstruct an object from the cacheable representation. At the very least * this method should perform a deep copy if the type is mutable. (optional * operation) * @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, * java.lang.Object) */ public Object assemble(Serializable cached, Object owner) throws HibernateException { // ? return deepCopy(cached); } /* * Transform the object into its cacheable representation. At the very least * this method should perform a deep copy if the type is mutable. That may not * be enough for some implementations, however; for example, associations must * be cached as identifier values. (optional operation) * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object) */ public Serializable disassemble(Object value) throws HibernateException { // ? return new String(value.toString()); } /* * Get a hashcode for the instance, consistent with persistence "equality" * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object) */ public int hashCode(Object x) throws HibernateException { // ? return super.hashCode(); } /* * During merge, replace the existing (target) value in the entity we are * merging to with a new (original) value from the detached entity we are * merging. For immutable objects, or null values, it is safe to simply return * the first parameter. For mutable objects, it is safe to return a copy of * the first parameter. For objects with component values, it might make sense * to recursively replace component values. * @see org.hibernate.usertype.UserType#replace(java.lang.Object, * java.lang.Object, java.lang.Object) */ public Object replace(Object original, Object target, Object owner) throws HibernateException { // ? return original; } }