001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.support.lob;
018
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.io.Reader;
022import java.io.Writer;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.sql.Blob;
026import java.sql.Clob;
027import java.sql.Connection;
028import java.sql.PreparedStatement;
029import java.sql.ResultSet;
030import java.sql.SQLException;
031import java.util.HashMap;
032import java.util.Iterator;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039
040import org.springframework.dao.DataAccessResourceFailureException;
041import org.springframework.dao.InvalidDataAccessApiUsageException;
042import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
043import org.springframework.util.FileCopyUtils;
044
045/**
046 * {@link LobHandler} implementation for Oracle databases. Uses proprietary API
047 * to create {@code oracle.sql.BLOB} and {@code oracle.sql.CLOB}
048 * instances, as necessary when working with Oracle's JDBC driver.
049 * Note that this LobHandler requires Oracle JDBC driver 9i or higher!
050 *
051 * <p>While most databases are able to work with {@link DefaultLobHandler},
052 * Oracle 9i (or more specifically, the Oracle 9i JDBC driver) just accepts
053 * Blob/Clob instances created via its own proprietary BLOB/CLOB API,
054 * and additionally doesn't accept large streams for PreparedStatement's
055 * corresponding setter methods. Therefore, you need to use a strategy like
056 * this LobHandler implementation, or upgrade to the Oracle 10g/11g driver
057 * (which still supports access to Oracle 9i databases).
058 *
059 * <p><b>NOTE: As of Oracle 10.2, {@link DefaultLobHandler} should work equally
060 * well out of the box. On Oracle 11g, JDBC 4.0 based options such as
061 * {@link DefaultLobHandler#setStreamAsLob} and {@link DefaultLobHandler#setCreateTemporaryLob}
062 * are available as well, rendering this proprietary OracleLobHandler obsolete.</b>
063 * Also, consider upgrading to a new driver even when accessing an older database.
064 * See the {@link LobHandler} interface javadoc for a summary of recommendations.
065 *
066 * <p>Needs to work on a native JDBC Connection, to be able to cast it to
067 * {@code oracle.jdbc.OracleConnection}. If you pass in Connections from a
068 * connection pool (the usual case in a Java EE environment), you need to set an
069 * appropriate {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}
070 * to allow for automatic retrieval of the underlying native JDBC Connection.
071 * LobHandler and NativeJdbcExtractor are separate concerns, therefore they
072 * are represented by separate strategy interfaces.
073 *
074 * <p>Coded via reflection to avoid dependencies on Oracle classes.
075 * Even reads in Oracle constants via reflection because of different Oracle
076 * drivers (classes12, ojdbc14, ojdbc5, ojdbc6) having different constant values!
077 * As this LobHandler initializes Oracle classes on instantiation, do not define
078 * this as eager-initializing singleton if you do not want to depend on the Oracle
079 * JAR being in the class path: use "lazy-init=true" to avoid this issue.
080 *
081 * @author Juergen Hoeller
082 * @author Thomas Risberg
083 * @since 04.12.2003
084 * @see DefaultLobHandler
085 * @see #setNativeJdbcExtractor
086 * @deprecated in favor of {@link DefaultLobHandler} for the Oracle 10g driver and
087 * higher. Consider using the 10g/11g driver even against an Oracle 9i database!
088 * {@link DefaultLobHandler#setCreateTemporaryLob} is the direct equivalent of this
089 * OracleLobHandler's implementation strategy, just using standard JDBC 4.0 API.
090 * That said, in most cases, regular DefaultLobHandler setup will work fine as well.
091 */
092@Deprecated
093public class OracleLobHandler extends AbstractLobHandler {
094
095        private static final String BLOB_CLASS_NAME = "oracle.sql.BLOB";
096
097        private static final String CLOB_CLASS_NAME = "oracle.sql.CLOB";
098
099        private static final String DURATION_SESSION_FIELD_NAME = "DURATION_SESSION";
100
101        private static final String MODE_READWRITE_FIELD_NAME = "MODE_READWRITE";
102
103        private static final String MODE_READONLY_FIELD_NAME = "MODE_READONLY";
104
105
106        protected final Log logger = LogFactory.getLog(getClass());
107
108        private NativeJdbcExtractor nativeJdbcExtractor;
109
110        private Boolean cache = Boolean.TRUE;
111
112        private Boolean releaseResourcesAfterRead = Boolean.FALSE;
113
114        private Class<?> blobClass;
115
116        private Class<?> clobClass;
117
118        private final Map<Class<?>, Integer> durationSessionConstants = new HashMap<Class<?>, Integer>(2);
119
120        private final Map<Class<?>, Integer> modeReadWriteConstants = new HashMap<Class<?>, Integer>(2);
121
122        private final Map<Class<?>, Integer> modeReadOnlyConstants = new HashMap<Class<?>, Integer>(2);
123
124
125        /**
126         * Set an appropriate NativeJdbcExtractor to be able to retrieve the underlying
127         * native {@code oracle.jdbc.OracleConnection}. This is necessary for
128         * DataSource-based connection pools, as those need to return wrapped JDBC
129         * Connection handles that cannot be cast to a native Connection implementation.
130         * <p>Effectively, this LobHandler just invokes a single NativeJdbcExtractor
131         * method, namely {@code getNativeConnectionFromStatement} with a
132         * PreparedStatement argument (falling back to a
133         * {@code PreparedStatement.getConnection()} call if no extractor is set).
134         * <p>A common choice is {@code SimpleNativeJdbcExtractor}, whose Connection unwrapping
135         * (which is what OracleLobHandler needs) will work with many connection pools.
136         * See {@code SimpleNativeJdbcExtractor} and
137         * <a href="https://download.oracle.com/otn_hosted_doc/jdeveloper/905/jdbc-javadoc/oracle/jdbc/OracleConnection.html">
138         * oracle.jdbc.OracleConnection</a> javadoc for details.
139         * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor#getNativeConnectionFromStatement
140         * @see org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor
141         * @see org.springframework.jdbc.support.nativejdbc.OracleJdbc4NativeJdbcExtractor
142         */
143        public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
144                this.nativeJdbcExtractor = nativeJdbcExtractor;
145        }
146
147        /**
148         * Set whether to cache the temporary LOB in the buffer cache.
149         * This value will be passed into BLOB/CLOB.createTemporary.
150         * <p>Default is {@code true}.
151         * <p><strong>See Also:</strong>
152         * <ul>
153         * <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>
154         * <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>
155         * </ul>
156         */
157        public void setCache(boolean cache) {
158                this.cache = cache;
159        }
160
161        /**
162         * Set whether to aggressively release any resources used by the LOB. If set to {@code true}
163         * then you can only read the LOB values once. Any subsequent reads will fail since the resources
164         * have been closed.
165         * <p>Setting this property to {@code true} can be useful when your queries generates large
166         * temporary LOBs that occupy space in the TEMPORARY tablespace or when you want to free up any
167         * memory allocated by the driver for the LOB reading.
168         * <p>Default is {@code false}.
169         * <p><strong>See Also:</strong>
170         * <ul>
171         * <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>
172         * <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>
173         * <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>
174         * <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>
175         * <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>
176         * <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>
177         * </ul>
178         */
179        public void setReleaseResourcesAfterRead(boolean releaseResources) {
180                this.releaseResourcesAfterRead = releaseResources;
181        }
182
183
184        /**
185         * Retrieve the {@code oracle.sql.BLOB} and {@code oracle.sql.CLOB}
186         * classes via reflection, and initialize the values for the
187         * DURATION_SESSION, MODE_READWRITE and MODE_READONLY constants defined there.
188         * <p><strong>See Also:</strong>
189         * <ul>
190         * <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>
191         * <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>
192         * <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>
193         * <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>
194         * <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>
195         * <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>
196         * </ul>
197         * @param con the Oracle Connection, for using the exact same class loader
198         * that the Oracle driver was loaded with
199         */
200        protected synchronized void initOracleDriverClasses(Connection con) {
201                if (this.blobClass == null) {
202                        try {
203                                // Initialize oracle.sql.BLOB class
204                                this.blobClass = con.getClass().getClassLoader().loadClass(BLOB_CLASS_NAME);
205                                this.durationSessionConstants.put(
206                                                this.blobClass, this.blobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null));
207                                this.modeReadWriteConstants.put(
208                                                this.blobClass, this.blobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null));
209                                this.modeReadOnlyConstants.put(
210                                                this.blobClass, this.blobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null));
211
212                                // Initialize oracle.sql.CLOB class
213                                this.clobClass = con.getClass().getClassLoader().loadClass(CLOB_CLASS_NAME);
214                                this.durationSessionConstants.put(
215                                                this.clobClass, this.clobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null));
216                                this.modeReadWriteConstants.put(
217                                                this.clobClass, this.clobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null));
218                                this.modeReadOnlyConstants.put(
219                                                this.clobClass, this.clobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null));
220                        }
221                        catch (Exception ex) {
222                                throw new InvalidDataAccessApiUsageException(
223                                                "Couldn't initialize OracleLobHandler because Oracle driver classes are not available. " +
224                                                "Note that OracleLobHandler requires Oracle JDBC driver 9i or higher!", ex);
225                        }
226                }
227        }
228
229
230        @Override
231        public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException {
232                logger.debug("Returning Oracle BLOB as bytes");
233                Blob blob = rs.getBlob(columnIndex);
234                initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob);
235                byte[] retVal = (blob != null ? blob.getBytes(1, (int) blob.length()) : null);
236                releaseResourcesAfterRead(rs.getStatement().getConnection(), blob);
237                return retVal;
238        }
239
240        @Override
241        public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException {
242                logger.debug("Returning Oracle BLOB as binary stream");
243                Blob blob = rs.getBlob(columnIndex);
244                initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob);
245                InputStream retVal = (blob != null ? blob.getBinaryStream() : null);
246                releaseResourcesAfterRead(rs.getStatement().getConnection(), blob);
247                return retVal;
248        }
249
250        @Override
251        public String getClobAsString(ResultSet rs, int columnIndex) throws SQLException {
252                logger.debug("Returning Oracle CLOB as string");
253                Clob clob = rs.getClob(columnIndex);
254                initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
255                String retVal = (clob != null ? clob.getSubString(1, (int) clob.length()) : null);
256                releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
257                return retVal;
258        }
259
260        @Override
261        public InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException {
262                logger.debug("Returning Oracle CLOB as ASCII stream");
263                Clob clob = rs.getClob(columnIndex);
264                initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
265                InputStream retVal = (clob != null ? clob.getAsciiStream() : null);
266                releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
267                return retVal;
268        }
269
270        @Override
271        public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException {
272                logger.debug("Returning Oracle CLOB as character stream");
273                Clob clob = rs.getClob(columnIndex);
274                initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob);
275                Reader retVal = (clob != null ? clob.getCharacterStream() : null);
276                releaseResourcesAfterRead(rs.getStatement().getConnection(), clob);
277                return retVal;
278        }
279
280        @Override
281        public LobCreator getLobCreator() {
282                return new OracleLobCreator();
283        }
284
285        /**
286         * Initialize any LOB resources before a read is done.
287         * <p>This implementation calls {@code BLOB.open(BLOB.MODE_READONLY)} or
288         * {@code CLOB.open(CLOB.MODE_READONLY)} on any non-temporary LOBs if
289         * {@code releaseResourcesAfterRead} property is set to {@code true}.
290         * <p>This method can be overridden by sublcasses if different behavior is desired.
291         * @param con the connection to be usde for initilization
292         * @param lob the LOB to initialize
293         */
294        protected void initializeResourcesBeforeRead(Connection con, Object lob) {
295                if (this.releaseResourcesAfterRead) {
296                        initOracleDriverClasses(con);
297                        try {
298                                /*
299                                if (!((BLOB) lob.isTemporary() {
300                                */
301                                Method isTemporary = lob.getClass().getMethod("isTemporary");
302                                Boolean temporary = (Boolean) isTemporary.invoke(lob);
303                                if (!temporary) {
304                                        /*
305                                        ((BLOB) lob).open(BLOB.MODE_READONLY);
306                                        */
307                                        Method open = lob.getClass().getMethod("open", int.class);
308                                        open.invoke(lob, this.modeReadOnlyConstants.get(lob.getClass()));
309                                }
310                        }
311                        catch (InvocationTargetException ex) {
312                                logger.error("Could not open Oracle LOB", ex.getTargetException());
313                        }
314                        catch (Exception ex) {
315                                throw new DataAccessResourceFailureException("Could not open Oracle LOB", ex);
316                        }
317                }
318        }
319
320        /**
321         * Release any LOB resources after read is complete.
322         * <p>If {@code releaseResourcesAfterRead} property is set to {@code true}
323         * then this implementation calls
324         * {@code BLOB.close()} or {@code CLOB.close()}
325         * on any non-temporary LOBs that are open or
326         * {@code BLOB.freeTemporary()} or {@code CLOB.freeTemporary()}
327         * on any temporary LOBs.
328         * <p>This method can be overridden by sublcasses if different behavior is desired.
329         * @param con the connection to be usde for initilization
330         * @param lob the LOB to initialize
331         */
332        protected void releaseResourcesAfterRead(Connection con, Object lob) {
333                if (this.releaseResourcesAfterRead) {
334                        initOracleDriverClasses(con);
335                        Boolean temporary = Boolean.FALSE;
336                        try {
337                                /*
338                                if (((BLOB) lob.isTemporary() {
339                                */
340                                Method isTemporary = lob.getClass().getMethod("isTemporary");
341                                temporary = (Boolean) isTemporary.invoke(lob);
342                                if (temporary) {
343                                        /*
344                                        ((BLOB) lob).freeTemporary();
345                                        */
346                                        Method freeTemporary = lob.getClass().getMethod("freeTemporary");
347                                        freeTemporary.invoke(lob);
348                                }
349                                else {
350                                        /*
351                                        if (((BLOB) lob.isOpen() {
352                                        */
353                                        Method isOpen = lob.getClass().getMethod("isOpen");
354                                        Boolean open = (Boolean) isOpen.invoke(lob);
355                                        if (open) {
356                                                /*
357                                                ((BLOB) lob).close();
358                                                */
359                                                Method close = lob.getClass().getMethod("close");
360                                                close.invoke(lob);
361                                        }
362                                }
363                        }
364                        catch (InvocationTargetException ex) {
365                                if (temporary) {
366                                        logger.error("Could not free Oracle LOB", ex.getTargetException());
367                                }
368                                else {
369                                        logger.error("Could not close Oracle LOB", ex.getTargetException());
370                                }
371                        }
372                        catch (Exception ex) {
373                                if (temporary) {
374                                        throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex);
375                                }
376                                else {
377                                        throw new DataAccessResourceFailureException("Could not close Oracle LOB", ex);
378                                }
379                        }
380                }
381        }
382
383
384        /**
385         * LobCreator implementation for Oracle databases.
386         * Creates Oracle-style temporary BLOBs and CLOBs that it frees on close.
387         * @see #close
388         */
389        protected class OracleLobCreator implements LobCreator {
390
391                private final List<Object> temporaryLobs = new LinkedList<Object>();
392
393                @Override
394                public void setBlobAsBytes(PreparedStatement ps, int paramIndex, final byte[] content)
395                                throws SQLException {
396
397                        if (content != null) {
398                                Blob blob = (Blob) createLob(ps, false, new LobCallback() {
399                                        @Override
400                                        public void populateLob(Object lob) throws Exception {
401                                                Method methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream");
402                                                OutputStream out = (OutputStream) methodToInvoke.invoke(lob);
403                                                FileCopyUtils.copy(content, out);
404                                        }
405                                });
406                                ps.setBlob(paramIndex, blob);
407                                if (logger.isDebugEnabled()) {
408                                        logger.debug("Set bytes for Oracle BLOB with length " + blob.length());
409                                }
410                        }
411                        else {
412                                ps.setBlob(paramIndex, (Blob) null);
413                                logger.debug("Set Oracle BLOB to null");
414                        }
415                }
416
417                @Override
418                public void setBlobAsBinaryStream(
419                                PreparedStatement ps, int paramIndex, final InputStream binaryStream, int contentLength)
420                                throws SQLException {
421
422                        if (binaryStream != null) {
423                                Blob blob = (Blob) createLob(ps, false, new LobCallback() {
424                                        @Override
425                                        public void populateLob(Object lob) throws Exception {
426                                                Method methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream", (Class[]) null);
427                                                OutputStream out = (OutputStream) methodToInvoke.invoke(lob, (Object[]) null);
428                                                FileCopyUtils.copy(binaryStream, out);
429                                        }
430                                });
431                                ps.setBlob(paramIndex, blob);
432                                if (logger.isDebugEnabled()) {
433                                        logger.debug("Set binary stream for Oracle BLOB with length " + blob.length());
434                                }
435                        }
436                        else {
437                                ps.setBlob(paramIndex, (Blob) null);
438                                logger.debug("Set Oracle BLOB to null");
439                        }
440                }
441
442                @Override
443                public void setClobAsString(PreparedStatement ps, int paramIndex, final String content)
444                        throws SQLException {
445
446                        if (content != null) {
447                                Clob clob = (Clob) createLob(ps, true, new LobCallback() {
448                                        @Override
449                                        public void populateLob(Object lob) throws Exception {
450                                                Method methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class[]) null);
451                                                Writer writer = (Writer) methodToInvoke.invoke(lob, (Object[]) null);
452                                                FileCopyUtils.copy(content, writer);
453                                        }
454                                });
455                                ps.setClob(paramIndex, clob);
456                                if (logger.isDebugEnabled()) {
457                                        logger.debug("Set string for Oracle CLOB with length " + clob.length());
458                                }
459                        }
460                        else {
461                                ps.setClob(paramIndex, (Clob) null);
462                                logger.debug("Set Oracle CLOB to null");
463                        }
464                }
465
466                @Override
467                public void setClobAsAsciiStream(
468                                PreparedStatement ps, int paramIndex, final InputStream asciiStream, int contentLength)
469                        throws SQLException {
470
471                        if (asciiStream != null) {
472                                Clob clob = (Clob) createLob(ps, true, new LobCallback() {
473                                        @Override
474                                        public void populateLob(Object lob) throws Exception {
475                                                Method methodToInvoke = lob.getClass().getMethod("getAsciiOutputStream", (Class[]) null);
476                                                OutputStream out = (OutputStream) methodToInvoke.invoke(lob, (Object[]) null);
477                                                FileCopyUtils.copy(asciiStream, out);
478                                        }
479                                });
480                                ps.setClob(paramIndex, clob);
481                                if (logger.isDebugEnabled()) {
482                                        logger.debug("Set ASCII stream for Oracle CLOB with length " + clob.length());
483                                }
484                        }
485                        else {
486                                ps.setClob(paramIndex, (Clob) null);
487                                logger.debug("Set Oracle CLOB to null");
488                        }
489                }
490
491                @Override
492                public void setClobAsCharacterStream(
493                                PreparedStatement ps, int paramIndex, final Reader characterStream, int contentLength)
494                        throws SQLException {
495
496                        if (characterStream != null) {
497                                Clob clob = (Clob) createLob(ps, true, new LobCallback() {
498                                        @Override
499                                        public void populateLob(Object lob) throws Exception {
500                                                Method methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class[]) null);
501                                                Writer writer = (Writer) methodToInvoke.invoke(lob, (Object[]) null);
502                                                FileCopyUtils.copy(characterStream, writer);
503                                        }
504                                });
505                                ps.setClob(paramIndex, clob);
506                                if (logger.isDebugEnabled()) {
507                                        logger.debug("Set character stream for Oracle CLOB with length " + clob.length());
508                                }
509                        }
510                        else {
511                                ps.setClob(paramIndex, (Clob) null);
512                                logger.debug("Set Oracle CLOB to null");
513                        }
514                }
515
516                /**
517                 * Create a LOB instance for the given PreparedStatement,
518                 * populating it via the given callback.
519                 */
520                protected Object createLob(PreparedStatement ps, boolean clob, LobCallback callback)
521                                throws SQLException {
522
523                        Connection con = null;
524                        try {
525                                con = getOracleConnection(ps);
526                                initOracleDriverClasses(con);
527                                Object lob = prepareLob(con, clob ? clobClass : blobClass);
528                                callback.populateLob(lob);
529                                lob.getClass().getMethod("close", (Class[]) null).invoke(lob, (Object[]) null);
530                                this.temporaryLobs.add(lob);
531                                if (logger.isDebugEnabled()) {
532                                        logger.debug("Created new Oracle " + (clob ? "CLOB" : "BLOB"));
533                                }
534                                return lob;
535                        }
536                        catch (SQLException ex) {
537                                throw ex;
538                        }
539                        catch (InvocationTargetException ex) {
540                                if (ex.getTargetException() instanceof SQLException) {
541                                        throw (SQLException) ex.getTargetException();
542                                }
543                                else if (con != null && ex.getTargetException() instanceof ClassCastException) {
544                                        throw new InvalidDataAccessApiUsageException(
545                                                        "OracleLobCreator needs to work on [oracle.jdbc.OracleConnection], not on [" +
546                                                        con.getClass().getName() + "]: specify a corresponding NativeJdbcExtractor",
547                                                        ex.getTargetException());
548                                }
549                                else {
550                                        throw new DataAccessResourceFailureException("Could not create Oracle LOB",
551                                                        ex.getTargetException());
552                                }
553                        }
554                        catch (Exception ex) {
555                                throw new DataAccessResourceFailureException("Could not create Oracle LOB", ex);
556                        }
557                }
558
559                /**
560                 * Retrieve the underlying OracleConnection, using a NativeJdbcExtractor if set.
561                 */
562                protected Connection getOracleConnection(PreparedStatement ps)
563                                throws SQLException, ClassNotFoundException {
564
565                        return (nativeJdbcExtractor != null ?
566                                        nativeJdbcExtractor.getNativeConnectionFromStatement(ps) : ps.getConnection());
567                }
568
569                /**
570                 * Create and open an oracle.sql.BLOB/CLOB instance via reflection.
571                 */
572                protected Object prepareLob(Connection con, Class<?> lobClass) throws Exception {
573                        /*
574                        BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
575                        blob.open(BLOB.MODE_READWRITE);
576                        return blob;
577                        */
578                        Method createTemporary = lobClass.getMethod(
579                                        "createTemporary", Connection.class, boolean.class, int.class);
580                        Object lob = createTemporary.invoke(null, con, cache, durationSessionConstants.get(lobClass));
581                        Method open = lobClass.getMethod("open", int.class);
582                        open.invoke(lob, modeReadWriteConstants.get(lobClass));
583                        return lob;
584                }
585
586                /**
587                 * Free all temporary BLOBs and CLOBs created by this creator.
588                 */
589                @Override
590                public void close() {
591                        try {
592                                for (Iterator<?> it = this.temporaryLobs.iterator(); it.hasNext();) {
593                                        /*
594                                        BLOB blob = (BLOB) it.next();
595                                        blob.freeTemporary();
596                                        */
597                                        Object lob = it.next();
598                                        Method freeTemporary = lob.getClass().getMethod("freeTemporary");
599                                        freeTemporary.invoke(lob);
600                                        it.remove();
601                                }
602                        }
603                        catch (InvocationTargetException ex) {
604                                logger.error("Could not free Oracle LOB", ex.getTargetException());
605                        }
606                        catch (Exception ex) {
607                                throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex);
608                        }
609                }
610        }
611
612
613        /**
614         * Internal callback interface for use with createLob.
615         */
616        protected interface LobCallback {
617
618                /**
619                 * Populate the given BLOB or CLOB instance with content.
620                 * @throws Exception any exception including InvocationTargetException
621                 */
622                void populateLob(Object lob) throws Exception;
623        }
624
625}