/*
 * Copyright 2002-2016 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.jdbc.support.lob;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
import org.springframework.util.FileCopyUtils;

/**
 * {@link LobHandler} implementation for Oracle databases. Uses proprietary API
 * to create {@code oracle.sql.BLOB} and {@code oracle.sql.CLOB}
 * instances, as necessary when working with Oracle's JDBC driver.
 * Note that this LobHandler requires Oracle JDBC driver 9i or higher!
 *
 * <p>While most databases are able to work with {@link DefaultLobHandler},
 * Oracle 9i (or more specifically, the Oracle 9i JDBC driver) just accepts
 * Blob/Clob instances created via its own proprietary BLOB/CLOB API,
 * and additionally doesn't accept large streams for PreparedStatement's
 * corresponding setter methods. Therefore, you need to use a strategy like
 * this LobHandler implementation, or upgrade to the Oracle 10g/11g driver
 * (which still supports access to Oracle 9i databases).
 *
 * <p><b>NOTE: As of Oracle 10.2, {@link DefaultLobHandler} should work equally
 * well out of the box. On Oracle 11g, JDBC 4.0 based options such as
 * {@link DefaultLobHandler#setStreamAsLob} and {@link DefaultLobHandler#setCreateTemporaryLob}
 * are available as well, rendering this proprietary OracleLobHandler obsolete.</b>
 * Also, consider upgrading to a new driver even when accessing an older database.
 * See the {@link LobHandler} interface javadoc for a summary of recommendations.
 *
 * <p>Needs to work on a native JDBC Connection, to be able to cast it to
 * {@code oracle.jdbc.OracleConnection}. If you pass in Connections from a
 * connection pool (the usual case in a Java EE environment), you need to set an
 * appropriate {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}
 * to allow for automatic retrieval of the underlying native JDBC Connection.
 * LobHandler and NativeJdbcExtractor are separate concerns, therefore they
 * are represented by separate strategy interfaces.
 *
 * <p>Coded via reflection to avoid dependencies on Oracle classes.
 * Even reads in Oracle constants via reflection because of different Oracle
 * drivers (classes12, ojdbc14, ojdbc5, ojdbc6) having different constant values!
 * As this LobHandler initializes Oracle classes on instantiation, do not define
 * this as eager-initializing singleton if you do not want to depend on the Oracle
 * JAR being in the class path: use "lazy-init=true" to avoid this issue.
 *
 * @author Juergen Hoeller
 * @author Thomas Risberg
 * @since 04.12.2003
 * @see DefaultLobHandler
 * @see #setNativeJdbcExtractor
 * @deprecated in favor of {@link DefaultLobHandler} for the Oracle 10g driver and
 * higher. Consider using the 10g/11g driver even against an Oracle 9i database!
 * {@link DefaultLobHandler#setCreateTemporaryLob} is the direct equivalent of this
 * OracleLobHandler's implementation strategy, just using standard JDBC 4.0 API.
 * That said, in most cases, regular DefaultLobHandler setup will work fine as well.
 */
@Deprecated
public class OracleLobHandler extends AbstractLobHandler {

	private static final String BLOB_CLASS_NAME = "oracle.sql.BLOB";

	private static final String CLOB_CLASS_NAME = "oracle.sql.CLOB";

	private static final String DURATION_SESSION_FIELD_NAME = "DURATION_SESSION";

	private static final String MODE_READWRITE_FIELD_NAME = "MODE_READWRITE";

	private static final String MODE_READONLY_FIELD_NAME = "MODE_READONLY";


	protected final Log logger = LogFactory.getLog(getClass());

	private NativeJdbcExtractor nativeJdbcExtractor;

	private Boolean cache = Boolean.TRUE;

	private Boolean releaseResourcesAfterRead = Boolean.FALSE;

	private Class<?> blobClass;

	private Class<?> clobClass;

	private final Map<Class<?>, Integer> durationSessionConstants = new HashMap<Class<?>, Integer>(2);

	private final Map<Class<?>, Integer> modeReadWriteConstants = new HashMap<Class<?>, Integer>(2);

	private final Map<Class<?>, Integer> modeReadOnlyConstants = new HashMap<Class<?>, Integer>(2);


	/**
	 * Set an appropriate NativeJdbcExtractor to be able to retrieve the underlying
	 * native {@code oracle.jdbc.OracleConnection}. This is necessary for
	 * DataSource-based connection pools, as those need to return wrapped JDBC
	 * Connection handles that cannot be cast to a native Connection implementation.
	 * <p>Effectively, this LobHandler just invokes a single NativeJdbcExtractor
	 * method, namely {@code getNativeConnectionFromStatement} with a
	 * PreparedStatement argument (falling back to a
	 * {@code PreparedStatement.getConnection()} call if no extractor is set).
	 * <p>A common choice is {@code SimpleNativeJdbcExtractor}, whose Connection unwrapping
	 * (which is what OracleLobHandler needs) will work with many connection pools.
	 * See {@code SimpleNativeJdbcExtractor} and
	 * <a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/jdbc/OracleConnection.html">
	 * oracle.jdbc.OracleConnection</a> javadoc for details.
	 * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor#getNativeConnectionFromStatement
	 * @see org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor
	 * @see org.springframework.jdbc.support.nativejdbc.OracleJdbc4NativeJdbcExtractor
	 */
	public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
		this.nativeJdbcExtractor = nativeJdbcExtractor;
	}

	/**
	 * Set whether to cache the temporary LOB in the buffer cache.
	 * This value will be passed into BLOB/CLOB.createTemporary.
	 * <p>Default is {@code true}.
	 * <p><strong>See Also:</strong>
	 * <ul>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#createTemporary()">oracle.sql.BLOB.createTemporary</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#createTemporary()">oracle.sql.CLOB.createTemporary</a></li>
	 * </ul>
	 */
	public void setCache(boolean cache) {
		this.cache = cache;
	}

	/**
	 * Set whether to aggressively release any resources used by the LOB. If set to {@code true}
	 * then you can only read the LOB values once. Any subsequent reads will fail since the resources
	 * have been closed.
	 * <p>Setting this property to {@code true} can be useful when your queries generates large
	 * temporary LOBs that occupy space in the TEMPORARY tablespace or when you want to free up any
	 * memory allocated by the driver for the LOB reading.
	 * <p>Default is {@code false}.
	 * <p><strong>See Also:</strong>
	 * <ul>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#freeTemporary()">oracle.sql.BLOB.freeTemporary</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#freeTemporary()">oracle.sql.CLOB.freeTemporary</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#open()">oracle.sql.BLOB.open</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#open()">oracle.sql.CLOB.open</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#open()">oracle.sql.BLOB.close</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#open()">oracle.sql.CLOB.close</a></li>
	 * </ul>
	 */
	public void setReleaseResourcesAfterRead(boolean releaseResources) {
		this.releaseResourcesAfterRead = releaseResources;
	}


	/**
	 * Retrieve the {@code oracle.sql.BLOB} and {@code oracle.sql.CLOB}
	 * classes via reflection, and initialize the values for the
	 * DURATION_SESSION, MODE_READWRITE and MODE_READONLY constants defined there.
	 * <p><strong>See Also:</strong>
	 * <ul>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#DURATION_SESSION">oracle.sql.BLOB.DURATION_SESSION</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#MODE_READWRITE">oracle.sql.BLOB.MODE_READWRITE</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/BLOB.html#MODE_READONLY">oracle.sql.BLOB.MODE_READONLY</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#DURATION_SESSION">oracle.sql.CLOB.DURATION_SESSION</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#MODE_READWRITE">oracle.sql.CLOB.MODE_READWRITE</a></li>
	 * <li><a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/sql/CLOB.html#MODE_READONLY">oracle.sql.CLOB.MODE_READONLY</a></li>
	 * </ul>
	 * @param con the Oracle Connection, for using the exact same class loader
	 * that the Oracle driver was loaded with
	 */
	protected synchronized void initOracleDriverClasses(Connection con) {
		if (this.blobClass == null) {
			try {
				// Initialize oracle.sql.BLOB class
				this.blobClass = con.getClass().getClassLoader().loadClass(BLOB_CLASS_NAME);
				this.durationSessionConstants.put(
						this.blobClass, this.blobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null));
				this.modeReadWriteConstants.put(
						this.blobClass, this.blobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null));
				this.modeReadOnlyConstants.put(
						this.blobClass, this.blobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null));

				// Initialize oracle.sql.CLOB class
				this.clobClass = con.getClass().getClassLoader().loadClass(CLOB_CLASS_NAME);
				this.durationSessionConstants.put(
						this.clobClass, this.clobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null));
				this.modeReadWriteConstants.put(
						this.clobClass, this.clobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null));
				this.modeReadOnlyConstants.put(
						this.clobClass, this.clobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null));
			}
			catch (Exception ex) {
				throw new InvalidDataAccessApiUsageException(
						"Couldn't initialize OracleLobHandler because Oracle driver classes are not available. " +
						"Note that OracleLobHandler requires Oracle JDBC driver 9i or higher!", ex);
			}
		}
	}


	@Override
	public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException {
		logger.debug("Returning Oracle BLOB as bytes");
		Blob blob = rs.getBlob(columnIndex);
		initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob);
		byte[] retVal = (blob != null ? blob.getBytes(1, (int) blob.length()) : null);
		releaseResourcesAfterRead(rs.getStatement().getConnection(), blob);
		return retVal;
	}

	@Override
	public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException {
		logger.debug("Returning Oracle BLOB as binary stream");
		Blob blob = rs.getBlob(columnIndex);
		initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob);
		InputStream retVal = (blob != null ? blob.getBinaryStream() : null);
		releaseResourcesAfterRead(rs.getStatement().getConnection(), blob);
		return retVal;
	}

	@Override
	public String getClobAsString(ResultSet rs, int columnIndex) throws SQLException {
		logger.debug("Returning Oracle CLOB as string");
		Clob clob = rs.getClob(columnIndex);
		initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
		String retVal = (clob != null ? clob.getSubString(1, (int) clob.length()) : null);
		releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
		return retVal;
	}

	@Override
	public InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException {
		logger.debug("Returning Oracle CLOB as ASCII stream");
		Clob clob = rs.getClob(columnIndex);
		initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
		InputStream retVal = (clob != null ? clob.getAsciiStream() : null);
		releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
		return retVal;
	}

	@Override
	public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException {
		logger.debug("Returning Oracle CLOB as character stream");
		Clob clob = rs.getClob(columnIndex);
		initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
		Reader retVal = (clob != null ? clob.getCharacterStream() : null);
		releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
		return retVal;
	}

	@Override
	public LobCreator getLobCreator() {
		return new OracleLobCreator();
	}

	/**
	 * Initialize any LOB resources before a read is done.
	 * <p>This implementation calls {@code BLOB.open(BLOB.MODE_READONLY)} or
	 * {@code CLOB.open(CLOB.MODE_READONLY)} on any non-temporary LOBs if
	 * {@code releaseResourcesAfterRead} property is set to {@code true}.
	 * <p>This method can be overridden by sublcasses if different behavior is desired.
	 * @param con the connection to be usde for initilization
	 * @param lob the LOB to initialize
	 */
	protected void initializeResourcesBeforeRead(Connection con, Object lob) {
		if (this.releaseResourcesAfterRead) {
			initOracleDriverClasses(con);
			try {
				/*
				if (!((BLOB) lob.isTemporary() {
				*/
				Method isTemporary = lob.getClass().getMethod("isTemporary");
				Boolean temporary = (Boolean) isTemporary.invoke(lob);
				if (!temporary) {
					/*
					((BLOB) lob).open(BLOB.MODE_READONLY);
					*/
					Method open = lob.getClass().getMethod("open", int.class);
					open.invoke(lob, this.modeReadOnlyConstants.get(lob.getClass()));
				}
			}
			catch (InvocationTargetException ex) {
				logger.error("Could not open Oracle LOB", ex.getTargetException());
			}
			catch (Exception ex) {
				throw new DataAccessResourceFailureException("Could not open Oracle LOB", ex);
			}
		}
	}

	/**
	 * Release any LOB resources after read is complete.
	 * <p>If {@code releaseResourcesAfterRead} property is set to {@code true}
	 * then this implementation calls
	 * {@code BLOB.close()} or {@code CLOB.close()}
	 * on any non-temporary LOBs that are open or
	 * {@code BLOB.freeTemporary()} or {@code CLOB.freeTemporary()}
	 * on any temporary LOBs.
	 * <p>This method can be overridden by sublcasses if different behavior is desired.
	 * @param con the connection to be usde for initilization
	 * @param lob the LOB to initialize
	 */
	protected void releaseResourcesAfterRead(Connection con, Object lob) {
		if (this.releaseResourcesAfterRead) {
			initOracleDriverClasses(con);
			Boolean temporary = Boolean.FALSE;
			try {
				/*
				if (((BLOB) lob.isTemporary() {
				*/
				Method isTemporary = lob.getClass().getMethod("isTemporary");
				temporary = (Boolean) isTemporary.invoke(lob);
				if (temporary) {
					/*
					((BLOB) lob).freeTemporary();
					*/
					Method freeTemporary = lob.getClass().getMethod("freeTemporary");
					freeTemporary.invoke(lob);
				}
				else {
					/*
					if (((BLOB) lob.isOpen() {
					*/
					Method isOpen = lob.getClass().getMethod("isOpen");
					Boolean open = (Boolean) isOpen.invoke(lob);
					if (open) {
						/*
						((BLOB) lob).close();
						*/
						Method close = lob.getClass().getMethod("close");
						close.invoke(lob);
					}
				}
			}
			catch (InvocationTargetException ex) {
				if (temporary) {
					logger.error("Could not free Oracle LOB", ex.getTargetException());
				}
				else {
					logger.error("Could not close Oracle LOB", ex.getTargetException());
				}
			}
			catch (Exception ex) {
				if (temporary) {
					throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex);
				}
				else {
					throw new DataAccessResourceFailureException("Could not close Oracle LOB", ex);
				}
			}
		}
	}


	/**
	 * LobCreator implementation for Oracle databases.
	 * Creates Oracle-style temporary BLOBs and CLOBs that it frees on close.
	 * @see #close
	 */
	protected class OracleLobCreator implements LobCreator {

		private final List<Object> temporaryLobs = new LinkedList<Object>();

		@Override
		public void setBlobAsBytes(PreparedStatement ps, int paramIndex, final byte[] content)
				throws SQLException {

			if (content != null) {
				Blob blob = (Blob) createLob(ps, false, new LobCallback() {
					@Override
					public void populateLob(Object lob) throws Exception {
						Method methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream");
						OutputStream out = (OutputStream) methodToInvoke.invoke(lob);
						FileCopyUtils.copy(content, out);
					}
				});
				ps.setBlob(paramIndex, blob);
				if (logger.isDebugEnabled()) {
					logger.debug("Set bytes for Oracle BLOB with length " + blob.length());
				}
			}
			else {
				ps.setBlob(paramIndex, (Blob) null);
				logger.debug("Set Oracle BLOB to null");
			}
		}

		@Override
		public void setBlobAsBinaryStream(
				PreparedStatement ps, int paramIndex, final InputStream binaryStream, int contentLength)
				throws SQLException {

			if (binaryStream != null) {
				Blob blob = (Blob) createLob(ps, false, new LobCallback() {
					@Override
					public void populateLob(Object lob) throws Exception {
						Method methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream", (Class[]) null);
						OutputStream out = (OutputStream) methodToInvoke.invoke(lob, (Object[]) null);
						FileCopyUtils.copy(binaryStream, out);
					}
				});
				ps.setBlob(paramIndex, blob);
				if (logger.isDebugEnabled()) {
					logger.debug("Set binary stream for Oracle BLOB with length " + blob.length());
				}
			}
			else {
				ps.setBlob(paramIndex, (Blob) null);
				logger.debug("Set Oracle BLOB to null");
			}
		}

		@Override
		public void setClobAsString(PreparedStatement ps, int paramIndex, final String content)
			throws SQLException {

			if (content != null) {
				Clob clob = (Clob) createLob(ps, true, new LobCallback() {
					@Override
					public void populateLob(Object lob) throws Exception {
						Method methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class[]) null);
						Writer writer = (Writer) methodToInvoke.invoke(lob, (Object[]) null);
						FileCopyUtils.copy(content, writer);
					}
				});
				ps.setClob(paramIndex, clob);
				if (logger.isDebugEnabled()) {
					logger.debug("Set string for Oracle CLOB with length " + clob.length());
				}
			}
			else {
				ps.setClob(paramIndex, (Clob) null);
				logger.debug("Set Oracle CLOB to null");
			}
		}

		@Override
		public void setClobAsAsciiStream(
				PreparedStatement ps, int paramIndex, final InputStream asciiStream, int contentLength)
			throws SQLException {

			if (asciiStream != null) {
				Clob clob = (Clob) createLob(ps, true, new LobCallback() {
					@Override
					public void populateLob(Object lob) throws Exception {
						Method methodToInvoke = lob.getClass().getMethod("getAsciiOutputStream", (Class[]) null);
						OutputStream out = (OutputStream) methodToInvoke.invoke(lob, (Object[]) null);
						FileCopyUtils.copy(asciiStream, out);
					}
				});
				ps.setClob(paramIndex, clob);
				if (logger.isDebugEnabled()) {
					logger.debug("Set ASCII stream for Oracle CLOB with length " + clob.length());
				}
			}
			else {
				ps.setClob(paramIndex, (Clob) null);
				logger.debug("Set Oracle CLOB to null");
			}
		}

		@Override
		public void setClobAsCharacterStream(
				PreparedStatement ps, int paramIndex, final Reader characterStream, int contentLength)
			throws SQLException {

			if (characterStream != null) {
				Clob clob = (Clob) createLob(ps, true, new LobCallback() {
					@Override
					public void populateLob(Object lob) throws Exception {
						Method methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class[]) null);
						Writer writer = (Writer) methodToInvoke.invoke(lob, (Object[]) null);
						FileCopyUtils.copy(characterStream, writer);
					}
				});
				ps.setClob(paramIndex, clob);
				if (logger.isDebugEnabled()) {
					logger.debug("Set character stream for Oracle CLOB with length " + clob.length());
				}
			}
			else {
				ps.setClob(paramIndex, (Clob) null);
				logger.debug("Set Oracle CLOB to null");
			}
		}

		/**
		 * Create a LOB instance for the given PreparedStatement,
		 * populating it via the given callback.
		 */
		protected Object createLob(PreparedStatement ps, boolean clob, LobCallback callback)
				throws SQLException {

			Connection con = null;
			try {
				con = getOracleConnection(ps);
				initOracleDriverClasses(con);
				Object lob = prepareLob(con, clob ? clobClass : blobClass);
				callback.populateLob(lob);
				lob.getClass().getMethod("close", (Class[]) null).invoke(lob, (Object[]) null);
				this.temporaryLobs.add(lob);
				if (logger.isDebugEnabled()) {
					logger.debug("Created new Oracle " + (clob ? "CLOB" : "BLOB"));
				}
				return lob;
			}
			catch (SQLException ex) {
				throw ex;
			}
			catch (InvocationTargetException ex) {
				if (ex.getTargetException() instanceof SQLException) {
					throw (SQLException) ex.getTargetException();
				}
				else if (con != null && ex.getTargetException() instanceof ClassCastException) {
					throw new InvalidDataAccessApiUsageException(
							"OracleLobCreator needs to work on [oracle.jdbc.OracleConnection], not on [" +
							con.getClass().getName() + "]: specify a corresponding NativeJdbcExtractor",
							ex.getTargetException());
				}
				else {
					throw new DataAccessResourceFailureException("Could not create Oracle LOB",
							ex.getTargetException());
				}
			}
			catch (Exception ex) {
				throw new DataAccessResourceFailureException("Could not create Oracle LOB", ex);
			}
		}

		/**
		 * Retrieve the underlying OracleConnection, using a NativeJdbcExtractor if set.
		 */
		protected Connection getOracleConnection(PreparedStatement ps)
				throws SQLException, ClassNotFoundException {

			return (nativeJdbcExtractor != null ?
					nativeJdbcExtractor.getNativeConnectionFromStatement(ps) : ps.getConnection());
		}

		/**
		 * Create and open an oracle.sql.BLOB/CLOB instance via reflection.
		 */
		protected Object prepareLob(Connection con, Class<?> lobClass) throws Exception {
			/*
			BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
			blob.open(BLOB.MODE_READWRITE);
			return blob;
			*/
			Method createTemporary = lobClass.getMethod(
					"createTemporary", Connection.class, boolean.class, int.class);
			Object lob = createTemporary.invoke(null, con, cache, durationSessionConstants.get(lobClass));
			Method open = lobClass.getMethod("open", int.class);
			open.invoke(lob, modeReadWriteConstants.get(lobClass));
			return lob;
		}

		/**
		 * Free all temporary BLOBs and CLOBs created by this creator.
		 */
		@Override
		public void close() {
			try {
				for (Iterator<?> it = this.temporaryLobs.iterator(); it.hasNext();) {
					/*
					BLOB blob = (BLOB) it.next();
					blob.freeTemporary();
					*/
					Object lob = it.next();
					Method freeTemporary = lob.getClass().getMethod("freeTemporary");
					freeTemporary.invoke(lob);
					it.remove();
				}
			}
			catch (InvocationTargetException ex) {
				logger.error("Could not free Oracle LOB", ex.getTargetException());
			}
			catch (Exception ex) {
				throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex);
			}
		}
	}


	/**
	 * Internal callback interface for use with createLob.
	 */
	protected interface LobCallback {

		/**
		 * Populate the given BLOB or CLOB instance with content.
		 * @throws Exception any exception including InvocationTargetException
		 */
		void populateLob(Object lob) throws Exception;
	}

}
