001 package org.apache.ojb.broker.platforms;
002
003 /* Copyright 2002-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.util.logging.Logger;
019 import org.apache.ojb.broker.util.logging.LoggerFactory;
020 import org.apache.ojb.broker.util.ClassHelper;
021 import org.apache.ojb.broker.util.sequence.SequenceManagerHelper;
022
023 import java.io.ByteArrayInputStream;
024 import java.io.InputStreamReader;
025 import java.io.Reader;
026 import java.io.StringReader;
027 import java.lang.reflect.Field;
028 import java.security.AccessController;
029 import java.security.PrivilegedAction;
030 import java.sql.CallableStatement;
031 import java.sql.Connection;
032 import java.sql.DatabaseMetaData;
033 import java.sql.PreparedStatement;
034 import java.sql.SQLException;
035 import java.sql.Statement;
036 import java.sql.Types;
037 import java.util.Properties;
038
039 /**
040 * This class is a concrete implementation of <code>Platform</code>. Provides an implementation
041 * that works around some issues with Oracle in general and Oracle's Thin driver in particular.
042 *
043 * <p/>
044 * Many of the database sequence specific properties can be specified using
045 * <em>custom attributes</em> within the <em>sequence-manager</em> element.
046 * <br/>
047 * The database sequence specific properties are generally speaking, see database user guide
048 * for detailed description.
049 *
050 * <p>
051 * Implementation configuration properties:
052 * </p>
053 *
054 * <table cellspacing="2" cellpadding="2" border="3" frame="box">
055 * <tr>
056 * <td><strong>Property Key</strong></td>
057 * <td><strong>Property Values</strong></td>
058 * </tr>
059 * <tr>
060 * <td>sequenceStart</td>
061 * <td>
062 * DEPRECATED. Database sequence specific property.<br/>
063 * Specifies the first sequence number to be
064 * generated. Allowed: <em>1</em> or greater.
065 * </td>
066 * </tr>
067 * <tr>
068 * <td>seq.start</td>
069 * <td>
070 * Database sequence specific property.<br/>
071 * Specifies the first sequence number to be
072 * generated. Allowed: <em>1</em> or greater.
073 * </td>
074 * </tr>
075 * <tr>
076 * <td>seq.incrementBy</td>
077 * <td>
078 * Database sequence specific property.<br/>
079 * Specifies the interval between sequence numbers.
080 * This value can be any positive or negative
081 * integer, but it cannot be 0.
082 * </td>
083 * </tr>
084 * <tr>
085 * <td>seq.maxValue</td>
086 * <td>
087 * Database sequence specific property.<br/>
088 * Set max value for sequence numbers.
089 * </td>
090 * </tr>
091 * <tr>
092 * <td>seq.minValue</td>
093 * <td>
094 * Database sequence specific property.<br/>
095 * Set min value for sequence numbers.
096 * </td>
097 * </tr>
098 * <tr>
099 * <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 }