| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| PlatformOracleImpl | 
 | 
 | 4.214285714285714;4.214 | ||||
| PlatformOracleImpl$1 | 
 | 
 | 4.214285714285714;4.214 | 
| 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 | } |