View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.dbcp.cpdsadapter;
19  
20  import java.util.Hashtable;
21  import java.util.Properties;
22  import java.io.PrintWriter;
23  import java.io.Serializable;
24  import java.sql.DriverManager;
25  import java.sql.SQLException;
26  import javax.sql.PooledConnection;
27  import javax.sql.ConnectionPoolDataSource;
28  import javax.naming.Name;
29  import javax.naming.Context;
30  import javax.naming.Referenceable;
31  import javax.naming.spi.ObjectFactory;
32  import javax.naming.Reference;
33  import javax.naming.RefAddr;
34  import javax.naming.StringRefAddr;
35  import javax.naming.NamingException;
36  
37  import org.apache.commons.pool.KeyedObjectPool;
38  import org.apache.commons.pool.impl.GenericKeyedObjectPool;
39  
40  /**
41   * <p>
42   * An adapter for jdbc drivers that do not include an implementation
43   * of {@link javax.sql.ConnectionPoolDataSource}, but still include a 
44   * {@link java.sql.DriverManager} implementation.  
45   * <code>ConnectionPoolDataSource</code>s are not used within general 
46   * applications.  They are used by <code>DataSource</code> implementations
47   * that pool <code>Connection</code>s, such as 
48   * {@link org.apache.commons.dbcp.datasources.SharedPoolDataSource}.  A J2EE
49   * container will normally provide some method of initializing the
50   * <code>ConnectionPoolDataSource</code> whose attributes are presented
51   * as bean getters/setters and then deploying it via JNDI.  It is then
52   * available as a source of physical connections to the database, when
53   * the pooling <code>DataSource</code> needs to create a new 
54   * physical connection.
55   * </p>
56   *
57   * <p>
58   * Although normally used within a JNDI environment, the DriverAdapterCPDS
59   * can be instantiated and initialized as any bean and then attached
60   * directly to a pooling <code>DataSource</code>. 
61   * <code>Jdbc2PoolDataSource</code> can use the 
62   * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
63   * </p>
64   *
65   * <p>
66   * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
67   * which is not generally available in jbdc2 
68   * <code>ConnectionPoolDataSource</code> implementation, but is 
69   * addressed within the jdbc3 specification.  The <code>PreparedStatement</code>
70   * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
71   * it has not undergone extensive testing in the configuration used here.
72   * It should be considered experimental and can be toggled with the 
73   * poolPreparedStatements attribute.
74   * </p>
75   *
76   * <p>
77   * The <a href="package-summary.html">package documentation</a> contains an 
78   * example using catalina and JNDI.  The <a 
79   * href="../datasources/package-summary.html">datasources package documentation</a>
80   * shows how to use <code>DriverAdapterCPDS</code> as a source for
81   * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
82   * </p>
83   *
84   * @author John D. McNally
85   * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
86   */
87  public class DriverAdapterCPDS
88      implements ConnectionPoolDataSource, Referenceable, Serializable, 
89                 ObjectFactory {
90    
91      private static final long serialVersionUID = -4820523787212147844L;
92  
93  
94      private static final String GET_CONNECTION_CALLED 
95              = "A PooledConnection was already requested from this source, " 
96              + "further initialization is not allowed.";
97  
98      /** Description */
99      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 }