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    }