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