Coverage Report - org.apache.ojb.broker.platforms.PlatformOracleImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
PlatformOracleImpl
N/A
N/A
4.643
PlatformOracleImpl$1
N/A
N/A
4.643
 
 1  
 package org.apache.ojb.broker.platforms;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 import org.apache.ojb.broker.util.logging.Logger;
 19  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 20  
 import org.apache.ojb.broker.util.ClassHelper;
 21  
 import org.apache.ojb.broker.util.sequence.SequenceManagerHelper;
 22  
 
 23  
 import java.io.ByteArrayInputStream;
 24  
 import java.io.InputStreamReader;
 25  
 import java.io.Reader;
 26  
 import java.io.StringReader;
 27  
 import java.lang.reflect.Field;
 28  
 import java.security.AccessController;
 29  
 import java.security.PrivilegedAction;
 30  
 import java.sql.CallableStatement;
 31  
 import java.sql.Connection;
 32  
 import java.sql.DatabaseMetaData;
 33  
 import java.sql.PreparedStatement;
 34  
 import java.sql.SQLException;
 35  
 import java.sql.Statement;
 36  
 import java.sql.Types;
 37  
 import java.util.Properties;
 38  
 
 39  
 /**
 40  
  * This class is a concrete implementation of <code>Platform</code>. Provides an implementation
 41  
  * that works around some issues with Oracle in general and Oracle's Thin driver in particular.
 42  
  *
 43  
  * <p/>
 44  
  * Many of the database sequence specific properties can be specified using
 45  
  * <em>custom attributes</em> within the <em>sequence-manager</em> element.
 46  
  * <br/>
 47  
  * The database sequence specific properties are generally speaking, see database user guide
 48  
  * for detailed description.
 49  
  *
 50  
  * <p>
 51  
  * Implementation configuration properties:
 52  
  * </p>
 53  
  *
 54  
  * <table cellspacing="2" cellpadding="2" border="3" frame="box">
 55  
  * <tr>
 56  
  *     <td><strong>Property Key</strong></td>
 57  
  *     <td><strong>Property Values</strong></td>
 58  
  * </tr>
 59  
  * <tr>
 60  
  *     <td>sequenceStart</td>
 61  
  *     <td>
 62  
  *          DEPRECATED. Database sequence specific property.<br/>
 63  
  *          Specifies the first sequence number to be
 64  
  *          generated. Allowed: <em>1</em> or greater.
 65  
  *    </td>
 66  
  * </tr>
 67  
  * <tr>
 68  
  *     <td>seq.start</td>
 69  
  *     <td>
 70  
  *          Database sequence specific property.<br/>
 71  
  *          Specifies the first sequence number to be
 72  
  *          generated. Allowed: <em>1</em> or greater.
 73  
  *    </td>
 74  
  * </tr>
 75  
  * <tr>
 76  
  *     <td>seq.incrementBy</td>
 77  
  *     <td>
 78  
  *          Database sequence specific property.<br/>
 79  
  *          Specifies the interval between sequence numbers.
 80  
  *          This value can be any positive or negative
 81  
  *          integer, but it cannot be 0.
 82  
  *    </td>
 83  
  * </tr>
 84  
  * <tr>
 85  
  *     <td>seq.maxValue</td>
 86  
  *     <td>
 87  
  *          Database sequence specific property.<br/>
 88  
  *          Set max value for sequence numbers.
 89  
  *    </td>
 90  
  * </tr>
 91  
  * <tr>
 92  
  *     <td>seq.minValue</td>
 93  
  *     <td>
 94  
  *          Database sequence specific property.<br/>
 95  
  *          Set min value for sequence numbers.
 96  
  *    </td>
 97  
  * </tr>
 98  
  * <tr>
 99  
  *     <td>seq.cycle</td>
 100  
  *     <td>
 101  
  *          Database sequence specific property.<br/>
 102  
  *          If <em>true</em>, specifies that the sequence continues to generate
 103  
  *          values after reaching either its maximum or minimum value.
 104  
  *          <br/>
 105  
  *          If <em>false</em>, specifies that the sequence cannot generate more values after
 106  
  *          reaching its maximum or minimum value.
 107  
  *    </td>
 108  
  * </tr>
 109  
  * <tr>
 110  
  *     <td>seq.cache</td>
 111  
  *     <td>
 112  
  *          Database sequence specific property.<br/>
 113  
  *          Specifies how many values of the sequence Oracle
 114  
  *          preallocates and keeps in memory for faster access.
 115  
  *          Allowed values: <em>2</em> or greater. If set <em>0</em>,
 116  
  *          an explicite <em>nocache</em> expression will be set.
 117  
  *    </td>
 118  
  * </tr>
 119  
  * <tr>
 120  
  *     <td>seq.order</td>
 121  
  *     <td>
 122  
  *          Database sequence specific property.<br/>
 123  
  *          If set <em>true</em>, guarantees that sequence numbers
 124  
  *          are generated in order of request.
 125  
  *          <br/>
 126  
  *          If <em>false</em>, a <em>no order</em> expression will be set.
 127  
  *    </td>
 128  
  * </tr>
 129  
  * </table>
 130  
  *
 131  
  * @author <a href="mailto:thma@apache.org">Thomas Mahler <a>
 132  
  * @version $Id: PlatformOracleImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
 133  
  */
 134  
 
 135  
 public class PlatformOracleImpl extends PlatformDefaultImpl
 136  
 {
 137  
     protected static final String THIN_URL_PREFIX = "jdbc:oracle:thin";
 138  
     // Oracle:thin handles direct BLOB insert <= 4000 and update <= 2000
 139  
     protected static final int THIN_BLOB_MAX_SIZE = 2000;
 140  
     // Oracle:thin handles direct CLOB insert and update <= 4000
 141  
     protected static final int THIN_CLOB_MAX_SIZE = 4000;
 142  
 
 143  
     /**
 144  
      * Field value of <code>oracle.jdbc.OracleTypes.CURSOR</code>.
 145  
      * @see #initOracleReflectedVars
 146  
      */
 147  
     protected static int ORACLE_JDBC_TYPE_CURSOR = -10;
 148  
 
 149  
     private Logger logger = LoggerFactory.getLogger(PlatformOracleImpl.class);
 150  
 
 151  
     /**
 152  
      * Default constructor.
 153  
      */
 154  
     public PlatformOracleImpl()
 155  
     {
 156  
         initOracleReflectedVars();
 157  
     }
 158  
 
 159  
     /**
 160  
      * Method prepareNextValProcedureStatement implementation 
 161  
      * is simply copied over from PlatformMsSQLServerImpl class.
 162  
      * @see org.apache.ojb.broker.platforms.Platform#prepareNextValProcedureStatement(java.sql.Connection, java.lang.String, java.lang.String)
 163  
      */
 164  
     public CallableStatement prepareNextValProcedureStatement(Connection con, String procedureName, String sequenceName)
 165  
             throws PlatformException
 166  
     {
 167  
         try
 168  
         {
 169  
             String sp = "{?= call " + procedureName + " (?)}";
 170  
             CallableStatement cs = con.prepareCall(sp);
 171  
             cs.registerOutParameter(1, Types.INTEGER);
 172  
             cs.setString(2, sequenceName);
 173  
             return cs;
 174  
         }
 175  
         catch (SQLException e)
 176  
         {
 177  
             throw new PlatformException(e);
 178  
         }
 179  
     }
 180  
 
 181  
     /**
 182  
      * In Oracle we set escape processing explizit 'true' after a statement was created.
 183  
      */
 184  
     public void afterStatementCreate(Statement stmt) throws PlatformException
 185  
     {
 186  
         try
 187  
         {
 188  
             stmt.setEscapeProcessing(true);
 189  
         }
 190  
         catch (SQLException e)
 191  
         {
 192  
             throw new PlatformException("Could not set escape processing", e);
 193  
         }
 194  
     }
 195  
 
 196  
     /**
 197  
      * For objects beyond 4k, weird things happen in Oracle if you try to use "setBytes", so for
 198  
      * all cases it's better to use setBinaryStream. Oracle also requires a change in the resultset
 199  
      * type of the prepared statement. MBAIRD NOTE: BLOBS may not work with Oracle database/thin
 200  
      * driver versions prior to 8.1.6.
 201  
      * 
 202  
      * @see Platform#setObjectForStatement
 203  
      */
 204  
     public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType)
 205  
             throws SQLException
 206  
     {
 207  
         if (((sqlType == Types.VARBINARY) || (sqlType == Types.LONGVARBINARY) || (sqlType == Types.BLOB))
 208  
                 && (value instanceof byte[]))
 209  
         {
 210  
             byte buf[] = (byte[]) value;
 211  
             int length = buf.length;
 212  
             if (isUsingOracleThinDriver(ps.getConnection()) && length > THIN_BLOB_MAX_SIZE)
 213  
             {
 214  
                 throw new SQLException(
 215  
                         "Oracle thin driver cannot update BLOB values with length>2000. (Consider using Oracle9i as OJB platform.)");
 216  
             }
 217  
             ByteArrayInputStream inputStream = new ByteArrayInputStream(buf);
 218  
             changePreparedStatementResultSetType(ps);
 219  
             ps.setBinaryStream(index, inputStream, length);
 220  
         }
 221  
         else if (value instanceof Double)
 222  
         {
 223  
             // workaround for the bug in Oracle thin driver
 224  
             ps.setDouble(index, ((Double) value).doubleValue());
 225  
         }
 226  
         else if (sqlType == Types.BIGINT && value instanceof Integer)
 227  
         {
 228  
             // workaround: Oracle thin driver problem when expecting long
 229  
             ps.setLong(index, ((Integer) value).intValue());
 230  
         }
 231  
         else if (sqlType == Types.INTEGER && value instanceof Long)
 232  
         {
 233  
             ps.setLong(index, ((Long) value).longValue());
 234  
         }
 235  
         else if (sqlType == Types.DATE && value instanceof String)
 236  
         {
 237  
             // special handling of like for dates (birthDate like '2000-01%')
 238  
             ps.setString(index, (String) value);
 239  
         }
 240  
         else if (sqlType == Types.CLOB && (value instanceof String || value instanceof byte[]))
 241  
         {
 242  
             Reader reader;
 243  
             int length;
 244  
             if (value instanceof String)
 245  
             {
 246  
                 String stringValue = (String) value;
 247  
                 length = stringValue.length();
 248  
                 reader = new StringReader(stringValue);
 249  
             }
 250  
             else
 251  
             {
 252  
                 byte buf[] = (byte[]) value;
 253  
                 ByteArrayInputStream inputStream = new ByteArrayInputStream(buf);
 254  
                 reader = new InputStreamReader(inputStream);
 255  
                 length = buf.length;
 256  
             }
 257  
             if (isUsingOracleThinDriver(ps.getConnection()) && length > THIN_CLOB_MAX_SIZE)
 258  
             {
 259  
                 throw new SQLException(
 260  
                         "Oracle thin driver cannot insert CLOB values with length>4000. (Consider using Oracle9i as OJB platform.)");
 261  
             }
 262  
             ps.setCharacterStream(index, reader, length);
 263  
         }
 264  
         else if ((sqlType == Types.CHAR || sqlType == Types.VARCHAR)
 265  
                  &&
 266  
                  (value instanceof String || value instanceof Character))
 267  
         {
 268  
             if (value instanceof String)
 269  
             {
 270  
                 ps.setString(index, (String) value);
 271  
             }
 272  
             else // assert: value instanceof Character
 273  
             {
 274  
                 ps.setString(index, value.toString());
 275  
             }
 276  
         }
 277  
         else
 278  
         {
 279  
             super.setObjectForStatement(ps, index, value, sqlType);
 280  
         }
 281  
     }
 282  
 
 283  
     /**
 284  
      * Attempts to modify a private member in the Oracle thin driver's resultset to allow proper
 285  
      * setting of large binary streams.
 286  
      */
 287  
     protected void changePreparedStatementResultSetType(PreparedStatement ps)
 288  
     {
 289  
         try
 290  
         {
 291  
             final Field f = ps.getClass().getSuperclass().getDeclaredField("m_userRsetType");
 292  
             AccessController.doPrivileged(new PrivilegedAction()
 293  
             {
 294  
                 public Object run()
 295  
                 {
 296  
                     f.setAccessible(true);
 297  
                     return null;
 298  
                 }
 299  
             });
 300  
             f.setInt(ps, 1);
 301  
             f.setAccessible(false);
 302  
         }
 303  
         catch (Exception e)
 304  
         {
 305  
             logger.info("Not using classes12.zip.");
 306  
         }
 307  
     }
 308  
 
 309  
     /**
 310  
      * Get join syntax type for this RDBMS - one on of the constants from JoinSyntaxType interface
 311  
      */
 312  
     public byte getJoinSyntaxType()
 313  
     {
 314  
         return ORACLE_JOIN_SYNTAX;
 315  
     }
 316  
 
 317  
     public String createSequenceQuery(String sequenceName)
 318  
     {
 319  
         return "CREATE SEQUENCE " + sequenceName;
 320  
     }
 321  
 
 322  
     public String createSequenceQuery(String sequenceName, Properties prop)
 323  
     {
 324  
         /*
 325  
         CREATE SEQUENCE [schema.]sequence
 326  
             [INCREMENT BY integer]
 327  
             [START WITH integer]
 328  
             [MAXVALUE integer | NOMAXVALUE]
 329  
             [MINVALUE integer | NOMINVALUE]
 330  
             [CYCLE | NOCYCLE]
 331  
             [CACHE integer | NOCACHE]
 332  
             [ORDER | NOORDER]
 333  
         */
 334  
         StringBuffer query = new StringBuffer(createSequenceQuery(sequenceName));
 335  
         if(prop != null)
 336  
         {
 337  
             Boolean b;
 338  
             Long value;
 339  
 
 340  
             value = SequenceManagerHelper.getSeqIncrementBy(prop);
 341  
             if(value != null)
 342  
             {
 343  
                 query.append(" INCREMENT BY ").append(value.longValue());
 344  
             }
 345  
 
 346  
             value = SequenceManagerHelper.getSeqStart(prop);
 347  
             if(value != null)
 348  
             {
 349  
                 query.append(" START WITH ").append(value.longValue());
 350  
             }
 351  
 
 352  
             value = SequenceManagerHelper.getSeqMaxValue(prop);
 353  
             if(value != null)
 354  
             {
 355  
                 query.append(" MAXVALUE ").append(value.longValue());
 356  
             }
 357  
 
 358  
             value = SequenceManagerHelper.getSeqMinValue(prop);
 359  
             if(value != null)
 360  
             {
 361  
                 query.append(" MINVALUE ").append(value.longValue());
 362  
             }
 363  
 
 364  
             b = SequenceManagerHelper.getSeqCycleValue(prop);
 365  
             if(b != null)
 366  
             {
 367  
                 if(b.booleanValue()) query.append(" CYCLE");
 368  
                 else query.append(" NOCYCLE");
 369  
             }
 370  
 
 371  
             value = SequenceManagerHelper.getSeqCacheValue(prop);
 372  
             if(value != null)
 373  
             {
 374  
                 query.append(" CACHE ").append(value.longValue());
 375  
             }
 376  
 
 377  
             b = SequenceManagerHelper.getSeqOrderValue(prop);
 378  
             if(b != null)
 379  
             {
 380  
                 if(b.booleanValue()) query.append(" ORDER");
 381  
                 else query.append(" NOORDER");
 382  
             }
 383  
         }
 384  
         return query.toString();
 385  
     }
 386  
 
 387  
     public String nextSequenceQuery(String sequenceName)
 388  
     {
 389  
         return "select " + sequenceName + ".nextval from dual";
 390  
     }
 391  
 
 392  
     public String dropSequenceQuery(String sequenceName)
 393  
     {
 394  
         return "drop sequence " + sequenceName;
 395  
     }
 396  
 
 397  
     /**
 398  
      * @see org.apache.ojb.broker.platforms.Platform#registerOutResultSet(java.sql.CallableStatement, int)
 399  
      */
 400  
     public void registerOutResultSet(CallableStatement stmt, int position)
 401  
             throws SQLException
 402  
     {
 403  
         stmt.registerOutParameter(position, ORACLE_JDBC_TYPE_CURSOR);
 404  
     }
 405  
 
 406  
     /**
 407  
      * Checks if the supplied connection is using the Oracle thin driver.
 408  
      * 
 409  
      * @param conn database connection for which to check JDBC-driver
 410  
      * @return <code>true</code> if the connection is using Oracle thin driver, <code>false</code>
 411  
      *         otherwise.
 412  
      */
 413  
     protected static boolean isUsingOracleThinDriver(Connection conn)
 414  
     {
 415  
         if (conn == null)
 416  
         {
 417  
             return false;
 418  
         }
 419  
         final DatabaseMetaData dbMetaData;
 420  
         final String dbUrl;
 421  
         try
 422  
         {
 423  
             dbMetaData = conn.getMetaData();
 424  
             dbUrl = dbMetaData.getURL();
 425  
             if (dbUrl != null && dbUrl.startsWith(THIN_URL_PREFIX))
 426  
             {
 427  
                 return true;
 428  
             }
 429  
         }
 430  
         catch (Exception e)
 431  
         {
 432  
             // ignore it
 433  
         }
 434  
         return false;
 435  
     }
 436  
 
 437  
     /**
 438  
      * Initializes static variables needed for getting Oracle-specific JDBC types.
 439  
      */
 440  
     protected void initOracleReflectedVars()
 441  
     {
 442  
         try
 443  
         {
 444  
             // Check for Oracle-specific Types class
 445  
             final Class oracleTypes = ClassHelper.getClass("oracle.jdbc.OracleTypes", false);
 446  
             final Field cursorField = oracleTypes.getField("CURSOR");
 447  
             ORACLE_JDBC_TYPE_CURSOR = cursorField.getInt(null);
 448  
         }
 449  
         catch (ClassNotFoundException e)
 450  
         {
 451  
             log.warn("PlatformOracleImpl could not find Oracle JDBC classes");
 452  
         }
 453  
         catch (NoSuchFieldException e)
 454  
         {
 455  
             log.warn("PlatformOracleImpl could not find Oracle JDBC type fields");
 456  
         }
 457  
         catch (IllegalAccessException e)
 458  
         {
 459  
             log.warn("PlatformOracleImpl could not get Oracle JDBC type values");
 460  
         }
 461  
     }
 462  
 
 463  
 }