Return-Path: Delivered-To: apmail-db-ojb-dev-archive@www.apache.org Received: (qmail 90638 invoked from network); 24 Jan 2004 00:09:02 -0000 Received: from daedalus.apache.org (HELO mail.apache.org) (208.185.179.12) by minotaur-2.apache.org with SMTP; 24 Jan 2004 00:09:02 -0000 Received: (qmail 79816 invoked by uid 500); 24 Jan 2004 00:08:45 -0000 Delivered-To: apmail-db-ojb-dev-archive@db.apache.org Received: (qmail 79793 invoked by uid 500); 24 Jan 2004 00:08:45 -0000 Mailing-List: contact ojb-dev-help@db.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Subscribe: List-Help: List-Post: List-Id: "OJB Developers List" Reply-To: "OJB Developers List" Delivered-To: mailing list ojb-dev@db.apache.org Received: (qmail 79743 invoked by uid 500); 24 Jan 2004 00:08:44 -0000 Received: (qmail 79735 invoked from network); 24 Jan 2004 00:08:44 -0000 Received: from unknown (HELO minotaur.apache.org) (209.237.227.194) by daedalus.apache.org with SMTP; 24 Jan 2004 00:08:44 -0000 Received: (qmail 90624 invoked by uid 1518); 24 Jan 2004 00:08:58 -0000 Date: 24 Jan 2004 00:08:58 -0000 Message-ID: <20040124000858.90623.qmail@minotaur.apache.org> From: mattbaird@apache.org To: db-ojb-cvs@apache.org Subject: cvs commit: db-ojb/src/java/org/apache/ojb/broker/platforms PlatformOracleImpl.java PlatformDefaultImpl.java Oracle9iLobHandler.java BlobWrapper.java PlatformOracle9iImpl.java Platform.java ClobWrapper.java X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N X-Spam-Rating: minotaur-2.apache.org 1.6.2 0/1000/N mattbaird 2004/01/23 16:08:58 Modified: src/java/org/apache/ojb/broker/util JdbcTypesHelper.java ClassHelper.java src/java/org/apache/ojb/broker/platforms PlatformOracleImpl.java PlatformDefaultImpl.java Oracle9iLobHandler.java BlobWrapper.java PlatformOracle9iImpl.java Platform.java ClobWrapper.java Log: Oracle BLOB/CLOB lovin' thin driver in 9i works with > 4k blobs and clobs. thanks to all who helped. Revision Changes Path 1.5 +32 -4 db-ojb/src/java/org/apache/ojb/broker/util/JdbcTypesHelper.java Index: JdbcTypesHelper.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/util/JdbcTypesHelper.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- JdbcTypesHelper.java 19 Dec 2003 16:23:52 -0000 1.4 +++ JdbcTypesHelper.java 24 Jan 2004 00:08:57 -0000 1.5 @@ -997,6 +997,34 @@ public static final class T_Clob extends BaseType { + protected static final int BUFSZ = 32768; + /** + * Convert CLOB to String. Safe for very large objects. + * @param aClob clob with character data + * @return a string containing the clob data + * @throws SQLException if conversion fails or the clob cannot be read + */ + protected static String safeClobToString(Clob aClob) throws SQLException { + long length = aClob.length(); + if (length == 0) { + return new String(); + } + StringBuffer sb = new StringBuffer(); + char[] buf = new char[BUFSZ]; + java.io.Reader stream = aClob.getCharacterStream(); + try { + int numRead; + while ((numRead = stream.read(buf)) != -1) + { + sb.append(buf, 0, numRead); + } + stream.close(); + } catch (java.io.IOException e) { + throw new SQLException(e.getLocalizedMessage()); + } + return sb.toString(); + } + public Object sequenceKeyConversion(Long identifier) throws SequenceManagerException { throw new SequenceManagerException("Not supported sequence key type 'CLOB'"); @@ -1011,19 +1039,19 @@ Object readValueFromStatement(CallableStatement stmt, int columnIndex) throws SQLException { Clob aClob = stmt.getClob(columnIndex); - return (stmt.wasNull() ? null : aClob.getSubString(1L, (int) aClob.length())); + return (stmt.wasNull() ? null : safeClobToString(aClob)); } Object readValueFromResultSet(ResultSet rs, String columnName) throws SQLException { Clob aClob = rs.getClob(columnName); - return (rs.wasNull() ? null : aClob.getSubString(1L, (int) aClob.length())); + return (rs.wasNull() ? null : safeClobToString(aClob)); } Object readValueFromResultSet(ResultSet rs, int columnIndex) throws SQLException { Clob aClob = rs.getClob(columnIndex); - return (rs.wasNull() ? null : aClob.getSubString(1L, (int) aClob.length())); + return (rs.wasNull() ? null : safeClobToString(aClob)); } public int getType() 1.4 +68 -1 db-ojb/src/java/org/apache/ojb/broker/util/ClassHelper.java Index: ClassHelper.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/util/ClassHelper.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- ClassHelper.java 31 Aug 2003 00:46:30 -0000 1.3 +++ ClassHelper.java 24 Jan 2004 00:08:58 -0000 1.4 @@ -1,6 +1,8 @@ package org.apache.ojb.broker.util; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Field; /** * @@ -38,6 +40,40 @@ return target.getConstructor(types).newInstance(args); } + /** + * Returns a method via reflection look-up of the specific signature. + * @param clazz method's java class + * @param methodName method name + * @param params method signature + * @return method invokable via java.lang.reflect.Method#invoke, + * or null if no matching method can be found + */ + public static Method getMethod(Class clazz, String methodName, Class[] params) { + Method method; + try { + method = clazz.getMethod(methodName, params); + } catch (Exception ignore) { + method = null; + } + return method; + } + + /** + * Returns a field via reflection look-up. + * @param clazz fields's java class + * @param fieldName field name + * @return field retrievable via java.lang.reflect.Field#getXXX, + * or null if no matching field can be found + */ + public static Field getField(Class clazz, String fieldName) { + Field field; + try { + field = clazz.getField(fieldName); + } catch (Exception ignore) { + field = null; + } + return field; + } // ******************************************************************* @@ -86,4 +122,35 @@ { return newInstance(className, new Class[]{type}, new Object[]{arg}); } + + /** + * Returns a method via reflection look-up of the specific signature. + * @param object runtime object instance + * @param methodName method name + * @param params method signature + * @return method invokable via java.lang.reflect.Method#invoke, + * or null if no matching method can be found + */ + public static Method getMethod(Object object, String methodName, Class[] params) { + return getMethod(object.getClass(), methodName, params); + } + + /** + * Returns a method via reflection look-up of the specific signature. + * @param className class name + * @param methodName method name + * @param params method signature + * @return method invokable via java.lang.reflect.Method#invoke, + * or null if no matching method can be found + */ + public static Method getMethod(String className, String methodName, Class[] params) { + Method method = null; + try { + Class clazz = getClass(className, false); + method = getMethod(clazz, methodName, params); + } catch (Exception ignore) { + } + return method; + } + } 1.16 +54 -7 db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformOracleImpl.java Index: PlatformOracleImpl.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformOracleImpl.java,v retrieving revision 1.15 retrieving revision 1.16 diff -u -r1.15 -r1.16 --- PlatformOracleImpl.java 29 Jul 2003 16:54:12 -0000 1.15 +++ PlatformOracleImpl.java 24 Jan 2004 00:08:58 -0000 1.16 @@ -64,6 +64,8 @@ import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; +import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; @@ -80,6 +82,11 @@ public class PlatformOracleImpl extends PlatformDefaultImpl { + protected static final String THIN_URL_PREFIX = "jdbc:oracle:thin"; + // Oracle:thin handles direct BLOB insert <= 4000 and update <= 2000 + protected static final int THIN_BLOB_MAX_SIZE = 2000; + // Oracle:thin handles direct CLOB insert and update <= 4000 + protected static final int THIN_CLOB_MAX_SIZE = 4000; private Logger logger = LoggerFactory.getLogger(PlatformOracleImpl.class); /** @@ -114,9 +121,13 @@ (sqlType == Types.BLOB)) && (value instanceof byte[])) { byte buf[] = (byte[]) value; + int length = buf.length; + if (isUsingOracleThinDriver(ps.getConnection()) && length > THIN_BLOB_MAX_SIZE) { + throw new SQLException("Oracle thin driver cannot update BLOB values with length>2000. (Consider using Oracle9i as OJB platform.)"); + } ByteArrayInputStream inputStream = new ByteArrayInputStream(buf); changePreparedStatementResultSetType(ps); - ps.setBinaryStream(index, inputStream, buf.length); + ps.setBinaryStream(index, inputStream, length); } else if (value instanceof Double) { @@ -132,20 +143,26 @@ { ps.setLong(index, ((Long) value).longValue()); } - else if (sqlType == Types.CLOB) + else if (sqlType == Types.CLOB && + (value instanceof String || value instanceof byte[])) { Reader reader = null; int length = 0; if (value instanceof String) { - reader = new StringReader((String) value); - length = (((String) value)).length(); + String stringValue = (String) value; + length = stringValue.length(); + reader = new StringReader(stringValue); } - else if (value instanceof byte[]) + else { byte buf[] = (byte[]) value; ByteArrayInputStream inputStream = new ByteArrayInputStream(buf); reader = new InputStreamReader(inputStream); + length = buf.length; + } + if (isUsingOracleThinDriver(ps.getConnection()) && length > THIN_CLOB_MAX_SIZE) { + throw new SQLException("Oracle thin driver cannot insert CLOB values with length>4000. (Consider using Oracle9i as OJB platform.)"); } ps.setCharacterStream(index, reader, length); } @@ -178,7 +195,7 @@ } catch (Exception e) { - this.logger.info("Not using classes12.zip."); + logger.info("Not using classes12.zip."); } } @@ -204,4 +221,34 @@ { return "drop sequence " + sequenceName; } + + /** + * Checks if the supplied connection is using the Oracle thin driver. + * @param conn database connection for which to check JDBC-driver + * @return true if the connection is using Oracle thin driver, + * false otherwise. + */ + protected static boolean isUsingOracleThinDriver(Connection conn) + { + if (conn == null) + { + return false; + } + final DatabaseMetaData dbMetaData; + final String dbUrl; + try + { + dbMetaData = conn.getMetaData(); + dbUrl = dbMetaData.getURL(); + if (dbUrl != null && dbUrl.startsWith(THIN_URL_PREFIX)) + { + return true; + } + } + catch (Exception e) + { + } + return false; + } + } 1.22 +1 -7 db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformDefaultImpl.java Index: PlatformDefaultImpl.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformDefaultImpl.java,v retrieving revision 1.21 retrieving revision 1.22 diff -u -r1.21 -r1.22 --- PlatformDefaultImpl.java 16 Nov 2003 18:21:48 -0000 1.21 +++ PlatformDefaultImpl.java 24 Jan 2004 00:08:58 -0000 1.22 @@ -333,12 +333,6 @@ throw new UnsupportedOperationException("This feature is not supported by this implementation"); } - public Object getClob(ResultSet rs, int jdbcType, String columnId) throws SQLException - { - java.sql.Clob aClob = rs.getClob(columnId); - return (rs.wasNull() ? null : aClob.getSubString(1L, (int) aClob.length())); - } - /* (non-Javadoc) * @see org.apache.ojb.broker.platforms.Platform#addPagingSql(java.lang.StringBuffer) */ 1.2 +298 -178 db-ojb/src/java/org/apache/ojb/broker/platforms/Oracle9iLobHandler.java Index: Oracle9iLobHandler.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/Oracle9iLobHandler.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Oracle9iLobHandler.java 29 Jul 2003 16:54:12 -0000 1.1 +++ Oracle9iLobHandler.java 24 Jan 2004 00:08:58 -0000 1.2 @@ -54,75 +54,82 @@ * . */ +import org.apache.ojb.broker.util.logging.Logger; +import org.apache.ojb.broker.util.logging.LoggerFactory; + import java.io.*; import java.sql.Connection; +import java.lang.reflect.InvocationTargetException; /** - * handles the Oracle LOB problems for 9i + * Handles the Oracle LOB problems for 9i. * * @author Matthew Baird + * @author Erik Forkalsrud + * @author Martin Kalén + * @version CVS $Id$ */ - public class Oracle9iLobHandler { - Connection m_connection = null; - // Temporary LOBs - BlobWrapper m_tempBlob = null; + + protected static Logger logger = LoggerFactory.getLogger(Oracle9iLobHandler.class); private static ClobWrapper createTempCLOB(Connection connection, ClobWrapper clob) { - ClobWrapper tempClob = null; - if (clob != null) - { - try - { - // Create a temporary CLOB with duration session - tempClob = ClobWrapper.createTemporary(connection, true, ClobWrapper.DURATION_SESSION); - - // Open the CLOB in readonly mode - clob.open(ClobWrapper.MODE_READONLY); - - // Open the temporary CLOB in readwrite mode to enable writing - tempClob.open(ClobWrapper.MODE_READWRITE); + if (clob == null) { + return null; + } - // No of bytes read each trip to database - int bytesread = 0; - - // Get the input stream for reading from the CLOB - Reader clobReader = clob.getCharacterStream(); - - // Get the output stream for writing into the CLOB - Writer tempClobWriter = tempClob.getCharacterOutputStream(); - - // Create a buffer to read data - // getBufferSize() returns the optimal buffer size - char[] charbuffer = new char[clob.getBufferSize()]; - - // Read from the CLOB and write into the temporary CLOB - while ((bytesread = clobReader.read(charbuffer)) != -1) - tempClobWriter.write(charbuffer, 0, bytesread); - - // Flush and close the streams - tempClobWriter.flush(); - tempClobWriter.close(); - clobReader.close(); - - // Close the CLOBs - clob.close(); - tempClob.close(); - } - catch (Exception ex) - { - // Since an error has been caught, free the temporary LOBs - freeTempLOB(tempClob, null); - } - } + ClobWrapper tempClob = null; + try + { + // Create a temporary CLOB with duration session + tempClob = ClobWrapper.createTemporary(connection, true, ClobWrapper.getDurationSessionValue()); + + // Open the CLOB in readonly mode + clob.open(ClobWrapper.getModeReadOnlyValue()); + + // Open the temporary CLOB in readwrite mode to enable writing + tempClob.open(ClobWrapper.getModeReadWriteValue()); + + // No of bytes read each trip to database + int bytesread; + + // Get the input stream for reading from the CLOB + Reader clobReader = clob.getCharacterStream(); + + // Get the output stream for writing into the CLOB + Writer tempClobWriter = tempClob.getCharacterOutputStream(); + + // Create a buffer to read data + // getBufferSize() returns the optimal buffer size + char[] charbuffer = new char[clob.getBufferSize()]; + + // Read from the CLOB and write into the temporary CLOB + while ((bytesread = clobReader.read(charbuffer)) != -1) + { + tempClobWriter.write(charbuffer, 0, bytesread); + } + + // Flush and close the streams + tempClobWriter.flush(); + tempClobWriter.close(); + clobReader.close(); + + // Close the CLOBs + clob.close(); + tempClob.close(); + } + catch (Exception e) + { + logger.error("Error during temporary CLOB write", e); + freeTempLOB(tempClob, null); + } return tempClob; } public static String convertCLOBtoString(Connection connection, Object nativeclob) { - String retval = null; ClobWrapper temp = new ClobWrapper(); temp.setClob(nativeclob); /** @@ -130,139 +137,252 @@ */ ClobWrapper clob = createTempCLOB(connection, temp); - if (clob != null) - { - // Buffer to hold the CLOB data - StringBuffer clobdata = new StringBuffer(); - // No of bytes read each trip to database - int bytesread = 0; - try - { - // Open the CLOB in readonly mode - clob.open(ClobWrapper.MODE_READONLY); - // Open the stream to read data - Reader clobReader = clob.getCharacterStream(); - // Buffer size is fixed using the getBufferSize() method which returns - // the optimal buffer size to read data from the LOB - char[] charbuffer = new char[clob.getBufferSize()]; - // Keep reading from the CLOB and append it to the stringbuffer till - // there is no more to read - while ((bytesread = clobReader.read(charbuffer)) != -1) - { - clobdata.append(charbuffer, 0, bytesread); - } - // Close the input stream - clobReader.close(); - // Close the CLOB - clob.close(); - retval = clobdata.toString(); - clobdata = null; - } - catch (Exception ex) - { - // report - } - } + if (clob == null) { + return null; + } + + String retval = null; + // Buffer to hold the CLOB data + StringBuffer clobdata = new StringBuffer(); + // No of bytes read each trip to database + int bytesread = 0; + try + { + // Open the CLOB in readonly mode + clob.open(ClobWrapper.getModeReadOnlyValue()); + // Open the stream to read data + Reader clobReader = clob.getCharacterStream(); + // Buffer size is fixed using the getBufferSize() method which returns + // the optimal buffer size to read data from the LOB + char[] charbuffer = new char[clob.getBufferSize()]; + // Keep reading from the CLOB and append it to the stringbuffer till + // there is no more to read + while ((bytesread = clobReader.read(charbuffer)) != -1) + { + clobdata.append(charbuffer, 0, bytesread); + } + // Close the input stream + clobReader.close(); + // Close the CLOB + clob.close(); + retval = clobdata.toString(); + clobdata = null; + } + catch (Exception e) + { + logger.error("Error during CLOB read", e); + freeTempLOB(clob, null); + } return retval; } public static Object createCLOBFromString(Connection conn, String clobData) { - ClobWrapper clob = new ClobWrapper(); - if (clobData != null) - { - try - { - clob = ClobWrapper.createTemporary(conn, true, ClobWrapper.DURATION_SESSION); - - // Open the temporary CLOB in readwrite mode to enable writing - clob.open(ClobWrapper.MODE_READWRITE); - - // Clear the previous contents of the CLOB - clob.trim(0); - - // Get the output stream to write - Writer tempClobWriter = clob.getCharacterOutputStream(); - - // Write the data into the temporary CLOB - tempClobWriter.write(clobData); - - // Flush and close the stream - tempClobWriter.flush(); - tempClobWriter.close(); - - // Close the temporary CLOB - clob.close(); - } - catch (Exception ex) - { - // Since an error has been caught, free the temporary LOBs - freeTempLOB(clob, null); - } - } - return clob.getClob(); + if (clobData == null) { + return null; + } + ClobWrapper clob = null; + try + { + clob = ClobWrapper.createTemporary(conn, true, ClobWrapper.getDurationSessionValue()); + if (clob != null) { + // Open the temporary CLOB in readwrite mode to enable writing + clob.open(ClobWrapper.getModeReadWriteValue()); + + // Clear the previous contents of the CLOB + clob.trim(0); + + // Get the output stream to write + Writer tempClobWriter = clob.getCharacterOutputStream(); + + if (tempClobWriter != null) { + // Write the data into the temporary CLOB + tempClobWriter.write(clobData); + + // Flush and close the stream + tempClobWriter.flush(); + tempClobWriter.close(); + } + + // Close the temporary CLOB + clob.close(); + } + } + catch (InvocationTargetException ite) { + Throwable t = ite.getTargetException(); + freeTempLOB(clob, null); + if (t instanceof java.lang.UnsatisfiedLinkError) { + logger.error("Oracle JDBC-driver version does not match installed OCI-driver"); + } else { + logger.error("Error during temporary CLOB write", t); + } + } + catch (Exception e) + { + logger.error("Error during temporary CLOB write", e); + freeTempLOB(clob, null); + } + return clob == null ? null : clob.getClob(); } - /** - * Updates the content of the temporary BLOB with the new data in the file. - **/ - void createBLOBFromFile(File file) - { - if (file != null) - { - try - { - // If temporary BLOB has not yet been created, create new - if (m_tempBlob == null) - { - BlobWrapper temp = new BlobWrapper(); - m_tempBlob = temp.createTemporary(m_connection, true, - BlobWrapper.DURATION_SESSION); - } - - // Open the temporary BLOB in readwrite mode to enable writing - m_tempBlob.open(BlobWrapper.MODE_READWRITE); - - // Clear the contents of the temporary BLOB - m_tempBlob.trim(0); - - // Get the input stream to read from the file - FileInputStream fileIStream = new FileInputStream(file); - - // Get the output stream to write into the temporary BLOB - OutputStream tempBlobOStream = m_tempBlob.getBinaryOutputStream(); - - // Get the optimal buffer size to read bytes - byte[] buffer = new byte[m_tempBlob.getBufferSize()]; - - // No of bytes read in each trip to database - int bytesread = 0; - - // Read from the file and write to the temporary BLOB - while ((bytesread = fileIStream.read(buffer)) != -1) - tempBlobOStream.write(buffer, 0, bytesread); - - // Flush and close the streams - tempBlobOStream.flush(); - fileIStream.close(); - tempBlobOStream.close(); - - // Close the temporary BLOB - m_tempBlob.close(); - } - catch (Exception ex) - { - // Since an error has been caught, free the temporary LOBs - freeTempLOB(null, m_tempBlob); - } - } - } + private static BlobWrapper createTempBLOB(Connection connection, BlobWrapper blob) + { + if (blob == null) { + return null; + } + + BlobWrapper tempBlob = null; + try + { + // Create a temporary BLOB with duration session + tempBlob = BlobWrapper.createTemporary(connection, true, BlobWrapper.getDurationSessionValue()); + + // Open the CLOB in readonly mode + blob.open(BlobWrapper.getModeReadOnlyValue()); + + // Open the temporary CLOB in readwrite mode to enable writing + tempBlob.open(BlobWrapper.getModeReadWriteValue()); + + // No of bytes read each trip to database + int bytesread; + + // Get the input stream for reading from the BLOB + InputStream blobInputStream = blob.getBinaryStream(); + + // Get the output stream for writing into the BLOB + OutputStream tempBlobOutputStream = tempBlob.getBinaryOutputStream(); + + // Create a buffer to read data + // getBufferSize() returns the optimal buffer size + byte[] bytebuffer = new byte[blob.getBufferSize()]; + + // Read from the BLOB and write into the temporary BLOB + while ((bytesread = blobInputStream.read(bytebuffer)) != -1) + { + tempBlobOutputStream.write(bytebuffer, 0, bytesread); + } + + // Flush and close the streams + tempBlobOutputStream.flush(); + tempBlobOutputStream.close(); + blobInputStream.close(); + + // Close the BLOBs + blob.close(); + tempBlob.close(); + } + catch (Exception e) + { + logger.error("Error during temporary BLOB write", e); + freeTempLOB(null, tempBlob); + } + return tempBlob; + } + + public static byte[] convertBLOBtoByteArray(Connection connection, Object nativeblob) + { + BlobWrapper temp = new BlobWrapper(); + temp.setBlob(nativeblob); + /** + * first, convert the blob to another blob. Thanks Oracle, you rule. + */ + BlobWrapper blob = createTempBLOB(connection, temp); + if (blob == null) { + return null; + } + + byte[] retval = null; + // Buffer to hold the BLOB data + ByteArrayOutputStream blobdata = new ByteArrayOutputStream(); + // No of bytes read each trip to database + int bytesread = 0; + try + { + // Open the BLOB in readonly mode + blob.open(BlobWrapper.getModeReadOnlyValue()); + // Open the stream to read data + InputStream blobInputStream = blob.getBinaryStream(); + // Buffer size is fixed using the getBufferSize() method which returns + // the optimal buffer size to read data from the LOB + byte[] bytebuffer = new byte[blob.getBufferSize()]; + // Keep reading from the BLOB and append it to the bytebuffer till + // there is no more to read + while ((bytesread = blobInputStream.read(bytebuffer)) != -1) + { + blobdata.write(bytebuffer, 0, bytesread); + } + // Close the input and output streams stream + blobInputStream.close(); + blobdata.flush(); + blobdata.close(); + + // Close the BLOB + blob.close(); + retval = blobdata.toByteArray(); + blobdata = null; + } + catch (Exception e) + { + logger.error("Error during BLOB read", e); + freeTempLOB(null, blob); + } + return retval; + } + + public static Object createBLOBFromByteArray(Connection conn, byte[] blobData) + { + if (blobData == null) { + return null; + } + + BlobWrapper blob = null; + try + { + blob = BlobWrapper.createTemporary(conn, true, BlobWrapper.getDurationSessionValue()); + + // Open the temporary BLOB in readwrite mode to enable writing + blob.open(BlobWrapper.getModeReadWriteValue()); + + // Clear the previous contents of the BLOB + blob.trim(0); + + // Get the output stream to write + OutputStream tempBlobOutputStream = blob.getBinaryOutputStream(); + + // Write the data into the temporary BLOB + tempBlobOutputStream.write(blobData); + + // Flush and close the stream + tempBlobOutputStream.flush(); + tempBlobOutputStream.close(); + + // Close the temporary BLOB + blob.close(); + } + catch (InvocationTargetException ite) { + Throwable t = ite.getTargetException(); + freeTempLOB(null, blob); + if (t instanceof java.lang.UnsatisfiedLinkError) { + logger.error("Oracle JDBC-driver version does not match installed OCI-driver"); + } else { + logger.error("Error during temporary BLOB write", t); + } + } + catch (Exception e) + { + logger.error("Error during temporary BLOB write", e); + freeTempLOB(null, blob); + } + return blob == null ? null : blob.getBlob(); + } /** - * Frees the temporary LOBs when an exception is raised in the application - * or when the LOBs are no longer needed. If the LOBs are not freed , the - * space used by these LOBs are not reclaimed. - **/ + * Frees the temporary LOBs when an exception is raised in the application + * or when the LOBs are no longer needed. If the LOBs are not freed, the + * space used by these LOBs are not reclaimed. + * @param clob CLOB-wrapper to free or null + * @param blob BLOB-wrapper to free or null + */ private static void freeTempLOB(ClobWrapper clob, BlobWrapper blob) { try @@ -277,7 +397,6 @@ // Free the memory used by this CLOB clob.freeTemporary(); - clob = null; } if (blob != null) @@ -290,11 +409,12 @@ // Free the memory used by this BLOB blob.freeTemporary(); - blob = null; } } - catch (Exception ex) + catch (Exception e) { + logger.error("Error during temporary LOB release", e); } } + } 1.2 +237 -60 db-ojb/src/java/org/apache/ojb/broker/platforms/BlobWrapper.java Index: BlobWrapper.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/BlobWrapper.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- BlobWrapper.java 29 Jul 2003 16:54:12 -0000 1.1 +++ BlobWrapper.java 24 Jan 2004 00:08:58 -0000 1.2 @@ -54,71 +54,248 @@ * . */ -import java.sql.Connection; -import java.sql.SQLException; +import org.apache.commons.lang.BooleanUtils; +import org.apache.ojb.broker.util.ClassHelper; + import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.SQLException; /** - * Created by IntelliJ IDEA. - * User: matthew.baird - * Date: Jun 26, 2003 - * Time: 3:58:47 PM - * To change this template use Options | File Templates. + * Wraps the Oracle BLOB type and makes it accessible via reflection + * without having to import the Oracle Classes. + * @author Matthew Baird + * @author Erik Forkalsrud + * @author Martin Kalén + * @version CVS $Id$ */ public class BlobWrapper { - public static final int MAX_CHUNK_SIZE = 32768; - public static final int DURATION_SESSION = 10; - public static final int DURATION_CALL = 12; - static final int OLD_WRONG_DURATION_SESSION = 1; - static final int OLD_WRONG_DURATION_CALL = 2; - public static final int MODE_READONLY = 0; - public static final int MODE_READWRITE = 1; - - private Object m_blob; - - public BlobWrapper createTemporary(Connection conn, boolean b, int i) - { - return new BlobWrapper(); - } - - public void open(int i) throws SQLException - { - - } - - public int getBufferSize() throws SQLException - { - return 1; - } - - public void close() throws SQLException - { - } - - public void trim(long l) throws SQLException - { - - } - - public void freeTemporary() throws SQLException - { - - } - - public InputStream getBinaryStream() throws SQLException - { - return null; - } - - public OutputStream getBinaryOutputStream() throws SQLException - { - return null; - } - - public boolean isOpen() throws SQLException - { - return true; - } + protected Object m_blob; + + // Fields - values must be looked up via reflection not be compile-time Oracle-version dependent + protected static Field durationSession; + protected static Field durationCall; + protected static Field modeReadOnly; + protected static Field modeReadWrite; + + // Methods + protected static Method createTemporary; + protected static Method freeTemporary; + protected static Method open; + protected static Method isOpen; + protected static Method getBinaryStream; + protected static Method getBinaryOutputStream; + protected static Method getBufferSize; + protected static Method close; + protected static Method trim; + + /** + * Initialize all methods and fields via reflection. + */ + static + { + try + { + Class blobClass = ClassHelper.getClass("oracle.sql.BLOB", false); + createTemporary = blobClass.getMethod("createTemporary", new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE}); + freeTemporary = blobClass.getMethod("freeTemporary", null); + open = blobClass.getMethod("open", new Class[]{Integer.TYPE}); + isOpen = blobClass.getMethod("isOpen", null); + getBinaryStream = blobClass.getMethod("getBinaryStream", null); + getBinaryOutputStream = blobClass.getMethod("getBinaryOutputStream", null); + getBufferSize = blobClass.getMethod("getBufferSize", null); + close = blobClass.getMethod("close", null); + trim = blobClass.getMethod("trim", new Class[]{Long.TYPE}); + + durationSession = ClassHelper.getField(blobClass, "DURATION_SESSION"); + durationCall = ClassHelper.getField(blobClass, "DURATION_CALL"); + modeReadOnly = ClassHelper.getField(blobClass, "MODE_READONLY"); + modeReadWrite = ClassHelper.getField(blobClass, "MODE_READWRITE"); + } + catch (Exception ingore) + { + } + } + + public Object getBlob() + { + return m_blob; + } + + public void setBlob(Object blob) + { + m_blob = blob; + } + + protected static int staticIntFieldValue(Field field) { + int value = 0; + try { + value = field.getInt(null); + } catch (Exception ignore) { + value = -1; + } + return value; + } + + public static int getDurationSessionValue() { + return staticIntFieldValue(durationSession); + } + + public static int getDurationCallValue() { + return staticIntFieldValue(durationCall); + } + + public static int getModeReadOnlyValue() { + return staticIntFieldValue(modeReadOnly); + } + + public static int getModeReadWriteValue() { + return staticIntFieldValue(modeReadWrite); + } + + public static BlobWrapper createTemporary(Connection conn, boolean b, int i) throws Exception + { + BlobWrapper retval = new BlobWrapper(); + // Passing null to invoke static method + retval.setBlob(createTemporary.invoke(null, new Object[]{conn, BooleanUtils.toBooleanObject(b), new Integer(i)})); + return retval; + } + + public void open(int i) throws SQLException + { + if (m_blob == null) { + return; + } + try + { + open.invoke(m_blob, new Object[]{new Integer(i)}); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + } + + public boolean isOpen() throws SQLException + { + if (m_blob == null) { + return false; + } + boolean open = false; + try + { + Boolean retval = (Boolean) isOpen.invoke(m_blob, null); + if (retval != null) { + open = retval.booleanValue(); + } + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + return open; + } + + public InputStream getBinaryStream() throws SQLException + { + if (m_blob == null) { + return null; + } + InputStream retval = null; + try + { + retval = (InputStream) getBinaryStream.invoke(m_blob, null); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + return retval; + } + + public OutputStream getBinaryOutputStream() throws SQLException + { + if (m_blob == null) { + return null; + } + OutputStream retval = null; + try + { + retval = (OutputStream) getBinaryOutputStream.invoke(m_blob, null); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + return retval; + } + + public int getBufferSize() throws SQLException + { + if (m_blob == null) { + return 0; + } + Integer retval = null; + try + { + retval = (Integer) getBufferSize.invoke(m_blob, null); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + return retval.intValue(); + } + + public void close() throws SQLException + { + if (m_blob == null) { + return; + } + try + { + close.invoke(m_blob, null); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + + } + + public void trim(long l) throws SQLException + { + if (m_blob == null) { + return; + } + try + { + trim.invoke(m_blob, new Object[]{new Long(l)}); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + + } + + public void freeTemporary() throws SQLException + { + if (m_blob == null) { + return; + } + try + { + freeTemporary.invoke(m_blob, null); + } + catch (Throwable e) + { + throw new SQLException(e.getMessage()); + } + } + } 1.9 +283 -399 db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformOracle9iImpl.java Index: PlatformOracle9iImpl.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/PlatformOracle9iImpl.java,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- PlatformOracle9iImpl.java 8 Dec 2003 10:18:16 -0000 1.8 +++ PlatformOracle9iImpl.java 24 Jan 2004 00:08:58 -0000 1.9 @@ -54,22 +54,25 @@ * . */ +import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor; +import org.apache.ojb.broker.metadata.JdbcType; +import org.apache.ojb.broker.util.ClassHelper; +import org.apache.ojb.broker.util.JdbcTypesHelper; + import java.io.ByteArrayInputStream; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.sql.Types; - -import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; /** - * This class is a concrete implementation of Platform. Provides - * an implementation that works around some issues with Oracle in general and - * Oracle 9i's Thin driver in particular. + * This class is a concrete implementation of Platform. Provides + * an implementation that works around some issues with Oracle in general and + * Oracle 9i's Thin driver in particular. * * Optimization: Oracle Batching (not standard JDBC batching) * see http://technet.oracle.com/products/oracle9i/daily/jun07.html @@ -81,400 +84,281 @@ * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/jdbc30/StmtCacheSample/Readme.html * * TODO: Optimization: use ROWNUM to minimize the effects of not having server side cursors - * * see http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064 * - * @author Matthew Baird + * @author Matthew Baird + * @author Erik Forkalsrud + * @author Martin Kalén + * @version CVS $Id$ + * @see Platform + * @see PlatformDefaultImpl + * @see PlatformOracleImpl */ - public class PlatformOracle9iImpl extends PlatformOracleImpl { - private static final Object[] BATCH_SIZE = {new Integer(10)}; - private static final Class[] PARAM_TYPE_INTEGER = {Integer.TYPE}; - private static final Class[] PARAM_TYPE_BOOLEAN = {Boolean.TYPE}; - private static boolean STATEMENT_CACHING_SUPPORTED = true; - private static boolean SET_EXECUTE_BATCH_METHOD_EXISTS = true; - private static boolean SEND_BATCH_METHOD_EXISTS = true; - private static Method SET_EXECUTE_BATCH = null; - private static Method SEND_BATCH = null; - private static Method SET_STATEMENT_CACHING_ENABLE = null; - private static Method SET_IMPLICIT_CACHING_ENABLED = null; - private static Method SET_CLOB = null; - private static Method SET_BLOB = null; - private static boolean SET_CLOB_AND_LOB_SUPPORTED = false; - private static Method GET_CLOB = null; - private static boolean GET_CLOB_AND_LOB_SUPPORTED = false; - - private static boolean ROW_PREFETCH_SUPPORTED = true; - private static Method SET_ROW_PREFETCH = null; - private static final int STATEMENT_CACHE_SIZE = 100; - private static final int ROW_PREFETCH_SIZE = 100; - - // Session time zone has to be set while accessing Timestamp with - // Time Zone and Timestamp with Local TimeZone datatypes. - // OracleConnection has the APIs to set the session time zone. - // Sets the session time zone to the time zone specified in the java - // virtual machine. - //((OracleConnection) m_connection).setSessionTimeZone(TimeZone.getDefault().getID()); - - - /** - * enable oracle statement caching - * - * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/jdbc30/StmtCacheSample/Readme.html - * - * @param jcd - * @param conn - * @throws PlatformException - */ - public void initializeJdbcConnection(JdbcConnectionDescriptor jcd, Connection conn) throws PlatformException - { - /** - * do all the generic initialization first - */ - super.initializeJdbcConnection(jcd, conn); - - if (SET_STATEMENT_CACHING_ENABLE == null && SET_IMPLICIT_CACHING_ENABLED == null && STATEMENT_CACHING_SUPPORTED) - { - try - { - SET_STATEMENT_CACHING_ENABLE = conn.getClass().getMethod("setStatementCacheSize", PARAM_TYPE_INTEGER); - SET_IMPLICIT_CACHING_ENABLED = conn.getClass().getMethod("setImplicitCachingEnabled", PARAM_TYPE_BOOLEAN); - } - catch (NoSuchMethodException e) - { - STATEMENT_CACHING_SUPPORTED = false; - } - catch (SecurityException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - } - if (STATEMENT_CACHING_SUPPORTED) - { - try - { - SET_STATEMENT_CACHING_ENABLE.invoke(conn, new Object[]{new Integer(STATEMENT_CACHE_SIZE)}); // use a 100 item cache - SET_IMPLICIT_CACHING_ENABLED.invoke(conn, new Object[]{Boolean.TRUE}); // use implicit caching - } - catch (IllegalAccessException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - catch (IllegalArgumentException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - catch (InvocationTargetException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - } - } - - /** - * - * doesn't seem to improve performance. - * - * support for oracle prefetching - * - * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html - * - * @param stmt - * @throws PlatformException - */ - public void XXXXafterStatementCreate(Statement stmt) throws PlatformException - { - super.afterStatementCreate(stmt); - if (ROW_PREFETCH_SUPPORTED && SET_ROW_PREFETCH == null) - { - try - { - SET_ROW_PREFETCH = stmt.getClass().getMethod("setRowPrefetch", PARAM_TYPE_INTEGER); - } - catch (NoSuchMethodException e) - { - ROW_PREFETCH_SUPPORTED = false; - } - catch (SecurityException e) - { - ROW_PREFETCH_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - } - if (ROW_PREFETCH_SUPPORTED) - { - try - { - SET_ROW_PREFETCH.invoke(stmt, new Object[]{new Integer(ROW_PREFETCH_SIZE)}); // use a 100 item cache - } - catch (IllegalAccessException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - catch (IllegalArgumentException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - catch (InvocationTargetException e) - { - STATEMENT_CACHING_SUPPORTED = false; - throw new PlatformException(e.getMessage(), e); - } - } - } - - /** - * don't bind to the Oracle drivers and thus have to cast, rather do the - * reflection to find out if we can use the setExecuteBatch method with this - * particular driver, and degrade to default JDBC batching if necessary - * @param stmt - * @throws PlatformException - */ - public void beforeBatch(PreparedStatement stmt) throws PlatformException - { - // ((OraclePreparedStatement)ps).setExecuteBatch (3); - if (SET_EXECUTE_BATCH_METHOD_EXISTS && SEND_BATCH_METHOD_EXISTS) - { - try - { - if (SET_EXECUTE_BATCH == null) - { - SET_EXECUTE_BATCH = stmt.getClass().getMethod("setExecuteBatch", PARAM_TYPE_INTEGER); - } - SET_EXECUTE_BATCH.invoke(stmt, BATCH_SIZE); - } - catch (IllegalAccessException e) - { - SET_EXECUTE_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (IllegalArgumentException e) - { - SET_EXECUTE_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (InvocationTargetException e) - { - SET_EXECUTE_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (NoSuchMethodException e) - { - SET_EXECUTE_BATCH_METHOD_EXISTS = false; - } - } - if (!SET_EXECUTE_BATCH_METHOD_EXISTS) - { - super.beforeBatch(stmt); - } - } - - /** - * in oracle batching we call executeUpdate instead of addBatch - * @param stmt - * @throws PlatformException - */ - public void addBatch(PreparedStatement stmt) throws PlatformException - { - // ps.executeUpdate(); - if (SET_EXECUTE_BATCH_METHOD_EXISTS && SEND_BATCH_METHOD_EXISTS) - { - try - { - stmt.executeUpdate(); - } - catch (SQLException e) - { - throw new PlatformException(e.getMessage(), e); - } - } - else - { - super.addBatch(stmt); - } - } - - /** - * equivelent of calling ((OraclePreparedStatement)ps).sendBatch() but we do it reflectively so - * we don't have to bind to the Oracle classes - * JDBC sends the queued request - * @param stmt the statement you want to execute sendBatch on. - * @return an int array of the success statuses. - * @throws PlatformException - */ - public int[] executeBatch(PreparedStatement stmt) throws PlatformException - { - int[] retval = null; - if (SEND_BATCH_METHOD_EXISTS) - { - try - { - if (SEND_BATCH == null) - { - SEND_BATCH = stmt.getClass().getMethod("sendBatch", null); - } - int numberOfItemsSubmitted = ((Integer) SEND_BATCH.invoke(stmt, null)).intValue(); - retval = new int[numberOfItemsSubmitted]; - for (int i = 0; i < numberOfItemsSubmitted; i++) - { - retval[i] = 1; - } - } - catch (IllegalAccessException e) - { - SEND_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (IllegalArgumentException e) - { - SEND_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (InvocationTargetException e) - { - SEND_BATCH_METHOD_EXISTS = false; - throw new PlatformException(e.getMessage(), e); - } - catch (NoSuchMethodException e) - { - SEND_BATCH_METHOD_EXISTS = false; - } - } - if (!SEND_BATCH_METHOD_EXISTS) - { - retval = super.executeBatch(stmt); - } - return retval; - } - - public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType) throws SQLException - { - if (SET_CLOB == null && SET_CLOB_AND_LOB_SUPPORTED) - { - try - { - SET_CLOB = ps.getClass().getMethod("setCLOB", new Class[]{Integer.TYPE, Class.forName("oracle.sql.CLOB")}); - SET_BLOB = ps.getClass().getMethod("setBLOB", new Class[]{Integer.TYPE, Class.forName("oracle.sql.BLOB")}); - SET_CLOB_AND_LOB_SUPPORTED = true; - } - catch (Throwable t) - { - SET_CLOB_AND_LOB_SUPPORTED = false; - } - } - if (((sqlType == Types.VARBINARY) || (sqlType == Types.LONGVARBINARY)) && (value instanceof byte[])) - { - byte buf[] = (byte[]) value; - ByteArrayInputStream inputStream = new ByteArrayInputStream(buf); - super.changePreparedStatementResultSetType(ps); - ps.setBinaryStream(index, inputStream, buf.length); - } - else if (value instanceof Double) - { - // workaround for the bug in Oracle thin driver - ps.setDouble(index, ((Double) value).doubleValue()); - } - else if (sqlType == Types.BIGINT && value instanceof Integer) - { - // workaround: Oracle thin driver problem when expecting long - ps.setLong(index, ((Integer) value).intValue()); - } - else if (sqlType == Types.INTEGER && value instanceof Long) - { - ps.setLong(index, ((Long) value).longValue()); - } - else if (sqlType == Types.CLOB && value instanceof String) - { - if (SET_CLOB_AND_LOB_SUPPORTED) - { - try - { - SET_CLOB.invoke(ps, new Object[]{new Integer(index), Oracle9iLobHandler.createCLOBFromString(ps.getConnection(), (String) value)}); - } - catch (IllegalAccessException e) - { - throw new SQLException(e.getMessage()); - } - catch (IllegalArgumentException e) - { - throw new SQLException(e.getMessage()); - } - catch (InvocationTargetException e) - { - throw new SQLException(e.getMessage()); - } - } - } - else if (sqlType == Types.BLOB) - { - if (SET_CLOB_AND_LOB_SUPPORTED) - { - try - { - SET_BLOB.invoke(ps, new Object[]{new Integer(index)}); - } - catch (IllegalAccessException e) - { - throw new SQLException(e.getMessage()); - } - catch (IllegalArgumentException e) - { - throw new SQLException(e.getMessage()); - } - catch (InvocationTargetException e) - { - throw new SQLException(e.getMessage()); - } - } - } - else - { - super.setObjectForStatement(ps, index, value, sqlType); - } - } - - public Object getClob(ResultSet rs, int jdbcType, String columnId) throws SQLException - { - String retval = ""; - if (GET_CLOB == null && GET_CLOB_AND_LOB_SUPPORTED) - { - try - { - GET_CLOB = rs.getClass().getMethod("getCLOB", new Class[]{String.class}); - GET_CLOB_AND_LOB_SUPPORTED = true; - } - catch (Throwable t) - { - GET_CLOB_AND_LOB_SUPPORTED = false; - } - } - if (GET_CLOB_AND_LOB_SUPPORTED) - { - try - { - Object obj = GET_CLOB.invoke(rs, new Object[]{columnId}); - retval = Oracle9iLobHandler.convertCLOBtoString(rs.getStatement().getConnection(), obj); - } - catch (IllegalAccessException e) - { - throw new SQLException(e.getMessage()); - } - catch (IllegalArgumentException e) - { - throw new SQLException(e.getMessage()); - } - catch (InvocationTargetException e) - { - throw new SQLException(e.getMessage()); - } - } - return retval; - } + protected static final int STATEMENT_CACHE_SIZE = 100; + protected static final int ROW_PREFETCH_SIZE = 100; + + // From Oracle9i JDBC Developer's Guide and Reference: + // "Batch values between 5 and 30 tend to be the most effective." + protected static final int STATEMENTS_PER_BATCH = 20; + protected static Map m_batchStatementsInProgress = Collections.synchronizedMap(new WeakHashMap(STATEMENTS_PER_BATCH)); + + protected static final Class[] PARAM_TYPE_INTEGER = {Integer.TYPE}; + protected static final Class[] PARAM_TYPE_BOOLEAN = {Boolean.TYPE}; + protected static final Class[] PARAM_TYPE_STRING = {String.class}; + + protected static final Object[] PARAM_STATEMENT_CACHE_SIZE = new Object[]{new Integer(STATEMENT_CACHE_SIZE)}; + protected static final Object[] PARAM_ROW_PREFETCH_SIZE = new Object[]{new Integer(ROW_PREFETCH_SIZE)}; + protected static final Object[] PARAM_STATEMENT_BATCH_SIZE = new Object[]{new Integer(STATEMENTS_PER_BATCH)}; + protected static final Object[] PARAM_BOOLEAN_TRUE = new Object[]{Boolean.TRUE}; + + protected static final JdbcType BASE_CLOB = JdbcTypesHelper.getJdbcTypeByName("clob"); + protected static final JdbcType BASE_BLOB = JdbcTypesHelper.getJdbcTypeByName("blob"); + + /** + * Enables Oracle statement caching if supported by the JDBC-driver. + * See + * {@link "http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/jdbc30/StmtCacheSample/Readme.html"} + * @param jcd the OJB JdbcConnectionDescriptor (metadata) for the connection to be initialized + * @param conn the Connection-object (physical) to be initialized + * @see PlatformDefaultImpl#initializeJdbcConnection + */ + public void initializeJdbcConnection(JdbcConnectionDescriptor jcd, Connection conn) throws PlatformException + { + // Do all the generic initialization in PlatformDefaultImpl first + super.initializeJdbcConnection(jcd, conn); + + // Check for OracleConnection-specific statement caching methods + final Method methodSetStatementCacheSize; + final Method methodSetImplicitCachingEnabled; + methodSetStatementCacheSize = ClassHelper.getMethod(conn, "setStatementCacheSize", PARAM_TYPE_INTEGER); + methodSetImplicitCachingEnabled = ClassHelper.getMethod(conn, "setImplicitCachingEnabled", PARAM_TYPE_BOOLEAN); + + final boolean statementCachingSupported = methodSetStatementCacheSize != null && methodSetImplicitCachingEnabled != null; + if (statementCachingSupported) + { + try + { + // Set number of cached statements and enable implicit caching + methodSetStatementCacheSize.invoke(conn, PARAM_STATEMENT_CACHE_SIZE); + methodSetImplicitCachingEnabled.invoke(conn, PARAM_BOOLEAN_TRUE); + } + catch (Exception e) + { + throw new PlatformException(e.getLocalizedMessage(), e); + } + } + } + + /** + * Enables Oracle row prefetching if supported. + * See http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html. + * This is RDBMS server-to-client prefetching and thus one layer below + * the OJB-internal prefetching-to-cache introduced in version 1.0rc5. + * @param stmt the statement just created + * @throws PlatformException upon JDBC failure + */ + public void afterStatementCreate(java.sql.Statement stmt) throws PlatformException + { + super.afterStatementCreate(stmt); + + // Check for OracleStatement-specific row prefetching support + final Method methodSetRowPrefetch; + methodSetRowPrefetch = ClassHelper.getMethod(stmt, "setRowPrefetch", PARAM_TYPE_INTEGER); + + final boolean rowPrefetchingSupported = methodSetRowPrefetch != null; + if (rowPrefetchingSupported) + { + try + { + // Set number of prefetched rows + methodSetRowPrefetch.invoke(stmt, PARAM_ROW_PREFETCH_SIZE); + } + catch (Exception e) + { + throw new PlatformException(e.getLocalizedMessage(), e); + } + } + } + + /** + * Try Oracle update batching and call setExecuteBatch or revert to + * JDBC update batching. See 12-2 Update Batching in the Oracle9i + * JDBC Developer's Guide and Reference. + * @param stmt the prepared statement to be used for batching + * @throws PlatformException upon JDBC failure + */ + public void beforeBatch(PreparedStatement stmt) throws PlatformException + { + // Check for Oracle batching support + final Method methodSetExecuteBatch; + final Method methodSendBatch; + methodSetExecuteBatch = ClassHelper.getMethod(stmt, "setExecuteBatch", PARAM_TYPE_INTEGER); + methodSendBatch = ClassHelper.getMethod(stmt, "sendBatch", null); + + final boolean statementBatchingSupported = methodSetExecuteBatch != null && methodSendBatch != null; + if (statementBatchingSupported) + { + try + { + // Set number of statements per batch + methodSetExecuteBatch.invoke(stmt, PARAM_STATEMENT_BATCH_SIZE); + m_batchStatementsInProgress.put(stmt, methodSendBatch); + } + catch (Exception e) + { + throw new PlatformException(e.getLocalizedMessage(), e); + } + } + else + { + super.beforeBatch(stmt); + } + } + + /** + * Try Oracle update batching and call executeUpdate or revert to + * JDBC update batching. + * @param stmt the statement beeing added to the batch + * @throws PlatformException upon JDBC failure + */ + public void addBatch(PreparedStatement stmt) throws PlatformException + { + // Check for Oracle batching support + final boolean statementBatchingSupported = m_batchStatementsInProgress.containsKey(stmt); + if (statementBatchingSupported) + { + try + { + stmt.executeUpdate(); + } + catch (SQLException e) + { + throw new PlatformException(e.getLocalizedMessage(), e); + } + } + else + { + super.addBatch(stmt); + } + } + + /** + * Try Oracle update batching and call sendBatch or revert to + * JDBC update batching. + * @param stmt the batched prepared statement about to be executed + * @return always null if Oracle update batching is used, + * since it is impossible to dissolve total row count into distinct + * statement counts. If JDBC update batching is used, an int array is + * returned containing number of updated rows for each batched statement. + * @throws PlatformException upon JDBC failure + */ + public int[] executeBatch(PreparedStatement stmt) throws PlatformException + { + // Check for Oracle batching support + final Method methodSendBatch = (Method) m_batchStatementsInProgress.remove(stmt); + final boolean statementBatchingSupported = methodSendBatch != null; + + int[] retval = null; + if (statementBatchingSupported) + { + try + { + // sendBatch() returns total row count as an Integer + methodSendBatch.invoke(stmt, null); + } + catch (Exception e) + { + throw new PlatformException(e.getLocalizedMessage(), e); + } + } + else + { + retval = super.executeBatch(stmt); + } + return retval; + } + + /** @see Platform#setObjectForStatement */ + public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType) throws SQLException + { + boolean blobHandlingSupported = false; + boolean clobHandlingSupported = false; + Method methodSetBlob = null; + Method methodSetClob = null; + + // Check for Oracle JDBC-driver LOB-support + if (sqlType == Types.CLOB) { + try { + Class clobClass = ClassHelper.getClass("oracle.sql.CLOB", false); + methodSetClob = ClassHelper.getMethod(ps, "setCLOB", new Class[]{Integer.TYPE, clobClass}); + clobHandlingSupported = methodSetClob != null; + } catch (Exception ignore) { + } + } + else if (sqlType == Types.BLOB) { + try { + Class blobClass = ClassHelper.getClass("oracle.sql.BLOB", false); + methodSetBlob = ClassHelper.getMethod(ps, "setBLOB", new Class[]{Integer.TYPE, blobClass}); + blobHandlingSupported = methodSetBlob != null; + } catch (Exception ignore) { + } + } + + // Type-specific Oracle conversions + if (((sqlType == Types.VARBINARY) || (sqlType == Types.LONGVARBINARY)) && (value instanceof byte[])) + { + byte buf[] = (byte[]) value; + ByteArrayInputStream inputStream = new ByteArrayInputStream(buf); + super.changePreparedStatementResultSetType(ps); + ps.setBinaryStream(index, inputStream, buf.length); + } + else if (value instanceof Double) + { + // workaround for the bug in Oracle thin driver + ps.setDouble(index, ((Double) value).doubleValue()); + } + else if (sqlType == Types.BIGINT && value instanceof Integer) + { + // workaround: Oracle thin driver problem when expecting long + ps.setLong(index, ((Integer) value).intValue()); + } + else if (sqlType == Types.INTEGER && value instanceof Long) + { + ps.setLong(index, ((Long) value).longValue()); + } + else if (sqlType == Types.CLOB && clobHandlingSupported && value instanceof String) + { + // TODO: If using Oracle update batching with the thin driver, throw exception on 4k limit + try + { + Object clob = Oracle9iLobHandler.createCLOBFromString(ps.getConnection(), (String) value); + methodSetClob.invoke(ps, new Object[]{new Integer(index), clob}); + } + catch (Exception e) + { + throw new SQLException(e.getLocalizedMessage()); + } + } + else if (sqlType == Types.BLOB && blobHandlingSupported && value instanceof byte[]) + { + // TODO: If using Oracle update batching with the thin driver, throw exception on 2k limit + try + { + Object blob = Oracle9iLobHandler.createBLOBFromByteArray(ps.getConnection(), (byte[]) value); + methodSetBlob.invoke(ps, new Object[]{new Integer(index), blob}); + } + catch (Exception e) + { + throw new SQLException(e.getLocalizedMessage()); + } + } + else + { + // Fall-through to superclass + super.setObjectForStatement(ps, index, value, sqlType); + } + } + } 1.20 +1 -7 db-ojb/src/java/org/apache/ojb/broker/platforms/Platform.java Index: Platform.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/Platform.java,v retrieving revision 1.19 retrieving revision 1.20 diff -u -r1.19 -r1.20 --- Platform.java 23 Nov 2003 10:29:23 -0000 1.19 +++ Platform.java 24 Jan 2004 00:08:58 -0000 1.20 @@ -214,11 +214,6 @@ */ public String getLastInsertIdentityQuery(String tableName); - /** - * Oracle has funky clobs. - */ - public Object getClob(ResultSet rs, int jdbcType, String columnId) throws SQLException; - /** * Answer true if LIMIT or equivalent is supported * SQL-Paging is not yet supported @@ -229,7 +224,6 @@ /** * Add the LIMIT or equivalent to the SQL * SQL-Paging is not yet supported - * @return */ public void addPagingSql(StringBuffer anSqlString); 1.5 +96 -66 db-ojb/src/java/org/apache/ojb/broker/platforms/ClobWrapper.java Index: ClobWrapper.java =================================================================== RCS file: /home/cvs//db-ojb/src/java/org/apache/ojb/broker/platforms/ClobWrapper.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- ClobWrapper.java 8 Dec 2003 10:18:16 -0000 1.4 +++ ClobWrapper.java 24 Jan 2004 00:08:58 -0000 1.5 @@ -54,55 +54,54 @@ * . */ +import org.apache.commons.lang.BooleanUtils; +import org.apache.ojb.broker.util.ClassHelper; + import java.io.Reader; import java.io.Writer; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Field; import java.sql.Connection; import java.sql.SQLException; -import org.apache.commons.lang.BooleanUtils; -import org.apache.ojb.broker.util.logging.LoggerFactory; - /** - * Wraps the Oracle CLOB type and makes it accessible via reflection without having to import the Oracle Classes. - * @author Matthew Baird + * Wraps the Oracle CLOB type and makes it accessible via reflection + * without having to import the Oracle Classes. + * @author Matthew Baird + * @author Martin Kalén + * @version CVS $Id$ */ - public class ClobWrapper { - public static final int MAX_CHUNK_SIZE = 32768; - public static final int DURATION_SESSION = 10; - public static final int DURATION_CALL = 12; - static final int OLD_WRONG_DURATION_SESSION = 1; - static final int OLD_WRONG_DURATION_CALL = 2; - public static final int MODE_READONLY = 0; - public static final int MODE_READWRITE = 1; - private Object m_clob = null; - - // methods - private static Method createTemporary = null; - private static Method open = null; - private static Method isOpen = null; - private static Method getCharacterStream = null; - private static Method getCharacterOutputStream = null; - private static Method getBufferSize = null; - private static Method close = null; - private static Method trim = null; - private static Method freeTemporary = null; + protected Object m_clob; + + // Fields - values must be looked up via reflection not be compile-time Oracle-version dependent + protected static Field durationSession; + protected static Field durationCall; + protected static Field modeReadOnly; + protected static Field modeReadWrite; + + // Methods + protected static Method createTemporary; + protected static Method freeTemporary; + protected static Method open; + protected static Method isOpen; + protected static Method getCharacterStream; + protected static Method getCharacterOutputStream; + protected static Method getBufferSize; + protected static Method close; + protected static Method trim; /** - * initialize all the methods + * Initialize all methods and fields via reflection. */ static { try { - /** - * try to find it - */ - Class clobClass = Class.forName("oracle.sql.CLOB"); - createTemporary = clobClass.getDeclaredMethod("createTemporary", new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE}); + Class clobClass = ClassHelper.getClass("oracle.sql.CLOB", false); + createTemporary = clobClass.getMethod("createTemporary", new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE}); + freeTemporary = clobClass.getMethod("freeTemporary", null); open = clobClass.getMethod("open", new Class[]{Integer.TYPE}); isOpen = clobClass.getMethod("isOpen", null); getCharacterStream = clobClass.getMethod("getCharacterStream", null); @@ -110,19 +109,14 @@ getBufferSize = clobClass.getMethod("getBufferSize", null); close = clobClass.getMethod("close", null); trim = clobClass.getMethod("trim", new Class[]{Long.TYPE}); - freeTemporary = clobClass.getMethod("freeTemporary", null); - } - catch (NoSuchMethodException e) - { - System.out.println(e.getMessage()); - } - catch (SecurityException e) - { - System.out.println(e.getMessage()); + + durationSession = ClassHelper.getField(clobClass, "DURATION_SESSION"); + durationCall = ClassHelper.getField(clobClass, "DURATION_CALL"); + modeReadOnly = ClassHelper.getField(clobClass, "MODE_READONLY"); + modeReadWrite = ClassHelper.getField(clobClass, "MODE_READWRITE"); } - catch (ClassNotFoundException e) + catch (Exception ingore) { - System.out.println(e.getMessage()); } } @@ -136,33 +130,45 @@ m_clob = clob; } - public static ClobWrapper createTemporary(Connection conn, boolean b, int i) + protected static int staticIntFieldValue(Field field) { + int value = 0; + try { + value = field.getInt(null); + } catch (Exception ignore) { + value = -1; + } + return value; + } + + public static int getDurationSessionValue() { + return staticIntFieldValue(durationSession); + } + + public static int getDurationCallValue() { + return staticIntFieldValue(durationCall); + } + + public static int getModeReadOnlyValue() { + return staticIntFieldValue(modeReadOnly); + } + + public static int getModeReadWriteValue() { + return staticIntFieldValue(modeReadWrite); + } + + public static ClobWrapper createTemporary(Connection conn, boolean b, int i) throws Exception { ClobWrapper retval = new ClobWrapper(); - try - { - /** - * passing null to the invoke means this is a static method. - */ - retval.m_clob = createTemporary.invoke(null, new Object[]{conn, BooleanUtils.toBooleanObject(b), new Integer(i)}); - } - catch (IllegalAccessException e) - { - LoggerFactory.getDefaultLogger().error(e); - } - catch (IllegalArgumentException e) - { - LoggerFactory.getDefaultLogger().error(e); - } - catch (InvocationTargetException e) - { - LoggerFactory.getDefaultLogger().error(e); - } + // Passing null to invoke static method + retval.setClob(createTemporary.invoke(null, new Object[]{conn, BooleanUtils.toBooleanObject(b), new Integer(i)})); return retval; } public void open(int i) throws SQLException { + if (m_clob == null) { + return; + } try { open.invoke(m_clob, new Object[]{new Integer(i)}); @@ -175,20 +181,29 @@ public boolean isOpen() throws SQLException { - Boolean retval = null; + if (m_clob == null) { + return false; + } + boolean open = false; try { - retval = (Boolean) isOpen.invoke(m_clob, null); + Boolean retval = (Boolean) isOpen.invoke(m_clob, null); + if (retval != null) { + open = retval.booleanValue(); + } } catch (Throwable e) { throw new SQLException(e.getMessage()); } - return retval.booleanValue(); + return open; } public Reader getCharacterStream() throws SQLException { + if (m_clob == null) { + return null; + } Reader retval = null; try { @@ -203,6 +218,9 @@ public Writer getCharacterOutputStream() throws SQLException { + if (m_clob == null) { + return null; + } Writer retval = null; try { @@ -217,6 +235,9 @@ public int getBufferSize() throws SQLException { + if (m_clob == null) { + return 0; + } Integer retval = null; try { @@ -231,6 +252,9 @@ public void close() throws SQLException { + if (m_clob == null) { + return; + } try { close.invoke(m_clob, null); @@ -244,6 +268,9 @@ public void trim(long l) throws SQLException { + if (m_clob == null) { + return; + } try { trim.invoke(m_clob, new Object[]{new Long(l)}); @@ -257,6 +284,9 @@ public void freeTemporary() throws SQLException { + if (m_clob == null) { + return; + } try { freeTemporary.invoke(m_clob, null); --------------------------------------------------------------------- To unsubscribe, e-mail: ojb-dev-unsubscribe@db.apache.org For additional commands, e-mail: ojb-dev-help@db.apache.org