001    package org.apache.ojb.broker.accesslayer;
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.sql.Connection;
019    import java.sql.PreparedStatement;
020    import java.sql.ResultSet;
021    import java.sql.SQLException;
022    import java.sql.Statement;
023    
024    import org.apache.ojb.broker.PersistenceBrokerSQLException;
025    import org.apache.ojb.broker.accesslayer.sql.SqlGenerator;
026    import org.apache.ojb.broker.accesslayer.sql.SqlGeneratorFactory;
027    import org.apache.ojb.broker.core.proxy.ProxyHelper;
028    import org.apache.ojb.broker.metadata.ClassDescriptor;
029    import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
030    import org.apache.ojb.broker.metadata.ConnectionPoolDescriptor;
031    import org.apache.ojb.broker.platforms.Platform;
032    import org.apache.ojb.broker.platforms.PlatformException;
033    import org.apache.ojb.broker.platforms.PlatformFactory;
034    import org.apache.ojb.broker.query.Query;
035    import org.apache.ojb.broker.util.logging.Logger;
036    import org.apache.ojb.broker.util.logging.LoggerFactory;
037    import org.apache.ojb.broker.util.ExceptionHelper;
038    
039    /**
040     * This class serves as a cache for Statements that are
041     * used for persistence operations on a given class.
042     * @author Thomas Mahler
043     * @version $Id: StatementsForClassImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
044     */
045    public class StatementsForClassImpl implements StatementsForClassIF
046    {
047        private Logger log = LoggerFactory.getLogger(StatementsForClassImpl.class);
048    
049        /**
050         * sets the escape processing mode
051         */
052        // protected static boolean ESCAPEPROCESSING = false;
053    
054        protected final ClassDescriptor classDescriptor;
055        protected final SqlGenerator sqlGenerator;
056        protected final Platform platform;
057        protected final Class clazz;
058        protected final int fetchSize;
059        private String deleteSql;
060        private String insertSql;
061        private String updateSql;
062        private String selectByPKSql;
063    
064        /**
065         * force use of JDBC 1.0 statement creation
066         */
067        protected boolean FORCEJDBC1_0 = false;
068    
069        public StatementsForClassImpl(
070            final JdbcConnectionDescriptor jcd,
071            final ClassDescriptor classDescriptor)
072        {
073            this.classDescriptor = classDescriptor;
074            clazz = classDescriptor.getClassOfObject();
075            platform = PlatformFactory.getPlatformFor(jcd);
076            sqlGenerator = SqlGeneratorFactory.getInstance().createSqlGenerator(platform);
077    
078            final ConnectionPoolDescriptor cpd = jcd.getConnectionPoolDescriptor();
079            fetchSize = cpd.getFetchSize();
080    
081            // detect JDBC level
082            double level = jcd.getJdbcLevel();
083            FORCEJDBC1_0 = level == 1.0;
084        }
085    
086        /**
087         * Answer true if a PreparedStatement has to be used
088         * <br>false for a CallableStatement
089         */
090        protected boolean usePreparedDeleteStatement()
091        {
092            return !(classDescriptor.getDeleteProcedure() != null &&
093                    classDescriptor.getDeleteProcedure().hasReturnValues());
094        }
095    
096        public PreparedStatement getDeleteStmt(Connection con) throws SQLException
097        {
098            if (deleteSql == null)
099            {
100                deleteSql = sqlGenerator.getPreparedDeleteStatement(classDescriptor).getStatement();
101            }
102            try
103            {
104                return prepareStatement(con,
105                        deleteSql,
106                        Query.NOT_SCROLLABLE,
107                        usePreparedDeleteStatement(),
108                        StatementManagerIF.FETCH_SIZE_NOT_APPLICABLE);
109            }
110            catch (SQLException ex)
111            {
112                log.error("Can't prepare delete statement: " + deleteSql, ex);
113                throw ex;
114            }
115        }
116    
117        public Statement getGenericStmt(Connection con, boolean scrollable)
118            throws PersistenceBrokerSQLException
119        {
120            Statement stmt;
121            try
122            {
123                stmt = createStatement(con, scrollable,
124                        StatementManagerIF.FETCH_SIZE_NOT_EXPLICITLY_SET);
125            }
126            catch (SQLException ex)
127            {
128                throw ExceptionHelper.generateException("Can't prepare statement:", ex, null, log);
129            }
130            return stmt;
131        }
132    
133        /**
134         * Answer true if a PreparedStatement has to be used
135         * <br>false for a CallableStatement
136         */
137        protected boolean usePreparedInsertStatement()
138        {
139            return !(classDescriptor.getInsertProcedure() != null &&
140                    classDescriptor.getInsertProcedure().hasReturnValues());
141        }
142    
143        public PreparedStatement getInsertStmt(Connection con) throws SQLException
144        {
145            if (insertSql == null)
146            {
147                insertSql = sqlGenerator.getPreparedInsertStatement(classDescriptor).getStatement();
148            }
149            try
150            {
151                return prepareStatement(con,
152                        insertSql,
153                        Query.NOT_SCROLLABLE,
154                        usePreparedInsertStatement(),
155                        StatementManagerIF.FETCH_SIZE_NOT_APPLICABLE);
156            }
157            catch (SQLException ex)
158            {
159                log.error("Can't prepare insert statement: " + insertSql, ex);
160                throw ex;
161            }
162        }
163    
164        public PreparedStatement getPreparedStmt(Connection con, String sql,
165                                                 boolean scrollable, int explicitFetchSizeHint, boolean callableStmt)
166            throws PersistenceBrokerSQLException
167        {
168            PreparedStatement stmt;
169            try
170            {
171                stmt = prepareStatement(con, sql, scrollable,
172                        !callableStmt, explicitFetchSizeHint);
173            }
174            catch (SQLException ex)
175            {
176                throw ExceptionHelper.generateException("Can't prepare statement:", ex, sql, log);
177            }
178            return stmt;
179        }
180    
181        public PreparedStatement getSelectByPKStmt(Connection con) throws SQLException
182        {
183            if (selectByPKSql == null)
184            {
185                selectByPKSql = sqlGenerator.getPreparedSelectByPkStatement(classDescriptor).getStatement();
186            }
187            try
188            {
189                return prepareStatement(con, selectByPKSql, Query.NOT_SCROLLABLE, true, 1);
190            }
191            catch (SQLException ex)
192            {
193                log.error(ex);
194                throw ex;
195            }
196        }
197    
198        /**
199         * Answer true if a PreparedStatement has to be used
200         * <br>false for a CallableStatement
201         */
202        protected boolean usePreparedUpdateStatement()
203        {
204            return !(classDescriptor.getUpdateProcedure() != null &&
205                    classDescriptor.getUpdateProcedure().hasReturnValues());
206        }
207    
208        public PreparedStatement getUpdateStmt(Connection con) throws SQLException
209        {
210            if (updateSql == null)
211            {
212                updateSql = sqlGenerator.getPreparedUpdateStatement(classDescriptor).getStatement();
213            }
214            try
215            {
216                return prepareStatement(con,
217                        updateSql,
218                        Query.NOT_SCROLLABLE,
219                        usePreparedUpdateStatement(),
220                        StatementManagerIF.FETCH_SIZE_NOT_APPLICABLE);
221            }
222            catch (SQLException ex)
223            {
224                log.error("Can't prepare update statement: " + updateSql, ex);
225                throw ex;
226            }
227        }
228    
229        /**
230         * Prepares a statement with parameters that should work with most RDBMS.
231         *
232         * @param con the connection to utilize
233         * @param sql the sql syntax to use when creating the statement.
234         * @param scrollable determines if the statement will be scrollable.
235         * @param createPreparedStatement if <code>true</code>, then a
236         * {@link PreparedStatement} will be created. If <code>false</code>, then
237         * a {@link java.sql.CallableStatement} will be created.
238         * @param explicitFetchSizeHint will be used as fetchSize hint
239         * (if applicable) if > 0
240         *
241         * @return a statement that can be used to execute the syntax contained in
242         * the <code>sql</code> argument.
243         */
244        protected PreparedStatement prepareStatement(Connection con,
245                                                     String sql,
246                                                     boolean scrollable,
247                                                     boolean createPreparedStatement,
248                                                     int explicitFetchSizeHint)
249            throws SQLException
250        {
251            PreparedStatement result;
252    
253            // if a JDBC1.0 driver is used the signature
254            // prepareStatement(String, int, int) is  not defined.
255            // we then call the JDBC1.0 variant prepareStatement(String)
256            try
257            {
258                // if necessary use JDB1.0 methods
259                if (!FORCEJDBC1_0)
260                {
261                    if (createPreparedStatement)
262                    {
263                        result =
264                            con.prepareStatement(
265                                sql,
266                                scrollable
267                                    ? ResultSet.TYPE_SCROLL_INSENSITIVE
268                                    : ResultSet.TYPE_FORWARD_ONLY,
269                                ResultSet.CONCUR_READ_ONLY);
270                        afterJdbc2CapableStatementCreate(result, explicitFetchSizeHint);
271                    }
272                    else
273                    {
274                        result =
275                            con.prepareCall(
276                                sql,
277                                scrollable
278                                    ? ResultSet.TYPE_SCROLL_INSENSITIVE
279                                    : ResultSet.TYPE_FORWARD_ONLY,
280                                ResultSet.CONCUR_READ_ONLY);
281                    }
282                }
283                else
284                {
285                    if (createPreparedStatement)
286                    {
287                        result = con.prepareStatement(sql);
288                    }
289                    else
290                    {
291                        result = con.prepareCall(sql);
292                    }
293                }
294            }
295            catch (AbstractMethodError err)
296            {
297                // this exception is raised if Driver is not JDBC 2.0 compliant
298                log.warn("Used driver seems not JDBC 2.0 compatible, use the JDBC 1.0 mode", err);
299                if (createPreparedStatement)
300                {
301                    result = con.prepareStatement(sql);
302                }
303                else
304                {
305                    result = con.prepareCall(sql);
306                }
307                FORCEJDBC1_0 = true;
308            }
309            catch (SQLException eSql)
310            {
311                // there are JDBC Driver that nominally implement JDBC 2.0, but
312                // throw DriverNotCapableExceptions. If we catch one of these
313                // we force usage of JDBC 1.0
314                if (eSql
315                    .getClass()
316                    .getName()
317                    .equals("interbase.interclient.DriverNotCapableException"))
318                {
319                    log.warn("JDBC 2.0 problems with this interbase driver, we use the JDBC 1.0 mode");
320                    if (createPreparedStatement)
321                    {
322                        result = con.prepareStatement(sql);
323                    }
324                    else
325                    {
326                        result = con.prepareCall(sql);
327                    }
328                    FORCEJDBC1_0 = true;
329                }
330                else
331                {
332                    throw eSql;
333                }
334            }
335            try
336            {
337                if (!ProxyHelper.isNormalOjbProxy(result))  // tomdz: What about VirtualProxy
338                {
339                    platform.afterStatementCreate(result);
340                }
341            }
342            catch (PlatformException e)
343            {
344                log.error("Platform dependend failure", e);
345            }
346            return result;
347        }
348    
349        /**
350         * Creates a statement with parameters that should work with most RDBMS.
351         */
352        private Statement createStatement(Connection con, boolean scrollable, int explicitFetchSizeHint)
353            throws java.sql.SQLException
354        {
355            Statement result;
356            try
357            {
358                // if necessary use JDBC1.0 methods
359                if (!FORCEJDBC1_0)
360                {
361                    result =
362                        con.createStatement(
363                            scrollable
364                                ? ResultSet.TYPE_SCROLL_INSENSITIVE
365                                : ResultSet.TYPE_FORWARD_ONLY,
366                            ResultSet.CONCUR_READ_ONLY);
367                    afterJdbc2CapableStatementCreate(result, explicitFetchSizeHint);
368                }
369                else
370                {
371                    result = con.createStatement();
372                }
373            }
374            catch (AbstractMethodError err)
375            {
376                // if a JDBC1.0 driver is used, the signature
377                // createStatement(int, int) is  not defined.
378                // we then call the JDBC1.0 variant createStatement()
379                log.warn("Used driver seems not JDBC 2.0 compatible, use the JDBC 1.0 mode", err);
380                result = con.createStatement();
381                FORCEJDBC1_0 = true;
382            }
383            catch (SQLException eSql)
384            {
385                // there are JDBC Driver that nominally implement JDBC 2.0, but
386                // throw DriverNotCapableExceptions. If we catch one of these
387                // we force usage of JDBC 1.0
388                if (eSql.getClass().getName()
389                    .equals("interbase.interclient.DriverNotCapableException"))
390                {
391                    log.warn("JDBC 2.0 problems with this interbase driver, we use the JDBC 1.0 mode");
392                    FORCEJDBC1_0 = true;
393                    result = con.createStatement();
394                }
395                else
396                {
397                    throw eSql;
398                }
399            }
400            try
401            {
402                platform.afterStatementCreate(result);
403            }
404            catch (PlatformException e)
405            {
406                log.error("Platform dependend failure", e);
407            }
408            return result;
409        }
410    
411        private void afterJdbc2CapableStatementCreate(Statement stmt, int explicitFetchSizeHint)
412                throws SQLException
413        {
414            if (stmt != null)
415            {
416                final int fetchSizeHint;
417                if (explicitFetchSizeHint == StatementManagerIF.FETCH_SIZE_NOT_APPLICABLE)
418                {
419                    fetchSizeHint = StatementManagerIF.FETCH_SIZE_NOT_APPLICABLE;
420                }
421                else if (explicitFetchSizeHint != StatementManagerIF.FETCH_SIZE_NOT_EXPLICITLY_SET)
422                {
423                    fetchSizeHint = explicitFetchSizeHint; // specific for this Statement
424                }
425                else
426                {
427                    fetchSizeHint = fetchSize; // connection pool default
428                }
429                if (fetchSizeHint > 0)
430                {
431                    stmt.setFetchSize(fetchSize);
432                }
433            }
434        }
435    
436    }