001    package org.apache.ojb.broker.platforms;
002    
003    /* Copyright 2003-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 org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
019    import org.apache.ojb.broker.metadata.ConnectionPoolDescriptor;
020    import org.apache.ojb.broker.util.ClassHelper;
021    import org.apache.ojb.broker.util.logging.Logger;
022    import org.apache.ojb.broker.util.logging.LoggerFactory;
023    
024    import java.io.ByteArrayInputStream;
025    import java.lang.reflect.Field;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.sql.Connection;
029    import java.sql.DatabaseMetaData;
030    import java.sql.PreparedStatement;
031    import java.sql.SQLException;
032    import java.sql.Statement;
033    import java.sql.Types;
034    import java.util.Collections;
035    import java.util.Map;
036    import java.util.WeakHashMap;
037    
038    /**
039     * This class is a concrete implementation of <code>Platform</code>. Provides
040     * an implementation that works around some issues with Oracle in general and
041     * Oracle 9i's Thin driver in particular.
042     *
043     * NOTE: When using BEA WebLogic and BLOB/CLOB datatypes, the physical connection will be
044     * used causing WebLogic to mark it as "infected" and discard it when
045     * the logicical connection is closed. You can change this behavior by setting the
046     * RemoveInfectedConnectionsEnabled attribute on a connection pool.
047     * see <a href="http://e-docs.bea.com/wls/docs81/jdbc/thirdparty.html#1043646">WebLogic docs</a>.
048     *
049     * Optimization: Oracle Batching (not standard JDBC batching)
050     * see http://technet.oracle.com/products/oracle9i/daily/jun07.html
051     *
052     * Optimization: Oracle Prefetching
053     * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html
054     *
055     * Optimization: Oracle Statement Caching
056     * see http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/jdbc30/StmtCacheSample/Readme.html
057     *
058     * TODO: Optimization: use ROWNUM to minimize the effects of not having server side cursors
059     * see http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
060     *
061     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
062     * @author <a href="mailto:mkalen@apache.org">Martin Kal&eacute;n</a>
063     * @author Contributions from: Erik Forkalsrud, Danilo Tommasina, Thierry Hanot, Don Lyon
064     * @version CVS $Id: PlatformOracle9iImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
065     * @see Platform
066     * @see PlatformDefaultImpl
067     * @see PlatformOracleImpl
068     */
069    public class PlatformOracle9iImpl extends PlatformOracleImpl
070    {
071        private Logger logger = LoggerFactory.getLogger(PlatformOracle9iImpl.class);
072    
073        /**
074         * Number of cached statements per connection,
075         * when using implicit caching with OracleConnections.
076         * Set in {@link #initializeJdbcConnection}.
077         * @see <a href="http://www.apache.org/~mkalen/ojb/broker-tests.html">Profiling page</a>
078         * for a discussion re sizing
079         */
080        protected static final int STATEMENT_CACHE_SIZE = 10;
081        /**
082         * Number of rows pre-fetched by the JDBC-driver for each executed query,
083         * when using Oracle row pre-fetching with OracleConnections.
084         * Set in {@link #initializeJdbcConnection}.
085         * <p>
086         * <em>Note</em>: this setting can be overridden by specifying a
087         * connection-pool attribute with name="jdbc.defaultRowPrefetch".
088         * Oracle JDBC-driver default value=10.
089         */
090        protected static final int ROW_PREFETCH_SIZE = 20;
091    
092        // From Oracle9i JDBC Developer's Guide and Reference:
093        // "Batch values between 5 and 30 tend to be the most effective."
094        protected static final int STATEMENTS_PER_BATCH = 20;
095        protected static Map m_batchStatementsInProgress = Collections.synchronizedMap(new WeakHashMap(STATEMENTS_PER_BATCH));
096    
097        protected static final Class[] PARAM_TYPE_EMPTY = {};
098        protected static final Class[] PARAM_TYPE_INTEGER = {Integer.TYPE};
099        protected static final Class[] PARAM_TYPE_BOOLEAN = {Boolean.TYPE};
100        protected static final Class[] PARAM_TYPE_STRING = {String.class};
101    
102        protected static final Object[] PARAM_EMPTY = new Object[]{};
103        protected static final Object[] PARAM_STATEMENT_CACHE_SIZE = new Object[]{new Integer(STATEMENT_CACHE_SIZE)};
104        protected static final Object[] PARAM_ROW_PREFETCH_SIZE = new Object[]{new Integer(ROW_PREFETCH_SIZE)};
105        protected static final Object[] PARAM_STATEMENT_BATCH_SIZE = new Object[]{new Integer(STATEMENTS_PER_BATCH)};
106        protected static final Object[] PARAM_BOOLEAN_TRUE = new Object[]{Boolean.TRUE};
107    
108        protected static final String JBOSS_CONN_NAME =
109                "org.jboss.resource.adapter.jdbc.WrappedConnection";
110        protected static Class JBOSS_CONN_CLASS = null;
111    
112        protected static Class ORA_CONN_CLASS;
113        protected static Class ORA_PS_CLASS;
114        protected static Class ORA_CLOB_CLASS;
115        protected static Class ORA_BLOB_CLASS;
116        protected static Class[] PARAM_TYPE_INT_ORACLOB;
117        protected static Class[] PARAM_TYPE_INT_ORABLOB;
118        protected static Method METHOD_SET_STATEMENT_CACHE_SIZE;
119        protected static Method METHOD_SET_IMPLICIT_CACHING_ENABLED;
120        protected static Method METHOD_SET_ROW_PREFETCH;
121        protected static Method METHOD_SET_BLOB = null;
122        protected static Method METHOD_SET_CLOB = null;
123        protected static boolean ORA_STATEMENT_CACHING_AVAILABLE;
124        protected static boolean ORA_ROW_PREFETCH_AVAILABLE;
125        protected static boolean ORA_CLOB_HANDLING_AVAILABLE;
126        protected static boolean ORA_BLOB_HANDLING_AVAILABLE;
127    
128        /** Method names used by {@link #unwrapConnection}. */
129        protected static final String UNWRAP_CONN_METHOD_NAMES[] =
130                {
131                    "unwrapCompletely"          /* Oracle 10g */,
132                    "getInnermostDelegate"      /* Commons DBCP */,
133                    "getUnderlyingConnection"   /* JBoss */,
134                    "getVendorConnection"       /* BEA WebLogic */,
135                    "getJDBC"                   /* P6Spy */
136                };
137        /**
138         * Method parameter signature used by {@link #unwrapConnection} for corresponding
139         * {@link #UNWRAP_CONN_METHOD_NAMES}-index.
140         * If signature is not {@link #PARAM_TYPE_EMPTY}, the actual connection object
141         * will be passed at runtime. (NB: Requires special handling of param type in constructor.)
142         */
143        protected static final Class[][] UNWRAP_CONN_PARAM_TYPES =
144                {
145                    null  /* Index 0 reserved for Oracle 10g - initialized in constructor */,
146                    PARAM_TYPE_EMPTY            /* Commons DBCP */,
147                    PARAM_TYPE_EMPTY            /* JBoss */,
148                    PARAM_TYPE_EMPTY            /* BEA WebLogic */,
149                    PARAM_TYPE_EMPTY            /* P6Spy */
150                };
151        /** Method names used by {@link #unwrapStatement}. */
152        protected static final String UNWRAP_PS_METHOD_NAMES[] =
153                {
154                    "getInnermostDelegate"      /* Commons DBCP */,
155                    "getUnderlyingStatement"    /* JBoss */,
156                    "getJDBC"                   /* P6Spy */,
157                    "ps"                                            /* XAPool, PATCHED: also genericUnwrap modified to unwrap fields */
158                };
159        /**
160         * Method parameter signature used by {@link #unwrapStatement} for corresponding
161         * {@link #UNWRAP_PS_METHOD_NAMES}-index.
162         * If signature is not {@link #PARAM_TYPE_EMPTY}, the actual Statement object
163         * will be passed at runtime. (NB: Requires special handling of param type in constructor.)
164         */
165        protected static final Class[][] UNWRAP_PS_PARAM_TYPES =
166                {
167                    PARAM_TYPE_EMPTY            /* Commons DBCP */,
168                    PARAM_TYPE_EMPTY            /* JBoss */,
169                    PARAM_TYPE_EMPTY            /* P6Spy */,
170                    PARAM_TYPE_EMPTY                        /* XAPool */
171                };
172    
173    
174        /**
175         * Default constructor.
176         */
177        public PlatformOracle9iImpl()
178        {
179            super();
180        }
181    
182        /**
183         * Enables Oracle statement caching and row prefetching if supported by the JDBC-driver.
184         * @param jcd the OJB <code>JdbcConnectionDescriptor</code> (metadata) for the connection to be initialized
185         * @param conn the <code>Connection</code>-object (physical) to be initialized
186         * @see PlatformDefaultImpl#initializeJdbcConnection
187         * @see <a href="http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/jdbc30/StmtCacheSample/Readme.html">
188         * Oracle TechNet Statement Caching Sample</a>
189         * @see <a href="http://otn.oracle.com/sample_code/tech/java/sqlj_jdbc/files/advanced/RowPrefetchSample/Readme.html">
190         * Oracle TechNet Row Pre-fetch Sample<a>
191         */
192        public void initializeJdbcConnection(final JdbcConnectionDescriptor jcd,
193                                             final Connection conn)
194                throws PlatformException
195        {
196            // Do all the generic initialization in PlatformDefaultImpl first
197            super.initializeJdbcConnection(jcd, conn);
198    
199            // Check for managed environments known to reject Oracle extension at this level
200            // (saves us from trying to unwrap just to catch exceptions next)
201            final Class connClass = conn.getClass();
202            if (JBOSS_CONN_CLASS != null && JBOSS_CONN_CLASS.isAssignableFrom(connClass))
203            {
204                if (logger.isDebugEnabled())
205                {
206                    logger.debug("JBoss detected, Oracle Connection tuning left to J2EE container.");
207                }
208                return;
209            }
210    
211            // Check if this is a wrapped connection and if so unwrap it
212            final Connection oraConn = unwrapConnection(conn);
213            if (oraConn == null)
214            {
215                return;
216            }
217    
218            // At this point we know that we have an OracleConnection instance and can thus
219            // try to invoke methods via reflection (if available)
220            if (ORA_STATEMENT_CACHING_AVAILABLE)
221            {
222                try
223                {
224                    // Set number of cached statements and enable implicit caching
225                    METHOD_SET_STATEMENT_CACHE_SIZE.invoke(oraConn, PARAM_STATEMENT_CACHE_SIZE);
226                    METHOD_SET_IMPLICIT_CACHING_ENABLED.invoke(oraConn, PARAM_BOOLEAN_TRUE);
227                }
228                catch (Exception e)
229                {
230                    if (logger.isDebugEnabled())
231                    {
232                        logger.debug("PlatformOracle9iImpl could not enable Oracle statement caching."
233                                     + " Original/unwrapped connection classes="
234                                     + connClass.getName() + "/" + oraConn.getClass().getName());
235                    }
236                }
237            }
238    
239            /*
240            mkalen: Note from the Oracle documentation:
241                Do not mix the JDBC 2.0 fetch size API and the Oracle row prefetching API
242                in your application. You can use one or the other, but not both.
243            */
244            final ConnectionPoolDescriptor cpd = jcd.getConnectionPoolDescriptor();
245            final int cpdFetchSizeHint = cpd.getFetchSize();
246            if (cpdFetchSizeHint == 0 && ORA_ROW_PREFETCH_AVAILABLE)
247            {
248                try
249                {
250                    final String prefetchFromJcd;
251                    prefetchFromJcd = cpd.getJdbcProperties().getProperty("defaultRowPrefetch");
252                    if (prefetchFromJcd == null)
253                    {
254                        METHOD_SET_ROW_PREFETCH.invoke(oraConn, PARAM_ROW_PREFETCH_SIZE);
255                    }
256                    // Else, number of prefetched rows were set via Properties on Connection
257                }
258                catch (Exception e)
259                {
260                    if (logger.isDebugEnabled())
261                    {
262                        logger.debug("PlatformOracle9iImpl could not enable Oracle row pre-fetching."
263                                     + "Original/unwrapped connection classes="
264                                     + connClass.getName() + "/" + oraConn.getClass().getName());
265                    }
266                }
267            }
268        }
269    
270        /**
271         * Performs platform-specific operations on each statement.
272         * @param stmt the statement just created
273         */
274        public void afterStatementCreate(Statement stmt)
275        {
276            // mkalen:  do NOT call super#afterStatementCreate since escape processing for SQL92
277            //          syntax is enabled by default for Oracle9i and higher, and explicit calls
278            //          to setEscapeProcessing for PreparedStatements will make Oracle 10g JDBC-
279            //          driver throw exceptions (and is functionally useless).
280        }
281    
282        /**
283         * Try Oracle update batching and call setExecuteBatch or revert to
284         * JDBC update batching. See 12-2 Update Batching in the Oracle9i
285         * JDBC Developer's Guide and Reference.
286         * @param stmt the prepared statement to be used for batching
287         * @throws PlatformException upon JDBC failure
288         */
289        public void beforeBatch(PreparedStatement stmt) throws PlatformException
290        {
291            // Check for Oracle batching support
292            final Method methodSetExecuteBatch;
293            final Method methodSendBatch;
294            methodSetExecuteBatch = ClassHelper.getMethod(stmt, "setExecuteBatch", PARAM_TYPE_INTEGER);
295            methodSendBatch = ClassHelper.getMethod(stmt, "sendBatch", null);
296    
297            final boolean statementBatchingSupported = methodSetExecuteBatch != null && methodSendBatch != null;
298            if (statementBatchingSupported)
299            {
300                try
301                {
302                    // Set number of statements per batch
303                    methodSetExecuteBatch.invoke(stmt, PARAM_STATEMENT_BATCH_SIZE);
304                    m_batchStatementsInProgress.put(stmt, methodSendBatch);
305                }
306                catch (Exception e)
307                {
308                    throw new PlatformException(e.getLocalizedMessage(), e);
309                }
310            }
311            else
312            {
313                super.beforeBatch(stmt);
314            }
315        }
316    
317        /**
318         * Try Oracle update batching and call executeUpdate or revert to
319         * JDBC update batching.
320         * @param stmt the statement beeing added to the batch
321         * @throws PlatformException upon JDBC failure
322         */
323        public void addBatch(PreparedStatement stmt) throws PlatformException
324        {
325            // Check for Oracle batching support
326            final boolean statementBatchingSupported = m_batchStatementsInProgress.containsKey(stmt);
327            if (statementBatchingSupported)
328            {
329                try
330                {
331                    stmt.executeUpdate();
332                }
333                catch (SQLException e)
334                {
335                    throw new PlatformException(e.getLocalizedMessage(), e);
336                }
337            }
338            else
339            {
340                super.addBatch(stmt);
341            }
342        }
343    
344        /**
345         * Try Oracle update batching and call sendBatch or revert to
346         * JDBC update batching.
347         * @param stmt the batched prepared statement about to be executed
348         * @return always <code>null</code> if Oracle update batching is used,
349         * since it is impossible to dissolve total row count into distinct
350         * statement counts. If JDBC update batching is used, an int array is
351         * returned containing number of updated rows for each batched statement.
352         * @throws PlatformException upon JDBC failure
353         */
354        public int[] executeBatch(PreparedStatement stmt) throws PlatformException
355        {
356            // Check for Oracle batching support
357            final Method methodSendBatch = (Method) m_batchStatementsInProgress.remove(stmt);
358            final boolean statementBatchingSupported = methodSendBatch != null;
359    
360            int[] retval = null;
361            if (statementBatchingSupported)
362            {
363                try
364                {
365                    // sendBatch() returns total row count as an Integer
366                    methodSendBatch.invoke(stmt, null);
367                }
368                catch (Exception e)
369                {
370                    throw new PlatformException(e.getLocalizedMessage(), e);
371                }
372            }
373            else
374            {
375                retval = super.executeBatch(stmt);
376            }
377            return retval;
378        }
379    
380        /** @see Platform#setObjectForStatement */
381        public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType) throws SQLException
382        {
383            // Check for Oracle JDBC-driver LOB-support
384            final Statement oraStmt;
385            final Connection oraConn;
386            final boolean oraLargeLobSupportAvailable;
387            if (sqlType == Types.CLOB || sqlType == Types.BLOB)
388            {
389                oraStmt = unwrapStatement(ps);
390                oraConn = unwrapConnection(ps.getConnection());
391                oraLargeLobSupportAvailable =
392                        oraStmt != null && oraConn != null &&
393                        (sqlType == Types.CLOB ? ORA_CLOB_HANDLING_AVAILABLE : ORA_BLOB_HANDLING_AVAILABLE);
394            }
395            else
396            {
397                oraStmt = null;
398                oraConn = null;
399                oraLargeLobSupportAvailable = false;
400            }
401    
402            // Type-specific Oracle conversions
403            if (((sqlType == Types.VARBINARY) || (sqlType == Types.LONGVARBINARY)) && (value instanceof byte[]))
404            {
405                byte buf[] = (byte[]) value;
406                ByteArrayInputStream inputStream = new ByteArrayInputStream(buf);
407                super.changePreparedStatementResultSetType(ps);
408                ps.setBinaryStream(index, inputStream, buf.length);
409            }
410            else if (value instanceof Double)
411            {
412                // workaround for the bug in Oracle thin driver
413                ps.setDouble(index, ((Double) value).doubleValue());
414            }
415            else if (sqlType == Types.BIGINT && value instanceof Integer)
416            {
417                // workaround: Oracle thin driver problem when expecting long
418                ps.setLong(index, ((Integer) value).intValue());
419            }
420            else if (sqlType == Types.INTEGER && value instanceof Long)
421            {
422                ps.setLong(index, ((Long) value).longValue());
423            }
424            else if (sqlType == Types.CLOB && oraLargeLobSupportAvailable && value instanceof String)
425            {
426                // TODO: If using Oracle update batching with the thin driver, throw exception on 4k limit
427                try
428                {
429                    Object clob = Oracle9iLobHandler.createCLOBFromString(oraConn, (String) value);
430                    METHOD_SET_CLOB.invoke(oraStmt, new Object[]{new Integer(index), clob});
431                }
432                catch (Exception e)
433                {
434                    throw new SQLException(e.getLocalizedMessage());
435                }
436            }
437            else if (sqlType == Types.BLOB && oraLargeLobSupportAvailable && value instanceof byte[])
438            {
439                // TODO: If using Oracle update batching with the thin driver, throw exception on 2k limit
440                try
441                {
442                    Object blob = Oracle9iLobHandler.createBLOBFromByteArray(oraConn, (byte[]) value);
443                    METHOD_SET_BLOB.invoke(oraStmt, new Object[]{new Integer(index), blob});
444                }
445                catch (Exception e)
446                {
447                    throw new SQLException(e.getLocalizedMessage());
448                }
449            }
450            else
451            {
452                // Fall-through to superclass
453                super.setObjectForStatement(ps, index, value, sqlType);
454            }
455        }
456    
457        /**
458         * Get join syntax type for this RDBMS.
459         *
460         * @return SQL92_NOPAREN_JOIN_SYNTAX
461         */
462        public byte getJoinSyntaxType()
463        {
464            return SQL92_NOPAREN_JOIN_SYNTAX;
465        }
466    
467        /**
468         * Return an OracleConnection after trying to unwrap from known Connection wrappers.
469         * @param conn the connection to unwrap (if needed)
470         * @return OracleConnection or null if not able to unwrap
471         */
472        protected Connection unwrapConnection(Connection conn)
473        {
474            Object unwrapped = genericUnwrap(ORA_CONN_CLASS, conn, UNWRAP_CONN_METHOD_NAMES, UNWRAP_CONN_PARAM_TYPES);
475            try {
476                    // We haven't received a different Connection, so we'll assume that there's
477                    // some additional proxying going on. Let's check whether we get something
478                    // different back from the DatabaseMetaData.getConnection() call.
479                    DatabaseMetaData metaData = conn.getMetaData();
480                    // The following check is only really there for mock Connections
481                    // which might not carry a DatabaseMetaData instance.
482                    if (metaData != null) {
483                            Connection metaCon = metaData.getConnection();
484                            if (metaCon != conn) {
485                                    // We've received a different Connection there:
486                                    // Let's retry the native extraction process with it.
487                                    unwrapped = genericUnwrap(ORA_CONN_CLASS, metaCon, UNWRAP_CONN_METHOD_NAMES, UNWRAP_CONN_PARAM_TYPES);
488                            }
489                    }
490            } catch (SQLException e) {
491                    if (logger.isDebugEnabled())
492                {
493                    logger.debug("Failed attempting to unwrap connection via database metadata.", e);
494                }
495            }
496            if (unwrapped == null)
497            {
498                // mkalen:  only log this as debug since it will be logged for every connection
499                //          (ie only useful during development).
500                if (logger.isDebugEnabled())
501                {
502                    logger.debug("PlatformOracle9iImpl could not unwrap " + conn.getClass().getName() +
503                                 ", Oracle-extensions disabled.");
504                }
505            }
506            return (Connection) unwrapped;
507        }
508    
509        /**
510         * Return an OraclePreparedStatement after trying to unwrap from known Statement wrappers.
511         * @param ps the PreparedStatement to unwrap (if needed)
512         * @return OraclePreparedStatement or null if not able to unwrap
513         */
514        protected Statement unwrapStatement(Statement ps)
515        {
516            final Object unwrapped;
517            unwrapped = genericUnwrap(ORA_PS_CLASS, ps, UNWRAP_PS_METHOD_NAMES, UNWRAP_PS_PARAM_TYPES);
518            if (unwrapped == null)
519            {
520                // mkalen:  only log this as debug since it will be logged for every connection
521                //          (ie only useful during development).
522                if (logger.isDebugEnabled())
523                {
524                    logger.debug("PlatformOracle9iImpl could not unwrap " + ps.getClass().getName() +
525                                 ", large CLOB/BLOB support disabled.");
526                }
527            }
528            return (Statement) unwrapped;
529        }
530    
531        protected Object genericUnwrap(Class classToMatch, Object toUnwrap,
532                                       String[] methodNameCandidates,
533                                       Class[][] methodTypeCandidates)
534        {
535            if (classToMatch == null)
536            {
537                return null;
538            }
539    
540            Object unwrapped = null;
541            final Class psClass = toUnwrap.getClass();
542            if (classToMatch.isAssignableFrom(psClass))
543            {
544                return toUnwrap;
545            }
546            try
547            {
548                String methodName;
549                Class[] paramTypes;
550                Object[] args;
551                for (int i = 0; i < methodNameCandidates.length; i++)
552                {
553                    methodName = methodNameCandidates[i];
554                    paramTypes = methodTypeCandidates[i];
555                    final Method method = ClassHelper.getMethod(toUnwrap, methodName, paramTypes);
556                    if (method != null)
557                    {
558                        args = paramTypes == PARAM_TYPE_EMPTY ? PARAM_EMPTY : new Object[]{ toUnwrap };
559                        unwrapped = method.invoke(toUnwrap, args);
560                                    } else {
561                                            // Check for a field:
562                                            // PACTHED: This next section was added as a patch to allow for XAPool's org.enhydra.jdbc.core.CorePreparedStatement
563                                            // which externalizes it's underlying prepared statement as a public field called 'ps'
564                                            final Field field = ClassHelper.getField(psClass, methodName);
565                                            if (field != null) {
566                                                    unwrapped = field.get(toUnwrap);
567                                            }
568                                    }
569                    if (unwrapped != null)
570                    {
571                        if (classToMatch.isAssignableFrom(unwrapped.getClass()))
572                        {
573                            return unwrapped;
574                        }
575                        // When using eg both DBCP and P6Spy we have to recursively unwrap
576                        return genericUnwrap(classToMatch, unwrapped,
577                                methodNameCandidates, methodTypeCandidates);
578                    }
579                }
580                
581                // PATCHED: Try to use JDBC 4 unwrap mechanism
582                final Method jdbcUnwrapMethod = ClassHelper.getMethod(toUnwrap, "unwrap", new Class[] { Class.class });
583    
584                if (jdbcUnwrapMethod != null && !Modifier.isAbstract(jdbcUnwrapMethod.getModifiers())) {
585                    Class jdbcClassToMatch = null;  // we're going to find 
586                    if (java.sql.Connection.class.isAssignableFrom(classToMatch)) {
587                            jdbcClassToMatch = java.sql.Connection.class;
588                    } else if (java.sql.PreparedStatement.class.isAssignableFrom(classToMatch)) {
589                            jdbcClassToMatch = java.sql.PreparedStatement.class;
590                    } else if (java.sql.Statement.class.isAssignableFrom(classToMatch)) {
591                            jdbcClassToMatch = java.sql.Statement.class;
592                    }
593    
594                    Object result = null;
595                    if (jdbcClassToMatch != null) {
596                            result = jdbcUnwrapMethod.invoke(toUnwrap, jdbcClassToMatch);
597                            if (result != null) {
598                                    // Sometimes we actually get a proxy back
599                                    if (classToMatch.isAssignableFrom(result.getClass()))
600                                    {
601                                            return result;
602                                    }
603                                    // When using eg both DBCP and P6Spy we have to recursively unwrap
604                                    return genericUnwrap(classToMatch, result,
605                                                    methodNameCandidates, methodTypeCandidates);
606                            }
607                    }
608                } // didn't work, fall back.
609            }
610            catch (Exception e)
611            {
612                // ignore
613                if (logger.isDebugEnabled())
614                {
615                    logger.debug("genericUnwrap failed", e);
616                }
617            }
618            return null;
619        }
620    
621        /**
622         * Initializes static variables needed for Oracle-extensions and large BLOB/CLOB support.
623         */
624        protected void initOracleReflectedVars()
625        {
626            super.initOracleReflectedVars();
627            try
628            {
629                /*
630                Check for Oracle-specific classes, OracleConnection-specific
631                statement caching/row pre-fetch methods and Oracle BLOB/CLOB access methods.
632                We can do this in constructor in spite of possible mixing of instance being
633                able vs unable passed at runtime (since withouth these classes and methods
634                it's impossible to enable ORA-extensions at all even if instances are capable).
635                */
636                ORA_CONN_CLASS = ClassHelper.getClass("oracle.jdbc.OracleConnection", false);
637                ORA_PS_CLASS = ClassHelper.getClass("oracle.jdbc.OraclePreparedStatement", false);
638                ORA_CLOB_CLASS = ClassHelper.getClass("oracle.sql.CLOB", false);
639                ORA_BLOB_CLASS = ClassHelper.getClass("oracle.sql.BLOB", false);
640                PARAM_TYPE_INT_ORACLOB = new Class[]{ Integer.TYPE, ORA_CLOB_CLASS };
641                PARAM_TYPE_INT_ORABLOB = new Class[]{ Integer.TYPE, ORA_BLOB_CLASS };
642    
643                // Index 0 reserved for Oracle 10g
644                UNWRAP_CONN_PARAM_TYPES[0] = new Class[]{ ORA_CONN_CLASS };
645    
646                METHOD_SET_STATEMENT_CACHE_SIZE =
647                        ClassHelper.getMethod(ORA_CONN_CLASS, "setStatementCacheSize", PARAM_TYPE_INTEGER);
648                METHOD_SET_IMPLICIT_CACHING_ENABLED =
649                        ClassHelper.getMethod(ORA_CONN_CLASS, "setImplicitCachingEnabled", PARAM_TYPE_BOOLEAN);
650                METHOD_SET_ROW_PREFETCH = ClassHelper.getMethod(ORA_CONN_CLASS, "setDefaultRowPrefetch", PARAM_TYPE_INTEGER);
651                METHOD_SET_CLOB = ClassHelper.getMethod(ORA_PS_CLASS, "setCLOB", PARAM_TYPE_INT_ORACLOB);
652                METHOD_SET_BLOB = ClassHelper.getMethod(ORA_PS_CLASS, "setBLOB", PARAM_TYPE_INT_ORABLOB);
653    
654                ORA_STATEMENT_CACHING_AVAILABLE =
655                        METHOD_SET_STATEMENT_CACHE_SIZE != null && METHOD_SET_IMPLICIT_CACHING_ENABLED != null;
656                ORA_ROW_PREFETCH_AVAILABLE = METHOD_SET_ROW_PREFETCH != null;
657                ORA_CLOB_HANDLING_AVAILABLE = METHOD_SET_CLOB != null;
658                ORA_BLOB_HANDLING_AVAILABLE = METHOD_SET_BLOB != null;
659            }
660            catch (ClassNotFoundException e)
661            {
662                // ignore (we tried...)
663            }
664            // Isolated checks for other connection classes (OK when not found)
665            try
666            {
667                JBOSS_CONN_CLASS = ClassHelper.getClass(JBOSS_CONN_NAME, false);
668            }
669            catch (ClassNotFoundException e)
670            {
671                // ignore (no problem)
672            }
673        }
674    
675    }