001    package org.apache.ojb.broker.platforms;
002    
003    /* Copyright 2004-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.io.ByteArrayInputStream;
019    import java.lang.reflect.Method;
020    import java.sql.Connection;
021    import java.sql.PreparedStatement;
022    import java.sql.SQLException;
023    import java.sql.Types;
024    import java.util.Collections;
025    import java.util.Map;
026    import java.util.WeakHashMap;
027    
028    import org.apache.ojb.broker.metadata.JdbcType;
029    import org.apache.ojb.broker.util.ClassHelper;
030    import org.apache.ojb.broker.metadata.JdbcTypesHelper;
031    
032    /**
033     * This class is a concrete implementation of <code>Platform</code>. Provides
034     * an implementation that works around some issues with Oracle running within WebLogic. As
035     * WebLogic wraps the Oracle physical connection with its own logical connection it is necessary to
036     * retrieve the underlying physical connection before creating a CLOB or BLOB.
037     *
038     * NOTE : When you use the physical connection WebLogic by default marks it as "infected" and discards it when
039     * the logicical connection is closed.  You can change this behavior by setting the
040     * RemoveInfectedConnectionsEnabled attribute on a connection pool.
041     * see http://e-docs.bea.com/wls/docs81/jdbc/thirdparty.html#1043646
042     *
043     * Optimization: Oracle Batching (not standard JDBC batching)
044     * see http://technet.oracle.com/products/oracle9i/daily/jun07.html
045     *
046     * Optimization: Oracle Prefetching
047     * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html
048     *
049     * TODO: Optimization: use ROWNUM to minimize the effects of not having server side cursors
050     * see http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
051     *
052     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
053     * @author <a href="mailto:erik@cj.com">Erik Forkalsrud</a>
054     * @author <a href="mailto:martin.kalen@curalia.se">Martin Kal&eacute;n</a>
055     * @author <a href="mailto:d441-6iq2@spamex.com">Dougall Squair</a>
056     * @version CVS $Id: PlatformWLOracle9iImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
057     * @see Platform
058     * @see PlatformDefaultImpl
059     * @see PlatformOracleImpl
060     * @see PlatformOracle9iImpl
061     *
062     * @deprecated since OJB 1.0.2 the default {@link PlatformOracle9iImpl} should be usable in WebLogic
063     */
064    public class PlatformWLOracle9iImpl extends PlatformOracleImpl
065    {
066        protected static final int ROW_PREFETCH_SIZE = 100;
067    
068        // From Oracle9i JDBC Developer's Guide and Reference:
069        // "Batch values between 5 and 30 tend to be the most effective."
070        protected static final int STATEMENTS_PER_BATCH = 20;
071        protected static Map m_batchStatementsInProgress = Collections.synchronizedMap(new WeakHashMap(STATEMENTS_PER_BATCH));
072    
073        protected static final Class[] PARAM_TYPE_INTEGER = {Integer.TYPE};
074        protected static final Class[] PARAM_TYPE_BOOLEAN = {Boolean.TYPE};
075        protected static final Class[] PARAM_TYPE_STRING = {String.class};
076    
077        protected static final Object[] PARAM_ROW_PREFETCH_SIZE = new Object[]{new Integer(ROW_PREFETCH_SIZE)};
078        protected static final Object[] PARAM_STATEMENT_BATCH_SIZE = new Object[]{new Integer(STATEMENTS_PER_BATCH)};
079        protected static final Object[] PARAM_BOOLEAN_TRUE = new Object[]{Boolean.TRUE};
080    
081        protected static final JdbcType BASE_CLOB = JdbcTypesHelper.getJdbcTypeByName("clob");
082        protected static final JdbcType BASE_BLOB = JdbcTypesHelper.getJdbcTypeByName("blob");
083    
084    
085    
086        /**
087         * Enables Oracle row prefetching if supported.
088         * See http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html.
089         * This is RDBMS server-to-client prefetching and thus one layer below
090         * the OJB-internal prefetching-to-cache introduced in version 1.0rc5.
091         * @param stmt the statement just created
092         * @throws PlatformException upon JDBC failure
093         */
094        public void afterStatementCreate(java.sql.Statement stmt) throws PlatformException
095        {
096            super.afterStatementCreate(stmt);
097    
098            // Check for OracleStatement-specific row prefetching support
099            final Method methodSetRowPrefetch;
100            methodSetRowPrefetch = ClassHelper.getMethod(stmt, "setRowPrefetch", PARAM_TYPE_INTEGER);
101    
102            final boolean rowPrefetchingSupported = methodSetRowPrefetch != null;
103            if (rowPrefetchingSupported)
104            {
105                try
106                {
107                    // Set number of prefetched rows
108                    methodSetRowPrefetch.invoke(stmt, PARAM_ROW_PREFETCH_SIZE);
109                }
110                catch (Exception e)
111                {
112                    throw new PlatformException(e.getLocalizedMessage(), e);
113                }
114            }
115        }
116    
117        /**
118         * Try Oracle update batching and call setExecuteBatch or revert to
119         * JDBC update batching. See 12-2 Update Batching in the Oracle9i
120         * JDBC Developer's Guide and Reference.
121         * @param stmt the prepared statement to be used for batching
122         * @throws PlatformException upon JDBC failure
123         */
124        public void beforeBatch(PreparedStatement stmt) throws PlatformException
125        {
126            // Check for Oracle batching support
127            final Method methodSetExecuteBatch;
128            final Method methodSendBatch;
129            methodSetExecuteBatch = ClassHelper.getMethod(stmt, "setExecuteBatch", PARAM_TYPE_INTEGER);
130            methodSendBatch = ClassHelper.getMethod(stmt, "sendBatch", null);
131    
132            final boolean statementBatchingSupported = methodSetExecuteBatch != null && methodSendBatch != null;
133            if (statementBatchingSupported)
134            {
135                try
136                {
137                    // Set number of statements per batch
138                    methodSetExecuteBatch.invoke(stmt, PARAM_STATEMENT_BATCH_SIZE);
139                    m_batchStatementsInProgress.put(stmt, methodSendBatch);
140                }
141                catch (Exception e)
142                {
143                    throw new PlatformException(e.getLocalizedMessage(), e);
144                }
145            }
146            else
147            {
148                super.beforeBatch(stmt);
149            }
150        }
151    
152        /**
153         * Try Oracle update batching and call executeUpdate or revert to
154         * JDBC update batching.
155         * @param stmt the statement beeing added to the batch
156         * @throws PlatformException upon JDBC failure
157         */
158        public void addBatch(PreparedStatement stmt) throws PlatformException
159        {
160            // Check for Oracle batching support
161            final boolean statementBatchingSupported = m_batchStatementsInProgress.containsKey(stmt);
162            if (statementBatchingSupported)
163            {
164                try
165                {
166                    stmt.executeUpdate();
167                }
168                catch (SQLException e)
169                {
170                    throw new PlatformException(e.getLocalizedMessage(), e);
171                }
172            }
173            else
174            {
175                super.addBatch(stmt);
176            }
177        }
178    
179        /**
180         * Try Oracle update batching and call sendBatch or revert to
181         * JDBC update batching.
182         * @param stmt the batched prepared statement about to be executed
183         * @return always <code>null</code> if Oracle update batching is used,
184         * since it is impossible to dissolve total row count into distinct
185         * statement counts. If JDBC update batching is used, an int array is
186         * returned containing number of updated rows for each batched statement.
187         * @throws PlatformException upon JDBC failure
188         */
189        public int[] executeBatch(PreparedStatement stmt) throws PlatformException
190        {
191            // Check for Oracle batching support
192            final Method methodSendBatch = (Method) m_batchStatementsInProgress.remove(stmt);
193            final boolean statementBatchingSupported = methodSendBatch != null;
194    
195            int[] retval = null;
196            if (statementBatchingSupported)
197            {
198                try
199                {
200                    // sendBatch() returns total row count as an Integer
201                    methodSendBatch.invoke(stmt, null);
202                }
203                catch (Exception e)
204                {
205                    throw new PlatformException(e.getLocalizedMessage(), e);
206                }
207            }
208            else
209            {
210                retval = super.executeBatch(stmt);
211            }
212            return retval;
213        }
214    
215        /** @see Platform#setObjectForStatement */
216        public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType) throws SQLException
217        {
218            boolean blobHandlingSupported = false;
219            boolean clobHandlingSupported = false;
220            Method methodSetBlob = null;
221            Method methodSetClob = null;
222            Method methodGetVendorConnection = null;
223    
224            // Check for Oracle JDBC-driver LOB-support
225            if (sqlType == Types.CLOB)
226            {
227                try
228                {
229                    Class clobClass = ClassHelper.getClass("oracle.sql.CLOB", false);
230                    methodSetClob = ClassHelper.getMethod(ps, "setCLOB", new Class[]{Integer.TYPE, clobClass});
231                    methodGetVendorConnection = ClassHelper.getMethod(ps.getConnection(), "getVendorConnection",
232                            new Class[]{});
233                    clobHandlingSupported = methodSetClob != null && methodGetVendorConnection != null;
234                }
235                catch (Exception ignore)
236                {
237                    // ignore it
238                }
239            }
240            else if (sqlType == Types.BLOB)
241            {
242                try
243                {
244                    Class blobClass = ClassHelper.getClass("oracle.sql.BLOB", false);
245                    methodSetBlob = ClassHelper.getMethod(ps, "setBLOB", new Class[]{Integer.TYPE, blobClass});
246                    methodGetVendorConnection = ClassHelper.getMethod(ps.getConnection(), "getVendorConnection",
247                            new Class[]{});
248                    blobHandlingSupported = methodSetBlob != null && methodGetVendorConnection != null;
249                }
250                catch (Exception ignore)
251                {
252                    // ignore it
253                }
254            }
255    
256            // Type-specific Oracle conversions
257            if (((sqlType == Types.VARBINARY) || (sqlType == Types.LONGVARBINARY)) && (value instanceof byte[]))
258            {
259                byte buf[] = (byte[]) value;
260                ByteArrayInputStream inputStream = new ByteArrayInputStream(buf);
261                super.changePreparedStatementResultSetType(ps);
262                ps.setBinaryStream(index, inputStream, buf.length);
263            }
264            else if (value instanceof Double)
265            {
266                // workaround for the bug in Oracle thin driver
267                ps.setDouble(index, ((Double) value).doubleValue());
268            }
269            else if (sqlType == Types.BIGINT && value instanceof Integer)
270            {
271                // workaround: Oracle thin driver problem when expecting long
272                ps.setLong(index, ((Integer) value).intValue());
273            }
274            else if (sqlType == Types.INTEGER && value instanceof Long)
275            {
276                ps.setLong(index, ((Long) value).longValue());
277            }
278            else if (sqlType == Types.CLOB && clobHandlingSupported && value instanceof String)
279            {
280                // TODO: If using Oracle update batching with the thin driver, throw exception on 4k limit
281                try
282                {
283                    Connection vendorConnection = (Connection) methodGetVendorConnection.invoke(ps.getConnection(),
284                            new Object[]{});
285                    Object clob = Oracle9iLobHandler.createCLOBFromString(vendorConnection, (String) value);
286                    methodSetClob.invoke(ps, new Object[]{new Integer(index), clob});
287                }
288                catch (Exception e)
289                {
290                    throw new SQLException(e.getLocalizedMessage());
291                }
292            }
293            else if (sqlType == Types.BLOB && blobHandlingSupported && value instanceof byte[])
294            {
295                // TODO: If using Oracle update batching with the thin driver, throw exception on 2k limit
296                try
297                {
298                    Connection vendorConnection = (Connection) methodGetVendorConnection.invoke(ps.getConnection(),
299                            new Object[]{});
300                    Object blob = Oracle9iLobHandler.createBLOBFromByteArray(vendorConnection, (byte[]) value);
301                    methodSetBlob.invoke(ps, new Object[]{new Integer(index), blob});
302                }
303                catch (Exception e)
304                {
305                    throw new SQLException(e.getLocalizedMessage());
306                }
307            }
308            else
309            {
310                // Fall-through to superclass
311                super.setObjectForStatement(ps, index, value, sqlType);
312            }
313        }
314    
315    }