001/*
002 * Copyright 2002-2014 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.ByteArrayInputStream;
020import java.io.InputStream;
021import java.io.InputStreamReader;
022import java.io.Reader;
023import java.io.StringReader;
024import java.io.UnsupportedEncodingException;
025import java.sql.Blob;
026import java.sql.Clob;
027import java.sql.PreparedStatement;
028import java.sql.ResultSet;
029import java.sql.SQLException;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033
034/**
035 * Default implementation of the {@link LobHandler} interface.
036 * Invokes the direct accessor methods that {@code java.sql.ResultSet}
037 * and {@code java.sql.PreparedStatement} offer.
038 *
039 * <p>By default, incoming streams are going to be passed to the appropriate
040 * {@code setBinary/Ascii/CharacterStream} method on the JDBC driver's
041 * {@link PreparedStatement}. If the specified content length is negative,
042 * this handler will use the JDBC 4.0 variants of the set-stream methods
043 * without a length parameter; otherwise, it will pass the specified length
044 * on to the driver.
045 *
046 * <p>This LobHandler should work for any JDBC driver that is JDBC compliant
047 * in terms of the spec's suggestions regarding simple BLOB and CLOB handling.
048 * This does not apply to Oracle 9i's drivers at all; as of Oracle 10g,
049 * it does work but may still come with LOB size limitations. Consider using
050 * recent Oracle drivers even when working against an older database server.
051 * See the {@link LobHandler} javadoc for the full set of recommendations.
052 *
053 * <p>Some JDBC drivers require values with a BLOB/CLOB target column to be
054 * explicitly set through the JDBC {@code setBlob} / {@code setClob} API:
055 * for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"}
056 * property to "true" when operating against such a driver.
057 *
058 * <p>On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content
059 * via the {@code setBlob} / {@code setClob} variants that take a stream
060 * argument directly. Consider switching the {@link #setStreamAsLob "streamAsLob"}
061 * property to "true" when operating against a fully compliant JDBC 4.0 driver.
062 *
063 * <p>Finally, primarily as a direct equivalent to {@link OracleLobHandler},
064 * this LobHandler also supports the creation of temporary BLOB/CLOB objects.
065 * Consider switching the {@link #setCreateTemporaryLob "createTemporaryLob"}
066 * property to "true" when "streamAsLob" happens to run into LOB size limitations.
067 *
068 * <p>See the {@link LobHandler} interface javadoc for a summary of recommendations.
069 *
070 * @author Juergen Hoeller
071 * @since 04.12.2003
072 * @see java.sql.ResultSet#getBytes
073 * @see java.sql.ResultSet#getBinaryStream
074 * @see java.sql.ResultSet#getString
075 * @see java.sql.ResultSet#getAsciiStream
076 * @see java.sql.ResultSet#getCharacterStream
077 * @see java.sql.PreparedStatement#setBytes
078 * @see java.sql.PreparedStatement#setBinaryStream
079 * @see java.sql.PreparedStatement#setString
080 * @see java.sql.PreparedStatement#setAsciiStream
081 * @see java.sql.PreparedStatement#setCharacterStream
082 */
083public class DefaultLobHandler extends AbstractLobHandler {
084
085        protected final Log logger = LogFactory.getLog(getClass());
086
087        private boolean wrapAsLob = false;
088
089        private boolean streamAsLob = false;
090
091        private boolean createTemporaryLob = false;
092
093
094        /**
095         * Specify whether to submit a byte array / String to the JDBC driver
096         * wrapped in a JDBC Blob / Clob object, using the JDBC {@code setBlob} /
097         * {@code setClob} method with a Blob / Clob argument.
098         * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
099         * / {@code setCharacterStream} method for setting the content. Switch this
100         * to "true" for explicit Blob / Clob wrapping against JDBC drivers that
101         * are known to require such wrapping (e.g. PostgreSQL's for access to OID
102         * columns, whereas BYTEA columns need to be accessed the standard way).
103         * <p>This setting affects byte array / String arguments as well as stream
104         * arguments, unless {@link #setStreamAsLob "streamAsLob"} overrides this
105         * handling to use JDBC 4.0's new explicit streaming support (if available).
106         * @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob)
107         * @see java.sql.PreparedStatement#setClob(int, java.sql.Clob)
108         */
109        public void setWrapAsLob(boolean wrapAsLob) {
110                this.wrapAsLob = wrapAsLob;
111        }
112
113        /**
114         * Specify whether to submit a binary stream / character stream to the JDBC
115         * driver as explicit LOB content, using the JDBC 4.0 {@code setBlob} /
116         * {@code setClob} method with a stream argument.
117         * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
118         * / {@code setCharacterStream} method for setting the content.
119         * Switch this to "true" for explicit JDBC 4.0 streaming, provided that your
120         * JDBC driver actually supports those JDBC 4.0 operations (e.g. Derby's).
121         * <p>This setting affects stream arguments as well as byte array / String
122         * arguments, requiring JDBC 4.0 support. For supporting LOB content against
123         * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting.
124         * @see java.sql.PreparedStatement#setBlob(int, java.io.InputStream, long)
125         * @see java.sql.PreparedStatement#setClob(int, java.io.Reader, long)
126         */
127        public void setStreamAsLob(boolean streamAsLob) {
128                this.streamAsLob = streamAsLob;
129        }
130
131        /**
132         * Specify whether to copy a byte array / String into a temporary JDBC
133         * Blob / Clob object created through the JDBC 4.0 {@code createBlob} /
134         * {@code createClob} methods.
135         * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
136         * / {@code setCharacterStream} method for setting the content. Switch this
137         * to "true" for explicit Blob / Clob creation using JDBC 4.0.
138         * <p>This setting affects stream arguments as well as byte array / String
139         * arguments, requiring JDBC 4.0 support. For supporting LOB content against
140         * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting.
141         * @see java.sql.Connection#createBlob()
142         * @see java.sql.Connection#createClob()
143         */
144        public void setCreateTemporaryLob(boolean createTemporaryLob) {
145                this.createTemporaryLob = createTemporaryLob;
146        }
147
148
149        @Override
150        public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException {
151                logger.debug("Returning BLOB as bytes");
152                if (this.wrapAsLob) {
153                        Blob blob = rs.getBlob(columnIndex);
154                        return blob.getBytes(1, (int) blob.length());
155                }
156                else {
157                        return rs.getBytes(columnIndex);
158                }
159        }
160
161        @Override
162        public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException {
163                logger.debug("Returning BLOB as binary stream");
164                if (this.wrapAsLob) {
165                        Blob blob = rs.getBlob(columnIndex);
166                        return blob.getBinaryStream();
167                }
168                else {
169                        return rs.getBinaryStream(columnIndex);
170                }
171        }
172
173        @Override
174        public String getClobAsString(ResultSet rs, int columnIndex) throws SQLException {
175                logger.debug("Returning CLOB as string");
176                if (this.wrapAsLob) {
177                        Clob clob = rs.getClob(columnIndex);
178                        return clob.getSubString(1, (int) clob.length());
179                }
180                else {
181                        return rs.getString(columnIndex);
182                }
183        }
184
185        @Override
186        public InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException {
187                logger.debug("Returning CLOB as ASCII stream");
188                if (this.wrapAsLob) {
189                        Clob clob = rs.getClob(columnIndex);
190                        return clob.getAsciiStream();
191                }
192                else {
193                        return rs.getAsciiStream(columnIndex);
194                }
195        }
196
197        @Override
198        public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException {
199                logger.debug("Returning CLOB as character stream");
200                if (this.wrapAsLob) {
201                        Clob clob = rs.getClob(columnIndex);
202                        return clob.getCharacterStream();
203                }
204                else {
205                        return rs.getCharacterStream(columnIndex);
206                }
207        }
208
209        @Override
210        public LobCreator getLobCreator() {
211                return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator());
212        }
213
214
215        /**
216         * Default LobCreator implementation as an inner class.
217         * Can be subclassed in DefaultLobHandler extensions.
218         */
219        protected class DefaultLobCreator implements LobCreator {
220
221                @Override
222                public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content)
223                                throws SQLException {
224
225                        if (streamAsLob) {
226                                if (content != null) {
227                                        ps.setBlob(paramIndex, new ByteArrayInputStream(content), content.length);
228                                }
229                                else {
230                                        ps.setBlob(paramIndex, (Blob) null);
231                                }
232                        }
233                        else if (wrapAsLob) {
234                                if (content != null) {
235                                        ps.setBlob(paramIndex, new PassThroughBlob(content));
236                                }
237                                else {
238                                        ps.setBlob(paramIndex, (Blob) null);
239                                }
240                        }
241                        else {
242                                ps.setBytes(paramIndex, content);
243                        }
244                        if (logger.isDebugEnabled()) {
245                                logger.debug(content != null ? "Set bytes for BLOB with length " + content.length :
246                                                "Set BLOB to null");
247                        }
248                }
249
250                @Override
251                public void setBlobAsBinaryStream(
252                                PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
253                                throws SQLException {
254
255                        if (streamAsLob) {
256                                if (binaryStream != null) {
257                                        if (contentLength >= 0) {
258                                                ps.setBlob(paramIndex, binaryStream, contentLength);
259                                        }
260                                        else {
261                                                ps.setBlob(paramIndex, binaryStream);
262                                        }
263                                }
264                                else {
265                                        ps.setBlob(paramIndex, (Blob) null);
266                                }
267                        }
268                        else if (wrapAsLob) {
269                                if (binaryStream != null) {
270                                        ps.setBlob(paramIndex, new PassThroughBlob(binaryStream, contentLength));
271                                }
272                                else {
273                                        ps.setBlob(paramIndex, (Blob) null);
274                                }
275                        }
276                        else if (contentLength >= 0) {
277                                ps.setBinaryStream(paramIndex, binaryStream, contentLength);
278                        }
279                        else {
280                                ps.setBinaryStream(paramIndex, binaryStream);
281                        }
282                        if (logger.isDebugEnabled()) {
283                                logger.debug(binaryStream != null ? "Set binary stream for BLOB with length " + contentLength :
284                                                "Set BLOB to null");
285                        }
286                }
287
288                @Override
289                public void setClobAsString(PreparedStatement ps, int paramIndex, String content)
290                                throws SQLException {
291
292                        if (streamAsLob) {
293                                if (content != null) {
294                                        ps.setClob(paramIndex, new StringReader(content), content.length());
295                                }
296                                else {
297                                        ps.setClob(paramIndex, (Clob) null);
298                                }
299                        }
300                        else if (wrapAsLob) {
301                                if (content != null) {
302                                        ps.setClob(paramIndex, new PassThroughClob(content));
303                                }
304                                else {
305                                        ps.setClob(paramIndex, (Clob) null);
306                                }
307                        }
308                        else {
309                                ps.setString(paramIndex, content);
310                        }
311                        if (logger.isDebugEnabled()) {
312                                logger.debug(content != null ? "Set string for CLOB with length " + content.length() :
313                                                "Set CLOB to null");
314                        }
315                }
316
317                @Override
318                public void setClobAsAsciiStream(
319                                PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength)
320                                throws SQLException {
321
322                        if (streamAsLob) {
323                                if (asciiStream != null) {
324                                        try {
325                                                Reader reader = new InputStreamReader(asciiStream, "US-ASCII");
326                                                if (contentLength >= 0) {
327                                                        ps.setClob(paramIndex, reader, contentLength);
328                                                }
329                                                else {
330                                                        ps.setClob(paramIndex, reader);
331                                                }
332                                        }
333                                        catch (UnsupportedEncodingException ex) {
334                                                throw new SQLException("US-ASCII encoding not supported: " + ex);
335                                        }
336                                }
337                                else {
338                                        ps.setClob(paramIndex, (Clob) null);
339                                }
340                        }
341                        else if (wrapAsLob) {
342                                if (asciiStream != null) {
343                                        ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength));
344                                }
345                                else {
346                                        ps.setClob(paramIndex, (Clob) null);
347                                }
348                        }
349                        else if (contentLength >= 0) {
350                                ps.setAsciiStream(paramIndex, asciiStream, contentLength);
351                        }
352                        else {
353                                ps.setAsciiStream(paramIndex, asciiStream);
354                        }
355                        if (logger.isDebugEnabled()) {
356                                logger.debug(asciiStream != null ? "Set ASCII stream for CLOB with length " + contentLength :
357                                                "Set CLOB to null");
358                        }
359                }
360
361                @Override
362                public void setClobAsCharacterStream(
363                                PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength)
364                                throws SQLException {
365
366                        if (streamAsLob) {
367                                if (characterStream != null) {
368                                        if (contentLength >= 0) {
369                                                ps.setClob(paramIndex, characterStream, contentLength);
370                                        }
371                                        else {
372                                                ps.setClob(paramIndex, characterStream);
373                                        }
374                                }
375                                else {
376                                        ps.setClob(paramIndex, (Clob) null);
377                                }
378                        }
379                        else if (wrapAsLob) {
380                                if (characterStream != null) {
381                                        ps.setClob(paramIndex, new PassThroughClob(characterStream, contentLength));
382                                }
383                                else {
384                                        ps.setClob(paramIndex, (Clob) null);
385                                }
386                        }
387                        else if (contentLength >= 0) {
388                                ps.setCharacterStream(paramIndex, characterStream, contentLength);
389                        }
390                        else {
391                                ps.setCharacterStream(paramIndex, characterStream);
392                        }
393                        if (logger.isDebugEnabled()) {
394                                logger.debug(characterStream != null ? "Set character stream for CLOB with length " + contentLength :
395                                                "Set CLOB to null");
396                        }
397                }
398
399                @Override
400                public void close() {
401                        // nothing to do when not creating temporary LOBs
402                }
403        }
404
405}