KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > support > lob > OracleLobHandler


1 /*
2  * Copyright 2002-2005 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.support.lob;
18
19 import java.io.InputStream JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.io.Reader JavaDoc;
22 import java.io.Writer JavaDoc;
23 import java.lang.reflect.InvocationTargetException JavaDoc;
24 import java.lang.reflect.Method JavaDoc;
25 import java.sql.Blob JavaDoc;
26 import java.sql.Clob JavaDoc;
27 import java.sql.Connection JavaDoc;
28 import java.sql.PreparedStatement JavaDoc;
29 import java.sql.ResultSet JavaDoc;
30 import java.sql.SQLException JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.LinkedList JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.Map JavaDoc;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40 import org.springframework.dao.DataAccessResourceFailureException;
41 import org.springframework.dao.InvalidDataAccessApiUsageException;
42 import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
43 import org.springframework.util.FileCopyUtils;
44
45 /**
46  * LobHandler implementation for Oracle databases. Uses proprietary API to
47  * create <code>oracle.sql.BLOB</code> and <code>oracle.sql.CLOB</code>
48  * instances, as necessary when working with Oracle's JDBC driver.
49  * Note that this LobHandler requires Oracle JDBC driver 9i or higher!
50  *
51  * <p>While most databases are able to work with DefaultLobHandler, Oracle just
52  * accepts Blob/Clob instances created via its own proprietary BLOB/CLOB API,
53  * and additionally doesn't accept large streams for PreparedStatement's
54  * corresponding setter methods. Therefore, you need to use a strategy like
55  * this LobHandler implementation.
56  *
57  * <p>Needs to work on a native JDBC Connection, to be able to cast it to
58  * <code>oracle.jdbc.OracleConnection</code>. If you pass in Connections from
59  * a connection pool (the usual case in a J2EE environment), you need to set
60  * an appropriate NativeJdbcExtractor to allow for automatical retrieval of
61  * the underlying native JDBC Connection. LobHandler and NativeJdbcExtractor
62  * are separate concerns, therefore they are represented by separate strategy
63  * interfaces.
64  *
65  * <p>Coded via reflection to avoid dependencies on Oracle classes.
66  * Even reads in Oracle constants via reflection because of different Oracle
67  * drivers (classes12, ojdbc14) having different constant values! As this
68  * LobHandler initializes Oracle classes on instantiation, do not define this
69  * as eager-initializing singleton if you do not want to depend on the Oracle
70  * JAR being in the class path: use "lazy-init=true" to avoid this issue.
71  *
72  * @author Juergen Hoeller
73  * @since 04.12.2003
74  * @see #setNativeJdbcExtractor
75  * @see oracle.sql.BLOB
76  * @see oracle.sql.CLOB
77  */

78 public class OracleLobHandler extends AbstractLobHandler {
79
80     private static final String JavaDoc BLOB_CLASS_NAME = "oracle.sql.BLOB";
81
82     private static final String JavaDoc CLOB_CLASS_NAME = "oracle.sql.CLOB";
83
84     private static final String JavaDoc DURATION_SESSION_FIELD_NAME = "DURATION_SESSION";
85
86     private static final String JavaDoc MODE_READWRITE_FIELD_NAME = "MODE_READWRITE";
87
88
89     protected final Log logger = LogFactory.getLog(getClass());
90
91     private NativeJdbcExtractor nativeJdbcExtractor;
92
93     private Boolean JavaDoc cache = Boolean.TRUE;
94
95     private Class JavaDoc blobClass;
96
97     private Class JavaDoc clobClass;
98
99     private final Map JavaDoc durationSessionConstants = new HashMap JavaDoc(2);
100
101     private final Map JavaDoc modeReadWriteConstants = new HashMap JavaDoc(2);
102
103
104     /**
105      * Set an appropriate NativeJdbcExtractor to be able to retrieve the underlying
106      * native <code>oracle.jdbc.OracleConnection</code>. This is necessary for
107      * DataSource-based connection pools, as those need to return wrapped JDBC
108      * Connection handles that cannot be cast to a native Connection implementation.
109      * <p>Effectively, this LobHandler just invokes a single NativeJdbcExtractor
110      * method, namely <code>getNativeConnectionFromStatement</code> with a
111      * PreparedStatement argument (falling back to a
112      * <code>PreparedStatement.getConnection()</code> call if no extractor is set).
113      * <p>A common choice is SimpleNativeJdbcExtractor, whose Connection unwrapping
114      * (which is what OracleLobHandler needs) will work with many connection pools.
115      * See SimpleNativeJdbcExtractor's javadoc for details.
116      * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor#getNativeConnectionFromStatement
117      * @see org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor
118      * @see oracle.jdbc.OracleConnection
119      */

120     public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
121         this.nativeJdbcExtractor = nativeJdbcExtractor;
122     }
123
124     /**
125      * Set whether to cache the temporary LOB in the buffer cache.
126      * This value will be passed into BLOB/CLOB.createTemporary. Default is "true".
127      * @see oracle.sql.BLOB#createTemporary
128      * @see oracle.sql.CLOB#createTemporary
129      */

130     public void setCache(boolean cache) {
131         this.cache = new Boolean JavaDoc(cache);
132     }
133
134
135     /**
136      * Retrieve the <code>oracle.sql.BLOB</code> and <code>oracle.sql.CLOB</code>
137      * classes via reflection, and initialize the values for the
138      * DURATION_SESSION and MODE_READWRITE constants defined there.
139      * @param con the Oracle Connection, for using the exact same class loader
140      * that the Oracle driver was loaded with
141      * @see oracle.sql.BLOB#DURATION_SESSION
142      * @see oracle.sql.BLOB#MODE_READWRITE
143      * @see oracle.sql.CLOB#DURATION_SESSION
144      * @see oracle.sql.CLOB#MODE_READWRITE
145      */

146     protected synchronized void initOracleDriverClasses(Connection JavaDoc con) {
147         if (this.blobClass == null) {
148             try {
149                 // Initialize oracle.sql.BLOB class
150
this.blobClass = con.getClass().getClassLoader().loadClass(BLOB_CLASS_NAME);
151                 this.durationSessionConstants.put(
152                         this.blobClass, new Integer JavaDoc(this.blobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null)));
153                 this.modeReadWriteConstants.put(
154                         this.blobClass, new Integer JavaDoc(this.blobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null)));
155
156                 // Initialize oracle.sql.CLOB class
157
this.clobClass = con.getClass().getClassLoader().loadClass(CLOB_CLASS_NAME);
158                 this.durationSessionConstants.put(
159                         this.clobClass, new Integer JavaDoc(this.clobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null)));
160                 this.modeReadWriteConstants.put(
161                         this.clobClass, new Integer JavaDoc(this.clobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null)));
162             }
163             catch (Exception JavaDoc ex) {
164                 throw new InvalidDataAccessApiUsageException(
165                         "Couldn't initialize OracleLobHandler because Oracle driver classes are not available. " +
166                         "Note that OracleLobHandler requires Oracle JDBC driver 9i or higher!", ex);
167             }
168         }
169     }
170
171
172     public byte[] getBlobAsBytes(ResultSet JavaDoc rs, int columnIndex) throws SQLException JavaDoc {
173         logger.debug("Returning Oracle BLOB as bytes");
174         Blob JavaDoc blob = rs.getBlob(columnIndex);
175         return (blob != null ? blob.getBytes(1, (int) blob.length()) : null);
176     }
177
178     public InputStream JavaDoc getBlobAsBinaryStream(ResultSet JavaDoc rs, int columnIndex) throws SQLException JavaDoc {
179         logger.debug("Returning Oracle BLOB as binary stream");
180         Blob JavaDoc blob = rs.getBlob(columnIndex);
181         return (blob != null ? blob.getBinaryStream() : null);
182     }
183
184     public String JavaDoc getClobAsString(ResultSet JavaDoc rs, int columnIndex) throws SQLException JavaDoc {
185         logger.debug("Returning Oracle CLOB as string");
186         Clob JavaDoc clob = rs.getClob(columnIndex);
187         return (clob != null ? clob.getSubString(1, (int) clob.length()) : null);
188     }
189
190     public InputStream JavaDoc getClobAsAsciiStream(ResultSet JavaDoc rs, int columnIndex) throws SQLException JavaDoc {
191         logger.debug("Returning Oracle CLOB as ASCII stream");
192         Clob JavaDoc clob = rs.getClob(columnIndex);
193         return (clob != null ? clob.getAsciiStream() : null);
194     }
195
196     public Reader JavaDoc getClobAsCharacterStream(ResultSet JavaDoc rs, int columnIndex) throws SQLException JavaDoc {
197         logger.debug("Returning Oracle CLOB as character stream");
198         Clob JavaDoc clob = rs.getClob(columnIndex);
199         return (clob != null ? clob.getCharacterStream() : null);
200     }
201
202     public LobCreator getLobCreator() {
203         return new OracleLobCreator();
204     }
205
206
207     /**
208      * LobCreator implementation for Oracle databases.
209      * Creates Oracle-style temporary BLOBs and CLOBs that it frees on close.
210      * @see #close
211      */

212     protected class OracleLobCreator implements LobCreator {
213
214         private final List JavaDoc createdLobs = new LinkedList JavaDoc();
215
216         public void setBlobAsBytes(PreparedStatement JavaDoc ps, int paramIndex, final byte[] content)
217                 throws SQLException JavaDoc {
218
219             if (content != null) {
220                 Blob JavaDoc blob = (Blob JavaDoc) createLob(ps, false, new LobCallback() {
221                     public void populateLob(Object JavaDoc lob) throws Exception JavaDoc {
222                         Method JavaDoc methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream", new Class JavaDoc[0]);
223                         OutputStream JavaDoc out = (OutputStream JavaDoc) methodToInvoke.invoke(lob, (Object JavaDoc[]) null);
224                         FileCopyUtils.copy(content, out);
225                     }
226                 });
227                 ps.setBlob(paramIndex, blob);
228                 if (logger.isDebugEnabled()) {
229                     logger.debug("Set bytes for Oracle BLOB with length " + blob.length());
230                 }
231             }
232             else {
233                 ps.setBlob(paramIndex, null);
234                 logger.debug("Set Oracle BLOB to null");
235             }
236         }
237
238         public void setBlobAsBinaryStream(
239                 PreparedStatement JavaDoc ps, int paramIndex, final InputStream JavaDoc binaryStream, int contentLength)
240                 throws SQLException JavaDoc {
241
242             if (binaryStream != null) {
243                 Blob JavaDoc blob = (Blob JavaDoc) createLob(ps, false, new LobCallback() {
244                     public void populateLob(Object JavaDoc lob) throws Exception JavaDoc {
245                         Method JavaDoc methodToInvoke = lob.getClass().getMethod("getBinaryOutputStream", (Class JavaDoc[]) null);
246                         OutputStream JavaDoc out = (OutputStream JavaDoc) methodToInvoke.invoke(lob, (Object JavaDoc[]) null);
247                         FileCopyUtils.copy(binaryStream, out);
248                     }
249                 });
250                 ps.setBlob(paramIndex, blob);
251                 if (logger.isDebugEnabled()) {
252                     logger.debug("Set binary stream for Oracle BLOB with length " + blob.length());
253                 }
254             }
255             else {
256                 ps.setBlob(paramIndex, null);
257                 logger.debug("Set Oracle BLOB to null");
258             }
259         }
260
261         public void setClobAsString(PreparedStatement JavaDoc ps, int paramIndex, final String JavaDoc content)
262             throws SQLException JavaDoc {
263
264             if (content != null) {
265                 Clob JavaDoc clob = (Clob JavaDoc) createLob(ps, true, new LobCallback() {
266                     public void populateLob(Object JavaDoc lob) throws Exception JavaDoc {
267                         Method JavaDoc methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class JavaDoc[]) null);
268                         Writer JavaDoc writer = (Writer JavaDoc) methodToInvoke.invoke(lob, (Object JavaDoc[]) null);
269                         FileCopyUtils.copy(content, writer);
270                     }
271                 });
272                 ps.setClob(paramIndex, clob);
273                 if (logger.isDebugEnabled()) {
274                     logger.debug("Set string for Oracle CLOB with length " + clob.length());
275                 }
276             }
277             else {
278                 ps.setClob(paramIndex, null);
279                 logger.debug("Set Oracle CLOB to null");
280             }
281         }
282
283         public void setClobAsAsciiStream(
284                 PreparedStatement JavaDoc ps, int paramIndex, final InputStream JavaDoc asciiStream, int contentLength)
285             throws SQLException JavaDoc {
286
287             if (asciiStream != null) {
288                 Clob JavaDoc clob = (Clob JavaDoc) createLob(ps, true, new LobCallback() {
289                     public void populateLob(Object JavaDoc lob) throws Exception JavaDoc {
290                         Method JavaDoc methodToInvoke = lob.getClass().getMethod("getAsciiOutputStream", (Class JavaDoc[]) null);
291                         OutputStream JavaDoc out = (OutputStream JavaDoc) methodToInvoke.invoke(lob, (Object JavaDoc[]) null);
292                         FileCopyUtils.copy(asciiStream, out);
293                     }
294                 });
295                 ps.setClob(paramIndex, clob);
296                 if (logger.isDebugEnabled()) {
297                     logger.debug("Set ASCII stream for Oracle CLOB with length " + clob.length());
298                 }
299             }
300             else {
301                 ps.setClob(paramIndex, null);
302                 logger.debug("Set Oracle CLOB to null");
303             }
304         }
305
306         public void setClobAsCharacterStream(
307                 PreparedStatement JavaDoc ps, int paramIndex, final Reader JavaDoc characterStream, int contentLength)
308             throws SQLException JavaDoc {
309
310             if (characterStream != null) {
311                 Clob JavaDoc clob = (Clob JavaDoc) createLob(ps, true, new LobCallback() {
312                     public void populateLob(Object JavaDoc lob) throws Exception JavaDoc {
313                         Method JavaDoc methodToInvoke = lob.getClass().getMethod("getCharacterOutputStream", (Class JavaDoc[]) null);
314                         Writer JavaDoc writer = (Writer JavaDoc) methodToInvoke.invoke(lob, (Object JavaDoc[]) null);
315                         FileCopyUtils.copy(characterStream, writer);
316                     }
317                 });
318                 ps.setClob(paramIndex, clob);
319                 if (logger.isDebugEnabled()) {
320                     logger.debug("Set character stream for Oracle CLOB with length " + clob.length());
321                 }
322             }
323             else {
324                 ps.setClob(paramIndex, null);
325                 logger.debug("Set Oracle CLOB to null");
326             }
327         }
328
329         /**
330          * Create a LOB instance for the given PreparedStatement,
331          * populating it via the given callback.
332          */

333         protected Object JavaDoc createLob(PreparedStatement JavaDoc ps, boolean clob, LobCallback callback)
334                 throws SQLException JavaDoc {
335
336             Connection JavaDoc con = null;
337             try {
338                 con = getOracleConnection(ps);
339                 initOracleDriverClasses(con);
340                 Object JavaDoc lob = prepareLob(con, clob ? clobClass : blobClass);
341                 callback.populateLob(lob);
342                 lob.getClass().getMethod("close", (Class JavaDoc[]) null).invoke(lob, (Object JavaDoc[]) null);
343                 this.createdLobs.add(lob);
344                 if (logger.isDebugEnabled()) {
345                     logger.debug("Created new Oracle " + (clob ? "CLOB" : "BLOB"));
346                 }
347                 return lob;
348             }
349             catch (SQLException JavaDoc ex) {
350                 throw ex;
351             }
352             catch (InvocationTargetException JavaDoc ex) {
353                 if (ex.getTargetException() instanceof SQLException JavaDoc) {
354                     throw (SQLException JavaDoc) ex.getTargetException();
355                 }
356                 else if (con != null && ex.getTargetException() instanceof ClassCastException JavaDoc) {
357                     throw new InvalidDataAccessApiUsageException(
358                             "OracleLobCreator needs to work on [oracle.jdbc.OracleConnection], not on [" +
359                             con.getClass().getName() + "]: specify a corresponding NativeJdbcExtractor",
360                             ex.getTargetException());
361                 }
362                 else {
363                     throw new DataAccessResourceFailureException("Could not create Oracle LOB",
364                             ex.getTargetException());
365                 }
366             }
367             catch (Exception JavaDoc ex) {
368                 throw new DataAccessResourceFailureException("Could not create Oracle LOB", ex);
369             }
370         }
371
372         /**
373          * Retrieve the underlying OracleConnection, using a NativeJdbcExtractor if set.
374          */

375         protected Connection JavaDoc getOracleConnection(PreparedStatement JavaDoc ps)
376                 throws SQLException JavaDoc, ClassNotFoundException JavaDoc {
377
378             return (nativeJdbcExtractor != null) ?
379                     nativeJdbcExtractor.getNativeConnectionFromStatement(ps) : ps.getConnection();
380         }
381
382         /**
383          * Create and open an oracle.sql.BLOB/CLOB instance via reflection.
384          */

385         protected Object JavaDoc prepareLob(Connection JavaDoc con, Class JavaDoc lobClass) throws Exception JavaDoc {
386             /*
387             BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
388             blob.open(BLOB.MODE_READWRITE);
389             return blob;
390             */

391             Method JavaDoc createTemporary = lobClass.getMethod(
392                     "createTemporary", new Class JavaDoc[] {Connection JavaDoc.class, boolean.class, int.class});
393             Object JavaDoc lob = createTemporary.invoke(
394                     null, new Object JavaDoc[] {con, cache, durationSessionConstants.get(lobClass)});
395             Method JavaDoc open = lobClass.getMethod("open", new Class JavaDoc[] {int.class});
396             open.invoke(lob, new Object JavaDoc[] {modeReadWriteConstants.get(lobClass)});
397             return lob;
398         }
399
400         /**
401          * Free all temporary BLOBs and CLOBs created by this creator.
402          */

403         public void close() {
404             try {
405                 for (Iterator JavaDoc it = this.createdLobs.iterator(); it.hasNext();) {
406                     /*
407                     BLOB blob = (BLOB) it.next();
408                     blob.freeTemporary();
409                     */

410                     Object JavaDoc lob = it.next();
411                     Method JavaDoc freeTemporary = lob.getClass().getMethod("freeTemporary", new Class JavaDoc[0]);
412                     freeTemporary.invoke(lob, new Object JavaDoc[0]);
413                     it.remove();
414                 }
415             }
416             catch (InvocationTargetException JavaDoc ex) {
417                 logger.error("Could not free Oracle LOB", ex.getTargetException());
418             }
419             catch (Exception JavaDoc ex) {
420                 throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex);
421             }
422         }
423     }
424
425
426     /**
427      * Internal callback interface for use with createLob.
428      */

429     protected static interface LobCallback {
430
431         /**
432          * Populate the given BLOB or CLOB instance with content.
433          * @throws Exception any exception including InvocationTargetException
434          */

435         void populateLob(Object JavaDoc lob) throws Exception JavaDoc;
436     }
437
438 }
439
Popular Tags