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.20.2.4 2005/08/16 20:11:19 arminw Exp $
133: */
134:
135: public class PlatformOracleImpl extends PlatformDefaultImpl {
136: protected static final String THIN_URL_PREFIX = "jdbc:oracle:thin";
137: // Oracle:thin handles direct BLOB insert <= 4000 and update <= 2000
138: protected static final int THIN_BLOB_MAX_SIZE = 2000;
139: // Oracle:thin handles direct CLOB insert and update <= 4000
140: protected static final int THIN_CLOB_MAX_SIZE = 4000;
141:
142: /**
143: * Field value of <code>oracle.jdbc.OracleTypes.CURSOR</code>.
144: * @see #initOracleReflectedVars
145: */
146: protected static int ORACLE_JDBC_TYPE_CURSOR = -10;
147:
148: private Logger logger = LoggerFactory
149: .getLogger(PlatformOracleImpl.class);
150:
151: /**
152: * Default constructor.
153: */
154: public PlatformOracleImpl() {
155: initOracleReflectedVars();
156: }
157:
158: /**
159: * Method prepareNextValProcedureStatement implementation
160: * is simply copied over from PlatformMsSQLServerImpl class.
161: * @see org.apache.ojb.broker.platforms.Platform#prepareNextValProcedureStatement(java.sql.Connection, java.lang.String, java.lang.String)
162: */
163: public CallableStatement prepareNextValProcedureStatement(
164: Connection con, String procedureName, String sequenceName)
165: throws PlatformException {
166: try {
167: String sp = "{?= call " + procedureName + " (?)}";
168: CallableStatement cs = con.prepareCall(sp);
169: cs.registerOutParameter(1, Types.INTEGER);
170: cs.setString(2, sequenceName);
171: return cs;
172: } catch (SQLException e) {
173: throw new PlatformException(e);
174: }
175: }
176:
177: /**
178: * In Oracle we set escape processing explizit 'true' after a statement was created.
179: */
180: public void afterStatementCreate(Statement stmt)
181: throws PlatformException {
182: try {
183: stmt.setEscapeProcessing(true);
184: } catch (SQLException e) {
185: throw new PlatformException(
186: "Could not set escape processing", e);
187: }
188: }
189:
190: /**
191: * For objects beyond 4k, weird things happen in Oracle if you try to use "setBytes", so for
192: * all cases it's better to use setBinaryStream. Oracle also requires a change in the resultset
193: * type of the prepared statement. MBAIRD NOTE: BLOBS may not work with Oracle database/thin
194: * driver versions prior to 8.1.6.
195: *
196: * @see Platform#setObjectForStatement
197: */
198: public void setObjectForStatement(PreparedStatement ps, int index,
199: Object value, int sqlType) throws SQLException {
200: if (((sqlType == Types.VARBINARY)
201: || (sqlType == Types.LONGVARBINARY) || (sqlType == Types.BLOB))
202: && (value instanceof byte[])) {
203: byte buf[] = (byte[]) value;
204: int length = buf.length;
205: if (isUsingOracleThinDriver(ps.getConnection())
206: && length > THIN_BLOB_MAX_SIZE) {
207: throw new SQLException(
208: "Oracle thin driver cannot update BLOB values with length>2000. (Consider using Oracle9i as OJB platform.)");
209: }
210: ByteArrayInputStream inputStream = new ByteArrayInputStream(
211: buf);
212: changePreparedStatementResultSetType(ps);
213: ps.setBinaryStream(index, inputStream, length);
214: } else if (value instanceof Double) {
215: // workaround for the bug in Oracle thin driver
216: ps.setDouble(index, ((Double) value).doubleValue());
217: } else if (sqlType == Types.BIGINT && value instanceof Integer) {
218: // workaround: Oracle thin driver problem when expecting long
219: ps.setLong(index, ((Integer) value).intValue());
220: } else if (sqlType == Types.INTEGER && value instanceof Long) {
221: ps.setLong(index, ((Long) value).longValue());
222: } else if (sqlType == Types.DATE && value instanceof String) {
223: // special handling of like for dates (birthDate like '2000-01%')
224: ps.setString(index, (String) value);
225: } else if (sqlType == Types.CLOB
226: && (value instanceof String || value instanceof byte[])) {
227: Reader reader;
228: int length;
229: if (value instanceof String) {
230: String stringValue = (String) value;
231: length = stringValue.length();
232: reader = new StringReader(stringValue);
233: } else {
234: byte buf[] = (byte[]) value;
235: ByteArrayInputStream inputStream = new ByteArrayInputStream(
236: buf);
237: reader = new InputStreamReader(inputStream);
238: length = buf.length;
239: }
240: if (isUsingOracleThinDriver(ps.getConnection())
241: && length > THIN_CLOB_MAX_SIZE) {
242: throw new SQLException(
243: "Oracle thin driver cannot insert CLOB values with length>4000. (Consider using Oracle9i as OJB platform.)");
244: }
245: ps.setCharacterStream(index, reader, length);
246: } else if ((sqlType == Types.CHAR || sqlType == Types.VARCHAR)
247: && (value instanceof String || value instanceof Character)) {
248: if (value instanceof String) {
249: ps.setString(index, (String) value);
250: } else // assert: value instanceof Character
251: {
252: ps.setString(index, value.toString());
253: }
254: } else {
255: super .setObjectForStatement(ps, index, value, sqlType);
256: }
257: }
258:
259: /**
260: * Attempts to modify a private member in the Oracle thin driver's resultset to allow proper
261: * setting of large binary streams.
262: */
263: protected void changePreparedStatementResultSetType(
264: PreparedStatement ps) {
265: try {
266: final Field f = ps.getClass().getSuperclass()
267: .getDeclaredField("m_userRsetType");
268: AccessController.doPrivileged(new PrivilegedAction() {
269: public Object run() {
270: f.setAccessible(true);
271: return null;
272: }
273: });
274: f.setInt(ps, 1);
275: f.setAccessible(false);
276: } catch (Exception e) {
277: logger.info("Not using classes12.zip.");
278: }
279: }
280:
281: /**
282: * Get join syntax type for this RDBMS - one on of the constants from JoinSyntaxType interface
283: */
284: public byte getJoinSyntaxType() {
285: return ORACLE_JOIN_SYNTAX;
286: }
287:
288: public String createSequenceQuery(String sequenceName) {
289: return "CREATE SEQUENCE " + sequenceName;
290: }
291:
292: public String createSequenceQuery(String sequenceName,
293: Properties prop) {
294: /*
295: CREATE SEQUENCE [schema.]sequence
296: [INCREMENT BY integer]
297: [START WITH integer]
298: [MAXVALUE integer | NOMAXVALUE]
299: [MINVALUE integer | NOMINVALUE]
300: [CYCLE | NOCYCLE]
301: [CACHE integer | NOCACHE]
302: [ORDER | NOORDER]
303: */
304: StringBuffer query = new StringBuffer(
305: createSequenceQuery(sequenceName));
306: if (prop != null) {
307: Boolean b;
308: Long value;
309:
310: value = SequenceManagerHelper.getSeqIncrementBy(prop);
311: if (value != null) {
312: query.append(" INCREMENT BY ")
313: .append(value.longValue());
314: }
315:
316: value = SequenceManagerHelper.getSeqStart(prop);
317: if (value != null) {
318: query.append(" START WITH ").append(value.longValue());
319: }
320:
321: value = SequenceManagerHelper.getSeqMaxValue(prop);
322: if (value != null) {
323: query.append(" MAXVALUE ").append(value.longValue());
324: }
325:
326: value = SequenceManagerHelper.getSeqMinValue(prop);
327: if (value != null) {
328: query.append(" MINVALUE ").append(value.longValue());
329: }
330:
331: b = SequenceManagerHelper.getSeqCycleValue(prop);
332: if (b != null) {
333: if (b.booleanValue())
334: query.append(" CYCLE");
335: else
336: query.append(" NOCYCLE");
337: }
338:
339: value = SequenceManagerHelper.getSeqCacheValue(prop);
340: if (value != null) {
341: query.append(" CACHE ").append(value.longValue());
342: }
343:
344: b = SequenceManagerHelper.getSeqOrderValue(prop);
345: if (b != null) {
346: if (b.booleanValue())
347: query.append(" ORDER");
348: else
349: query.append(" NOORDER");
350: }
351: }
352: return query.toString();
353: }
354:
355: public String nextSequenceQuery(String sequenceName) {
356: return "select " + sequenceName + ".nextval from dual";
357: }
358:
359: public String dropSequenceQuery(String sequenceName) {
360: return "drop sequence " + sequenceName;
361: }
362:
363: /**
364: * @see org.apache.ojb.broker.platforms.Platform#registerOutResultSet(java.sql.CallableStatement, int)
365: */
366: public void registerOutResultSet(CallableStatement stmt,
367: int position) throws SQLException {
368: stmt.registerOutParameter(position, ORACLE_JDBC_TYPE_CURSOR);
369: }
370:
371: /**
372: * Checks if the supplied connection is using the Oracle thin driver.
373: *
374: * @param conn database connection for which to check JDBC-driver
375: * @return <code>true</code> if the connection is using Oracle thin driver, <code>false</code>
376: * otherwise.
377: */
378: protected static boolean isUsingOracleThinDriver(Connection conn) {
379: if (conn == null) {
380: return false;
381: }
382: final DatabaseMetaData dbMetaData;
383: final String dbUrl;
384: try {
385: dbMetaData = conn.getMetaData();
386: dbUrl = dbMetaData.getURL();
387: if (dbUrl != null && dbUrl.startsWith(THIN_URL_PREFIX)) {
388: return true;
389: }
390: } catch (Exception e) {
391: // ignore it
392: }
393: return false;
394: }
395:
396: /**
397: * Initializes static variables needed for getting Oracle-specific JDBC types.
398: */
399: protected void initOracleReflectedVars() {
400: try {
401: // Check for Oracle-specific Types class
402: final Class oracleTypes = ClassHelper.getClass(
403: "oracle.jdbc.OracleTypes", false);
404: final Field cursorField = oracleTypes.getField("CURSOR");
405: ORACLE_JDBC_TYPE_CURSOR = cursorField.getInt(null);
406: } catch (ClassNotFoundException e) {
407: log
408: .warn("PlatformOracleImpl could not find Oracle JDBC classes");
409: } catch (NoSuchFieldException e) {
410: log
411: .warn("PlatformOracleImpl could not find Oracle JDBC type fields");
412: } catch (IllegalAccessException e) {
413: log
414: .warn("PlatformOracleImpl could not get Oracle JDBC type values");
415: }
416: }
417:
418: }
|