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 java.io.StringReader;
019    import java.sql.CallableStatement;
020    import java.sql.Connection;
021    import java.sql.DatabaseMetaData;
022    import java.sql.PreparedStatement;
023    import java.sql.ResultSet;
024    import java.sql.SQLException;
025    import java.sql.Statement;
026    import java.sql.Types;
027    import java.util.Properties;
028    
029    import org.apache.ojb.broker.PersistenceBrokerException;
030    import org.apache.ojb.broker.accesslayer.JoinSyntaxTypes;
031    import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
032    import org.apache.ojb.broker.query.LikeCriteria;
033    import org.apache.ojb.broker.util.logging.Logger;
034    import org.apache.ojb.broker.util.logging.LoggerFactory;
035    
036    /**
037     * This class is a concrete implementation of <code>Platform</code>. Provides default implementations for all
038     * methods declared in <code>Platform</code>.
039     * It is intended as a vanilla implementation and as baseclass for
040     * platform specific implementations.
041     *
042     * @version $Id: PlatformDefaultImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
043     * @author              Thomas Mahler
044     */
045    public class PlatformDefaultImpl implements Platform, JoinSyntaxTypes
046    {
047        protected Logger log = LoggerFactory.getLogger(PlatformDefaultImpl.class);
048        private static final String INITIALIZATION_CHECK_AUTOCOMMIT = "initializationCheck";
049        private static final String FALSE_STR = "false";
050    
051        protected boolean m_batchUpdatesChecked = false;
052        protected boolean m_supportsBatchUpdates = false;
053    
054        public boolean supportsBatchOperations()
055        {
056            return m_supportsBatchUpdates;
057        }
058    
059        /**
060         * Sets platform information for if the jdbc driver/db combo support
061         * batch operations. Will only be checked once, then have same batch
062         * support setting for the entire session.
063         *
064         * @param conn
065         */
066        protected void checkForBatchSupport(Connection conn)
067        {
068            if (!m_batchUpdatesChecked)
069            {
070                DatabaseMetaData meta;
071                try
072                {
073                    meta = conn.getMetaData();
074                    m_supportsBatchUpdates = meta.supportsBatchUpdates();
075                }
076                catch (Throwable th)
077                {
078                    log.info("Batch support check failed", th);
079                    m_supportsBatchUpdates = false;
080                }
081                finally
082                {
083                    m_batchUpdatesChecked = true;
084                }
085            }
086        }
087    
088        public void afterStatementCreate(Statement stmt) throws PlatformException
089        {
090            //noop
091        }
092    
093        public void beforeStatementClose(Statement stmt, ResultSet rs) throws PlatformException
094        {
095            if (rs != null)
096            {
097                try
098                {
099                    rs.close();
100                }
101                catch (SQLException e)
102                {
103                    throw new PlatformException("Resultset closing failed", e);
104                }
105            }
106        }
107    
108        public void afterStatementClose(Statement stmt, ResultSet rs) throws PlatformException
109        {
110            //nothing
111        }
112    
113        public void beforeBatch(PreparedStatement stmt) throws PlatformException
114        {
115            // nothing
116        }
117    
118        public void addBatch(PreparedStatement stmt) throws PlatformException
119        {
120            try
121            {
122                stmt.addBatch();
123            }
124            catch (SQLException e)
125            {
126                throw new PlatformException("Failure while calling 'addBatch' on given Statement object", e);
127            }
128        }
129    
130        public int[] executeBatch(PreparedStatement stmt) throws PlatformException
131        {
132            try
133            {
134                return stmt.executeBatch();
135            }
136            catch (SQLException e)
137            {
138                throw new PlatformException("Failure while calling 'executeBatch' on given Statement object", e);
139            }
140        }
141    
142    
143        /**
144         * @see Platform#initializeJdbcConnection
145         */
146        public void initializeJdbcConnection(JdbcConnectionDescriptor jcd, Connection conn) throws PlatformException
147        {
148            if (jcd.getBatchMode()) checkForBatchSupport(conn);
149    
150            switch (jcd.getUseAutoCommit())
151            {
152                case JdbcConnectionDescriptor.AUTO_COMMIT_IGNORE_STATE:
153                    // nothing to do
154                    break;
155                case JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE:
156                    try
157                    {
158                        /*
159                        arminw:
160                        workaround to be backward compatible. In future releases we shouldn't change the autocommit
161                        state of a connection at initializing by the ConnectionFactory. The autocommit state should
162                        only be changed by the ConnectionManager. We have to separate this stuff.
163                        */
164                        if (!jcd.getAttribute(INITIALIZATION_CHECK_AUTOCOMMIT, FALSE_STR).equalsIgnoreCase(FALSE_STR)
165                                && !conn.getAutoCommit())
166                        {
167                            conn.setAutoCommit(true);
168                        }
169                    }
170                    catch (SQLException e)
171                    {
172                        if (!jcd.isIgnoreAutoCommitExceptions())
173                        {
174                            throw new PlatformException("Connection initializing: setAutoCommit(true) failed", e);
175                        }
176                        else
177                        {
178                            log.info("Connection initializing: setAutoCommit jdbc-driver problems. " + e.getMessage());
179                        }
180                    }
181                    break;
182                case JdbcConnectionDescriptor.AUTO_COMMIT_SET_FALSE:
183                    try
184                    {
185                        if (conn.getAutoCommit()) conn.setAutoCommit(false);
186                    }
187                    catch (SQLException e)
188                    {
189                        if (!jcd.isIgnoreAutoCommitExceptions())
190                        {
191                            throw new PlatformException("Connection initializing: setAutoCommit(false) failed", e);
192                        }
193                        else
194                        {
195                            log.info("Connection initializing: setAutoCommit jdbc-driver problems. " + e.getMessage());
196                        }
197                    }
198                    break;
199            }
200        }
201    
202        public void changeAutoCommitState(JdbcConnectionDescriptor jcd, Connection con, boolean newState)
203        {
204            if (con == null)
205            {
206                log.error("Given m_connection was null, cannot prepare autoCommit state");
207                return;
208            }
209            if (JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE == jcd.getUseAutoCommit())
210            {
211                try
212                {
213                    con.setAutoCommit(newState);
214                }
215                catch (SQLException e)
216                {
217                    if (jcd.isIgnoreAutoCommitExceptions())
218                    {
219                        log.info("Set autoCommit(" + newState + ") failed: " + e.getMessage());
220                    }
221                    else
222                    {
223                        log.error("Set autoCommit(" + newState + ") failed", e);
224                        throw new PersistenceBrokerException("Set autoCommit(false) failed", e);
225                    }
226                }
227            }
228        }
229    
230        /*
231         * @see Platform#setObject(PreparedStatement, int, Object, int)
232         */
233        public void setObjectForStatement(PreparedStatement ps, int index, Object value, int sqlType)
234                throws SQLException
235        {
236            if ((sqlType == Types.LONGVARCHAR) && (value instanceof String))
237            {
238                String s = (String) value;
239                ps.setCharacterStream(index, new StringReader(s), s.length());
240            }
241            /*
242            PATCH for BigDecimal truncation problem. Seems that several databases (e.g. DB2, Sybase)
243            has problem with BigDecimal fields if the sql-type was set. The problem was discussed here
244            http://nagoya.apache.org/eyebrowse/ReadMsg?listName=ojb-user@db.apache.org&msgNo=14113
245            A better option will be
246            <snip>
247            else if ((value instanceof BigDecimal) && (sqlType == Types.DECIMAL
248                     || sqlType == Types.NUMERIC))
249             {
250                 ps.setObject(index, value, sqlType,
251                         ((BigDecimal) value).scale());
252             }
253             </snip>
254            But this way maxDB/sapDB does not work correct, so we use the most flexible solution
255            and let the jdbc-driver handle BigDecimal objects by itself.
256            */
257            else if(sqlType == Types.DECIMAL || sqlType == Types.NUMERIC)
258            {
259                ps.setObject(index, value);
260            }
261            else
262            {
263    // arminw: this method call is done very, very often, so we can improve performance
264    // by comment out this section
265    //            if (log.isDebugEnabled()) {
266    //                log.debug("Default setObjectForStatement, sqlType=" + sqlType +
267    //                          ", value class=" + (value == null ? "NULL!" : value.getClass().getName())
268    //                            + ", value=" + value);
269    //            }
270                ps.setObject(index, value, sqlType);
271            }
272        }
273    
274        /*
275         * @see Platform#setNullForStatement(PreparedStatement, int, int)
276         */
277        public void setNullForStatement(PreparedStatement ps, int index, int sqlType) throws SQLException
278        {
279            ps.setNull(index, sqlType);
280        }
281    
282        /**
283         * Get join syntax type for this RDBMS - one on of the constants from JoinSyntaxType interface
284         *
285         * @see Platform#getJoinSyntaxType
286         */
287        public byte getJoinSyntaxType()
288        {
289            return SQL92_JOIN_SYNTAX;
290        }
291    
292        /**
293         * Override default ResultSet size determination (rs.last();rs.getRow())
294         * with select count(*) operation
295         *
296         * @see Platform#useCountForResultsetSize()
297         */
298        public boolean useCountForResultsetSize()
299        {
300            return false;
301        }
302    
303        public String createSequenceQuery(String sequenceName, Properties prop)
304        {
305            return createSequenceQuery(sequenceName);
306        }
307    
308        /**
309         * Override this method to enable database based sequence generation
310         */
311        public String createSequenceQuery(String sequenceName)
312        {
313            /*default implementation does not support this*/
314            throw new UnsupportedOperationException("This feature is not supported by this implementation");
315        }
316    
317        /**
318         * Override this method to enable database based sequence generation
319         */
320        public String nextSequenceQuery(String sequenceName)
321        {
322            /*default implementation does not support this*/
323            throw new UnsupportedOperationException("This feature is not supported by this implementation");
324        }
325    
326        /**
327         * Override this method to enable database based sequence generation
328         */
329        public String dropSequenceQuery(String sequenceName)
330        {
331            /*default implementation does not support this*/
332            throw new UnsupportedOperationException("This feature is not supported by this implementation");
333        }
334    
335        public CallableStatement prepareNextValProcedureStatement(Connection con, String procedureName,
336                                                                  String sequenceName) throws PlatformException
337        {
338            /*@todo implementation*/
339            throw new UnsupportedOperationException("Not supported by this implementation");
340        }
341    
342        public String getLastInsertIdentityQuery(String tableName)
343        {
344            /*@todo implementation*/
345            throw new UnsupportedOperationException("This feature is not supported by this implementation");
346        }
347    
348        /**
349         * @see org.apache.ojb.broker.platforms.Platform#addPagingSql(java.lang.StringBuffer)
350         */
351        public void addPagingSql(StringBuffer anSqlString)
352        {
353            // do nothing
354        }
355    
356        /**
357         * @see org.apache.ojb.broker.platforms.Platform#bindPagingParametersFirst()
358         */
359        public boolean bindPagingParametersFirst()
360        {
361            return false;
362        }
363    
364        /**
365         * @see org.apache.ojb.broker.platforms.Platform#supportsPaging()
366         */
367        public boolean supportsPaging()
368        {
369            return false;
370        }
371    
372        /**
373         * @see org.apache.ojb.broker.platforms.Platform#bindPagingParameters(java.sql.PreparedStatement, int, int, int)
374         */
375        public int bindPagingParameters(PreparedStatement ps, int index, int startAt, int endAt) throws SQLException
376        {
377            ps.setInt(index, startAt - 1);              // zero based start
378            index++;
379            ps.setInt(index, endAt - (startAt - 1));    // number of rows to fetch
380            index++;
381            return index;
382        }
383    
384        /**
385         * Answer the Character for Concatenation
386         */
387        protected String getConcatenationCharacter()
388        {
389            return "||";
390        }
391    
392        /**
393         * {@inheritDoc}
394         */
395        public boolean supportsMultiColumnCountDistinct()
396        {
397            return true;
398        }
399    
400        /**
401         * @see org.apache.ojb.broker.platforms.Platform#concatenate(java.lang.String[])
402         */
403        public String concatenate(String[] theColumns)
404        {
405            if (theColumns.length == 1)
406            {
407                return theColumns[0];
408            }
409    
410            StringBuffer buf = new StringBuffer();
411            String concatChar = getConcatenationCharacter();
412    
413            for (int i = 0; i < theColumns.length; i++)
414            {
415                if (i > 0)
416                {
417                    buf.append(" ").append(concatChar).append(" ");
418                }
419                buf.append(theColumns[i]);
420            }
421    
422            return buf.toString();
423        }
424    
425        /**
426         * @see org.apache.ojb.broker.platforms.Platform#getEscapeClause(org.apache.ojb.broker.query.LikeCriteria)
427         */
428        public String getEscapeClause(LikeCriteria aCriteria)
429        {
430            String value = (String) aCriteria.getValue();
431            char escapeChar = LikeCriteria.getEscapeCharacter();
432    
433            if (value.indexOf(escapeChar) >= 0)
434            {
435                return " ESCAPE '" + escapeChar + "'";
436            }
437            else
438            {
439                return "";
440            }
441        }
442    
443        /**
444         * @see org.apache.ojb.broker.platforms.Platform#registerOutResultSet(java.sql.CallableStatement, int)
445         */
446        public void registerOutResultSet(CallableStatement stmt, int position)
447                throws SQLException
448        {
449            stmt.registerOutParameter(position, Types.OTHER);
450        }
451    }