001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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    package org.apache.commons.dbcp.cpdsadapter;
019    
020    import java.util.Hashtable;
021    import java.util.Properties;
022    import java.io.PrintWriter;
023    import java.io.Serializable;
024    import java.sql.DriverManager;
025    import java.sql.SQLException;
026    import javax.sql.PooledConnection;
027    import javax.sql.ConnectionPoolDataSource;
028    import javax.naming.Name;
029    import javax.naming.Context;
030    import javax.naming.Referenceable;
031    import javax.naming.spi.ObjectFactory;
032    import javax.naming.Reference;
033    import javax.naming.RefAddr;
034    import javax.naming.StringRefAddr;
035    import javax.naming.NamingException;
036    
037    import org.apache.commons.pool.KeyedObjectPool;
038    import org.apache.commons.pool.impl.GenericKeyedObjectPool;
039    
040    /**
041     * <p>
042     * An adapter for jdbc drivers that do not include an implementation
043     * of {@link javax.sql.ConnectionPoolDataSource}, but still include a 
044     * {@link java.sql.DriverManager} implementation.  
045     * <code>ConnectionPoolDataSource</code>s are not used within general 
046     * applications.  They are used by <code>DataSource</code> implementations
047     * that pool <code>Connection</code>s, such as 
048     * {@link org.apache.commons.dbcp.datasources.SharedPoolDataSource}.  A J2EE
049     * container will normally provide some method of initializing the
050     * <code>ConnectionPoolDataSource</code> whose attributes are presented
051     * as bean getters/setters and then deploying it via JNDI.  It is then
052     * available as a source of physical connections to the database, when
053     * the pooling <code>DataSource</code> needs to create a new 
054     * physical connection.
055     * </p>
056     *
057     * <p>
058     * Although normally used within a JNDI environment, the DriverAdapterCPDS
059     * can be instantiated and initialized as any bean and then attached
060     * directly to a pooling <code>DataSource</code>. 
061     * <code>Jdbc2PoolDataSource</code> can use the 
062     * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
063     * </p>
064     *
065     * <p>
066     * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
067     * which is not generally available in jbdc2 
068     * <code>ConnectionPoolDataSource</code> implementation, but is 
069     * addressed within the jdbc3 specification.  The <code>PreparedStatement</code>
070     * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
071     * it has not undergone extensive testing in the configuration used here.
072     * It should be considered experimental and can be toggled with the 
073     * poolPreparedStatements attribute.
074     * </p>
075     *
076     * <p>
077     * The <a href="package-summary.html">package documentation</a> contains an 
078     * example using catalina and JNDI.  The <a 
079     * href="../datasources/package-summary.html">datasources package documentation</a>
080     * shows how to use <code>DriverAdapterCPDS</code> as a source for
081     * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
082     * </p>
083     *
084     * @author John D. McNally
085     * @version $Revision: 892307 $ $Date: 2013-12-31 23:27:28 +0000 (Tue, 31 Dec 2013) $
086     */
087    public class DriverAdapterCPDS
088        implements ConnectionPoolDataSource, Referenceable, Serializable, 
089                   ObjectFactory {
090      
091        private static final long serialVersionUID = -4820523787212147844L;
092    
093    
094        private static final String GET_CONNECTION_CALLED 
095                = "A PooledConnection was already requested from this source, " 
096                + "further initialization is not allowed.";
097    
098        /** Description */
099        private String description;
100        /** Password */
101        private String password;
102        /** Url name */
103        private String url;
104        /** User name */
105        private String user;
106        /** Driver class name */
107        private String driver;
108    
109        /** Login TimeOut in seconds */
110        private int loginTimeout;
111        /** Log stream. NOT USED */
112        private transient PrintWriter logWriter = null;
113    
114        // PreparedStatement pool properties
115        private boolean poolPreparedStatements;
116        private int maxActive = 10;
117        private int maxIdle = 10;
118        private int _timeBetweenEvictionRunsMillis = -1;
119        private int _numTestsPerEvictionRun = -1;
120        private int _minEvictableIdleTimeMillis = -1;
121        private int _maxPreparedStatements = -1;
122    
123        /** Whether or not getConnection has been called */
124        private volatile boolean getConnectionCalled = false;
125        
126        /** Connection properties passed to JDBC Driver */
127        private Properties connectionProperties = null;
128    
129        static {
130            // Attempt to prevent deadlocks - see DBCP - 272
131            DriverManager.getDrivers();
132        }
133    
134        /** 
135         * Controls access to the underlying connection 
136         */
137        private boolean accessToUnderlyingConnectionAllowed = false; 
138        
139        /**
140         * Default no-arg constructor for Serialization
141         */
142        public DriverAdapterCPDS() {
143        }
144    
145        /**
146         * Attempt to establish a database connection using the default
147         * user and password.
148         */
149        public PooledConnection getPooledConnection() throws SQLException {
150            return getPooledConnection(getUser(), getPassword());
151        }
152                         
153        /**
154         * Attempt to establish a database connection.
155         * @param username name to be used for the connection
156         * @param pass password to be used fur the connection
157         */
158        public PooledConnection getPooledConnection(String username, 
159                                                    String pass)
160                throws SQLException {
161            getConnectionCalled = true;
162            /*
163            public GenericKeyedObjectPool(KeyedPoolableObjectFactory factory, 
164            int maxActive, byte whenExhaustedAction, long maxWait, 
165            int maxIdle, boolean testOnBorrow, boolean testOnReturn, 
166            long timeBetweenEvictionRunsMillis, 
167            int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, 
168            boolean testWhileIdle) {
169            */
170            KeyedObjectPool stmtPool = null;
171            if (isPoolPreparedStatements()) {
172                if (getMaxPreparedStatements() <= 0)
173                {
174                    // since there is no limit, create a prepared statement pool with an eviction thread
175                    //  evictor settings are the same as the connection pool settings.
176                    stmtPool = new GenericKeyedObjectPool(null,
177                        getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
178                        getMaxIdle(), false, false,
179                        getTimeBetweenEvictionRunsMillis(),getNumTestsPerEvictionRun(),getMinEvictableIdleTimeMillis(),
180                        false);
181                }
182                else
183                {
184                    // since there is limit, create a prepared statement pool without an eviction thread
185                    //  pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
186                    // see org.apache.commons.pool.impl.GenericKeyedObjectPool.clearOldest method
187                    stmtPool = new GenericKeyedObjectPool(null,
188                        getMaxActive(), GenericKeyedObjectPool.WHEN_EXHAUSTED_GROW, 0,
189                        getMaxIdle(), getMaxPreparedStatements(), false, false,
190                        -1,0,0, // -1 tells the pool that there should be no eviction thread.
191                        false);
192                }
193            }
194            // Workaround for buggy WebLogic 5.1 classloader - ignore the
195            // exception upon first invocation.
196            try {
197                PooledConnectionImpl pci = null;
198                if (connectionProperties != null) {
199                    connectionProperties.put("user", username);
200                    connectionProperties.put("password", pass);
201                    pci = new PooledConnectionImpl(
202                            DriverManager.getConnection(getUrl(), connectionProperties), 
203                            stmtPool);
204                } else {
205                    pci = new PooledConnectionImpl(
206                            DriverManager.getConnection(getUrl(), username, pass), 
207                            stmtPool);
208                }
209                pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
210                return pci;
211            }
212            catch (ClassCircularityError e)
213            {
214                PooledConnectionImpl pci = null;
215                if (connectionProperties != null) {
216                    pci = new PooledConnectionImpl(
217                            DriverManager.getConnection(getUrl(), connectionProperties), 
218                            stmtPool);
219                } else {
220                    pci = new PooledConnectionImpl(
221                            DriverManager.getConnection(getUrl(), username, pass), 
222                            stmtPool);
223                }
224                pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
225                return pci;
226            }
227        }
228    
229        // ----------------------------------------------------------------------
230        // Referenceable implementation 
231    
232        /**
233         * <CODE>Referenceable</CODE> implementation.
234         */
235        public Reference getReference() throws NamingException {
236            // this class implements its own factory
237            String factory = getClass().getName();
238            
239            Reference ref = new Reference(getClass().getName(), factory, null);
240    
241            ref.add(new StringRefAddr("description", getDescription()));
242            ref.add(new StringRefAddr("driver", getDriver()));
243            ref.add(new StringRefAddr("loginTimeout", 
244                                      String.valueOf(getLoginTimeout())));
245            ref.add(new StringRefAddr("password", getPassword()));
246            ref.add(new StringRefAddr("user", getUser()));
247            ref.add(new StringRefAddr("url", getUrl()));
248    
249            ref.add(new StringRefAddr("poolPreparedStatements", 
250                                      String.valueOf(isPoolPreparedStatements())));
251            ref.add(new StringRefAddr("maxActive", 
252                                      String.valueOf(getMaxActive())));
253            ref.add(new StringRefAddr("maxIdle", 
254                                      String.valueOf(getMaxIdle())));
255            ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", 
256                String.valueOf(getTimeBetweenEvictionRunsMillis())));
257            ref.add(new StringRefAddr("numTestsPerEvictionRun", 
258                String.valueOf(getNumTestsPerEvictionRun())));
259            ref.add(new StringRefAddr("minEvictableIdleTimeMillis", 
260                String.valueOf(getMinEvictableIdleTimeMillis())));
261            ref.add(new StringRefAddr("maxPreparedStatements",
262                String.valueOf(getMaxPreparedStatements())));
263    
264            return ref;
265        }
266    
267    
268        // ----------------------------------------------------------------------
269        // ObjectFactory implementation 
270    
271        /**
272         * implements ObjectFactory to create an instance of this class
273         */ 
274        public Object getObjectInstance(Object refObj, Name name, 
275                                        Context context, Hashtable env) 
276                throws Exception {
277            // The spec says to return null if we can't create an instance 
278            // of the reference
279            DriverAdapterCPDS cpds = null;
280            if (refObj instanceof Reference) {
281                Reference ref = (Reference)refObj;
282                if (ref.getClassName().equals(getClass().getName())) {
283                    RefAddr ra = ref.get("description");
284                    if (ra != null && ra.getContent() != null) {
285                        setDescription(ra.getContent().toString());
286                    }
287    
288                    ra = ref.get("driver");
289                    if (ra != null && ra.getContent() != null) {
290                        setDriver(ra.getContent().toString());
291                    }
292                    ra = ref.get("url");
293                    if (ra != null && ra.getContent() != null) {
294                        setUrl(ra.getContent().toString());
295                    }
296                    ra = ref.get("user");
297                    if (ra != null && ra.getContent() != null) {
298                        setUser(ra.getContent().toString());
299                    }
300                    ra = ref.get("password");
301                    if (ra != null && ra.getContent() != null) {
302                        setPassword(ra.getContent().toString());
303                    }
304    
305                    ra = ref.get("poolPreparedStatements");
306                    if (ra != null && ra.getContent() != null) {
307                        setPoolPreparedStatements(Boolean.valueOf(
308                            ra.getContent().toString()).booleanValue());
309                    }
310                    ra = ref.get("maxActive");
311                    if (ra != null && ra.getContent() != null) {
312                        setMaxActive(Integer.parseInt(ra.getContent().toString()));
313                    }
314    
315                    ra = ref.get("maxIdle");
316                    if (ra != null && ra.getContent() != null) {
317                        setMaxIdle(Integer.parseInt(ra.getContent().toString()));
318                    }
319    
320                    ra = ref.get("timeBetweenEvictionRunsMillis");
321                    if (ra != null && ra.getContent() != null) {
322                        setTimeBetweenEvictionRunsMillis(
323                            Integer.parseInt(ra.getContent().toString()));
324                    }
325    
326                    ra = ref.get("numTestsPerEvictionRun");
327                    if (ra != null && ra.getContent() != null) {
328                        setNumTestsPerEvictionRun(
329                            Integer.parseInt(ra.getContent().toString()));
330                    }
331    
332                    ra = ref.get("minEvictableIdleTimeMillis");
333                    if (ra != null && ra.getContent() != null) {
334                        setMinEvictableIdleTimeMillis(
335                            Integer.parseInt(ra.getContent().toString()));
336                    }
337                    ra = ref.get("maxPreparedStatements");
338                    if (ra != null && ra.getContent() != null) {
339                        setMaxPreparedStatements(
340                            Integer.parseInt(ra.getContent().toString()));
341                    }
342    
343                    cpds = this;
344                }
345            }
346            return cpds;
347        }
348    
349        /**
350         * Throws an IllegalStateException, if a PooledConnection has already
351         * been requested.
352         */
353        private void assertInitializationAllowed() throws IllegalStateException {
354            if (getConnectionCalled) {
355                throw new IllegalStateException(GET_CONNECTION_CALLED);
356            }
357        }
358    
359        // ----------------------------------------------------------------------
360        // Properties
361        
362        /**
363         * Get the connection properties passed to the JDBC driver.
364         * 
365         * @return the JDBC connection properties used when creating connections.
366         * @since 1.3
367         */
368        public Properties getConnectionProperties() {
369            return connectionProperties;
370        }
371        
372        /**
373         * <p>Set the connection properties passed to the JDBC driver.</p>
374         * 
375         * <p>If <code>props</code> contains "user" and/or "password"
376         * properties, the corresponding instance properties are set. If these
377         * properties are not present, they are filled in using
378         * {@link #getUser()}, {@link #getPassword()} when {@link #getPooledConnection()}
379         * is called, or using the actual parameters to the method call when 
380         * {@link #getPooledConnection(String, String)} is called. Calls to
381         * {@link #setUser(String)} or {@link #setPassword(String)} overwrite the values
382         * of these properties if <code>connectionProperties</code> is not null.</p>
383         * 
384         * @param props Connection properties to use when creating new connections.
385         * @since 1.3
386         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
387         */
388        public void setConnectionProperties(Properties props) {
389            assertInitializationAllowed();
390            connectionProperties = props;
391            if (connectionProperties.containsKey("user")) {
392                setUser(connectionProperties.getProperty("user"));
393            }
394            if (connectionProperties.containsKey("password")) {
395                setPassword(connectionProperties.getProperty("password"));
396            }
397        }
398        
399        /**
400         * Get the value of description.  This property is here for use by
401         * the code which will deploy this datasource.  It is not used
402         * internally.
403         *
404         * @return value of description, may be null.
405         * @see #setDescription(String)
406         */
407        public String getDescription() {
408            return description;
409        }
410        
411        /**
412         * Set the value of description.  This property is here for use by
413         * the code which will deploy this datasource.  It is not used
414         * internally.
415         *
416         * @param v  Value to assign to description.
417         */
418        public void setDescription(String  v) {
419            this.description = v;
420        }
421    
422        /**
423         * Get the value of password for the default user.
424         * @return value of password.
425         */
426        public String getPassword() {
427            return password;
428        }
429        
430        /**
431         * Set the value of password for the default user.
432         * @param v  Value to assign to password.
433         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
434         */
435        public void setPassword(String v) {
436            assertInitializationAllowed();
437            this.password = v;
438            if (connectionProperties != null) {
439                connectionProperties.setProperty("password", v);
440            }
441        }
442    
443        /**
444         * Get the value of url used to locate the database for this datasource.
445         * @return value of url.
446         */
447        public String getUrl() {
448            return url;
449        }
450        
451        /**
452         * Set the value of url used to locate the database for this datasource.
453         * @param v  Value to assign to url.
454         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
455        */
456        public void setUrl(String v) {
457            assertInitializationAllowed();
458            this.url = v;
459        }
460    
461        /**
462         * Get the value of default user (login or username).
463         * @return value of user.
464         */
465        public String getUser() {
466            return user;
467        }
468        
469        /**
470         * Set the value of default user (login or username).
471         * @param v  Value to assign to user.
472         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
473         */
474        public void setUser(String v) {
475            assertInitializationAllowed();
476            this.user = v;
477            if (connectionProperties != null) {
478                connectionProperties.setProperty("user", v);
479            }
480        }
481    
482        /**
483         * Get the driver classname.
484         * @return value of driver.
485         */
486        public String getDriver() {
487            return driver;
488        }
489        
490        /**
491         * Set the driver classname.  Setting the driver classname cause the 
492         * driver to be registered with the DriverManager.
493         * @param v  Value to assign to driver.
494         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
495         */
496        public void setDriver(String v) throws ClassNotFoundException {
497            assertInitializationAllowed();
498            this.driver = v;
499            // make sure driver is registered
500            Class.forName(v);
501        }
502        
503        /**
504         * Gets the maximum time in seconds that this data source can wait 
505         * while attempting to connect to a database. NOT USED.
506         */
507        public int getLoginTimeout() {
508            return loginTimeout;
509        }
510                               
511        /**
512         * Get the log writer for this data source. NOT USED.
513         */
514        public PrintWriter getLogWriter() {
515            return logWriter;
516        }
517                               
518        /**
519         * Sets the maximum time in seconds that this data source will wait 
520         * while attempting to connect to a database. NOT USED.
521         */
522        public void setLoginTimeout(int seconds) {
523            loginTimeout = seconds;
524        } 
525                               
526        /**
527         * Set the log writer for this data source. NOT USED.
528         */
529        public void setLogWriter(java.io.PrintWriter out) {
530            logWriter = out;
531        } 
532    
533    
534        // ------------------------------------------------------------------
535        // PreparedStatement pool properties
536    
537        
538        /**
539         * Flag to toggle the pooling of <code>PreparedStatement</code>s
540         * @return value of poolPreparedStatements.
541         */
542        public boolean isPoolPreparedStatements() {
543            return poolPreparedStatements;
544        }
545        
546        /**
547         * Flag to toggle the pooling of <code>PreparedStatement</code>s
548         * @param v  true to pool statements.
549         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
550         */
551        public void setPoolPreparedStatements(boolean v) {
552            assertInitializationAllowed();
553            this.poolPreparedStatements = v;
554        }
555    
556        /**
557         * The maximum number of active statements that can be allocated from
558         * this pool at the same time, or non-positive for no limit.
559         */
560        public int getMaxActive() {
561            return (this.maxActive);
562        }
563    
564        /**
565         * The maximum number of active statements that can be allocated from
566         * this pool at the same time, or non-positive for no limit.
567         * @param maxActive the maximum number of concurrent active statements allowed
568         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
569         */
570        public void setMaxActive(int maxActive) {
571            assertInitializationAllowed();
572            this.maxActive = maxActive;
573        }
574    
575        /**
576         * The maximum number of statements that can remain idle in the
577         * pool, without extra ones being released, or negative for no limit.
578         * @return the value of maxIdle
579         */
580        public int getMaxIdle() {
581            return (this.maxIdle);
582        }
583    
584        /**
585         * The maximum number of statements that can remain idle in the
586         * pool, without extra ones being released, or negative for no limit.
587         * 
588         * @param maxIdle The maximum number of statements that can remain idle
589         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
590         */
591        public void setMaxIdle(int maxIdle) {
592            assertInitializationAllowed();
593            this.maxIdle = maxIdle;
594        }
595    
596        /**
597         * Returns the number of milliseconds to sleep between runs of the
598         * idle object evictor thread.
599         * When non-positive, no idle object evictor thread will be
600         * run.
601         * @return the value of the evictor thread timer
602         * @see #setTimeBetweenEvictionRunsMillis(int)
603         */
604        public int getTimeBetweenEvictionRunsMillis() {
605            return _timeBetweenEvictionRunsMillis;
606        }
607    
608        /**
609         * Sets the number of milliseconds to sleep between runs of the
610         * idle object evictor thread.
611         * When non-positive, no idle object evictor thread will be
612         * run.
613         * @param timeBetweenEvictionRunsMillis
614         * @see #getTimeBetweenEvictionRunsMillis()
615         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
616         */
617        public void setTimeBetweenEvictionRunsMillis(
618                int timeBetweenEvictionRunsMillis) {
619            assertInitializationAllowed();
620            _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
621        }
622    
623        /**
624         * Returns the number of statements to examine during each run of the
625         * idle object evictor thread (if any).
626         *
627         * *see #setNumTestsPerEvictionRun
628         * *see #setTimeBetweenEvictionRunsMillis
629         */
630        public int getNumTestsPerEvictionRun() {
631            return _numTestsPerEvictionRun;
632        }
633    
634        /**
635         * Sets the number of statements to examine during each run of the
636         * idle object evictor thread (if any).
637         * <p>
638         * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
639         * tests will be run.  I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
640         * idle objects will be tested per run.
641         * 
642         * @param numTestsPerEvictionRun number of statements to examine per run
643         * @see #getNumTestsPerEvictionRun()
644         * @see #setTimeBetweenEvictionRunsMillis(int)
645         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
646         */
647        public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
648            assertInitializationAllowed();
649            _numTestsPerEvictionRun = numTestsPerEvictionRun;
650        }
651    
652        /**
653         * Returns the minimum amount of time a statement may sit idle in the pool
654         * before it is eligible for eviction by the idle object evictor
655         * (if any).
656         *
657         * *see #setMinEvictableIdleTimeMillis
658         * *see #setTimeBetweenEvictionRunsMillis
659         */
660        public int getMinEvictableIdleTimeMillis() {
661            return _minEvictableIdleTimeMillis;
662        }
663    
664        /**
665         * Sets the minimum amount of time a statement may sit idle in the pool
666         * before it is eligable for eviction by the idle object evictor
667         * (if any).
668         * When non-positive, no objects will be evicted from the pool
669         * due to idle time alone.
670         * @param minEvictableIdleTimeMillis minimum time to set (in ms)
671         * @see #getMinEvictableIdleTimeMillis()
672         * @see #setTimeBetweenEvictionRunsMillis(int)
673         * @throws IllegalStateException if {@link #getPooledConnection()} has been called
674         */
675        public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
676            assertInitializationAllowed();
677            _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
678        }
679        
680        /**
681         * Returns the value of the accessToUnderlyingConnectionAllowed property.
682         * 
683         * @return true if access to the underlying is allowed, false otherwise.
684         */
685        public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
686            return this.accessToUnderlyingConnectionAllowed;
687        }
688    
689        /**
690         * Sets the value of the accessToUnderlyingConnectionAllowed property.
691         * It controls if the PoolGuard allows access to the underlying connection.
692         * (Default: false)
693         * 
694         * @param allow Access to the underlying connection is granted when true.
695         */
696        public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
697            this.accessToUnderlyingConnectionAllowed = allow;
698        }
699        
700        /**
701         * Returns the maximun number of prepared statements.
702         * 
703         * @return maxPrepartedStatements value
704         * @since 1.2.2
705         */
706        public int getMaxPreparedStatements()
707        {
708            return _maxPreparedStatements;
709        }
710    
711        /**
712         * Sets the maximum number of prepared statements.
713         * @param maxPreparedStatements the new maximum number of prepared 
714         * statements
715         * 
716         * @since 1.2.2
717         */
718        public void setMaxPreparedStatements(int maxPreparedStatements)
719        {
720            _maxPreparedStatements = maxPreparedStatements;
721        }
722    }