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é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 }