View Javadoc

1   package org.apache.torque.pool;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.apache.org/>.
55   */
56  
57  import java.io.PrintWriter;
58  import java.io.Serializable;
59  import java.sql.Connection;
60  import java.sql.SQLException;
61  import java.util.HashMap;
62  import java.util.Hashtable;
63  import java.util.Map;
64  import java.util.Properties;
65  import javax.naming.BinaryRefAddr;
66  import javax.naming.Context;
67  import javax.naming.InitialContext;
68  import javax.naming.Name;
69  import javax.naming.NamingException;
70  import javax.naming.RefAddr;
71  import javax.naming.Reference;
72  import javax.naming.Referenceable;
73  import javax.naming.StringRefAddr;
74  import javax.naming.spi.ObjectFactory;
75  import javax.sql.ConnectionPoolDataSource;
76  import javax.sql.DataSource;
77  import org.apache.commons.lang.SerializationUtils;
78  
79  /***
80   * Torque's default connection pool DataSource
81   *
82   * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
83   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
84   * @version $Id: TorqueClassicDataSource.java,v 1.11 2003/08/18 21:48:11 mpoeschl Exp $
85   * @deprecated as of version 3.1
86   */
87  public class TorqueClassicDataSource
88      implements DataSource, Referenceable, Serializable, ObjectFactory
89  {
90      /*** Pools keyed by username. */
91      private static Map pools = new HashMap();
92  
93      /*** Counter used to create an internal unique name od the Data Source */
94      private static int cpdsCounter;
95  
96      /*** DataSource Name used to find the ConnectionPoolDataSource */
97      private String dataSourceName;
98  
99      /*** Description */
100     private String description;
101 
102     /*** Login TimeOut in seconds */
103     private int loginTimeout;
104 
105     /*** Pool Data Source that is used to fetch connections */
106     private ConnectionPoolDataSource cpds;
107 
108     /*** Log stream */
109     private PrintWriter logWriter;
110 
111     /*** Environment that may be used to set up a jndi initial context. */
112     private Properties jndiEnvironment;
113 
114     /*** Maximum Number of Connections cached in this Data Source */
115     private int defaultMaxConnections;
116 
117     /***
118      * Maximum Number of Connections for a specified User in this Data
119      * Source
120      */
121     private Properties perUserMaxConnections;
122 
123     /*** Maximum lifetime of a database connection */
124     private int maxExpiryTime;
125 
126     /***
127      * time to wait when initiating a connection
128      * for the database to respond
129      */
130     private int connectionWaitTimeout;
131 
132     /*** Interval (in seconds) that the monitor thread reports the pool state */
133     private int logInterval;
134 
135     /*** Do connections from this pool are auto-committing? */
136     private boolean defaultAutoCommit;
137 
138     /*** Are connections from this pool read-only? */
139     private boolean defaultReadOnly;
140 
141     /***
142      * Default no-arg constructor for Serialization
143      */
144     public TorqueClassicDataSource()
145     {
146         defaultAutoCommit = true;
147     }
148 
149     // Properties
150 
151     /***
152      * Get the number of database connections to cache per user.
153      * This value is used for any username which is not specified
154      * in perUserMaxConnections.  The default is 1.
155      *
156      * @return value of maxConnections.
157      */
158     public int getDefaultMaxConnections()
159     {
160         return defaultMaxConnections;
161     }
162 
163     /***
164      * Set the number of database connections to cache per user.
165      * This value is used for any username which is not specified
166      * in perUserMaxConnections.  The default is 1.
167      *
168      * @param v  Value to assign to maxConnections.
169      */
170     public void setDefaultMaxConnections(int  v)
171     {
172         this.defaultMaxConnections = v;
173     }
174 
175     /***
176      * Get the number of database connections to cache per user.  The keys
177      * are usernames and the value is the maximum connections.  Any username
178      * specified here will override the value of defaultMaxConnections.
179      *
180      * @return value of perUserMaxConnections.
181      */
182     public Properties getPerUserMaxConnections()
183     {
184         return perUserMaxConnections;
185     }
186 
187     /***
188      * Set the number of database connections to cache per user.  The keys
189      * are usernames and the value is the maximum connections.  Any username
190      * specified here will override the value of defaultMaxConnections.
191      *
192      * @param v  Value to assign to perUserMaxConnections.
193      */
194     public void setPerUserMaxConnections(Properties  v)
195     {
196         this.perUserMaxConnections = v;
197     }
198 
199     /***
200      * Get the amount of time (in seconds) that database connections
201      * will be cached.  The default is 3600 (1 hour).
202      *
203      * @return value of expiryTime.
204      */
205     public int getMaxExpiryTime()
206     {
207         return maxExpiryTime;
208     }
209 
210     /***
211      * Set the amount of time (in seconds) that database connections
212      * will be cached.  The default is 3600 (1 hour).
213      *
214      * @param v  Value to assign to expiryTime.
215      */
216     public void setMaxExpiryTime(int v)
217     {
218         this.maxExpiryTime = v;
219     }
220 
221     /***
222      * Get the amount of time (in seconds) a connection request will
223      * have to wait before a time out occurs and an error is thrown.
224      * The default is 10 seconds.
225      *
226      * @return value of connectionWaitTimeout.
227      */
228     public int getConnectionWaitTimeout()
229     {
230         return connectionWaitTimeout;
231     }
232 
233     /***
234      * Eet the amount of time (in seconds) a connection request will
235      * have to wait before a time out occurs and an error is thrown.
236      * The default is 10 seconds.
237      *
238      * @param v  Value to assign to connectionWaitTimeout.
239      */
240     public void setConnectionWaitTimeout(int v)
241     {
242         this.connectionWaitTimeout = v;
243     }
244 
245     /***
246      * Get the interval (in seconds) between which the ConnectionPool logs
247      * the status of it's Connections. Default is 0 which indicates no
248      * logging.
249      *
250      * @return value of logInterval.
251      */
252     public int getLogInterval()
253     {
254         return logInterval;
255     }
256 
257     /***
258      * Set the interval (in seconds) between which the ConnectionPool logs
259      * the status of it's Connections. Default is 0 which indicates no
260      * logging.
261      *
262      * @param v  Value to assign to logInterval.
263      */
264     public void setLogInterval(int v)
265     {
266         this.logInterval = v;
267     }
268 
269     /***
270      * Get the value of defaultAutoCommit, which defines the state of
271      * connections handed out from this pool.  The value can be changed
272      * on the Connection using Connection.setAutoCommit(boolean).
273      * The default is true.
274      *
275      * @return value of defaultAutoCommit.
276      */
277     public boolean isDefaultAutoCommit()
278     {
279         return defaultAutoCommit;
280     }
281 
282     /***
283      * Set the value of defaultAutoCommit, which defines the state of
284      * connections handed out from this pool.  The value can be changed
285      * on the Connection using Connection.setAutoCommit(boolean).
286      * The default is true.
287      *
288      * @param v  Value to assign to defaultAutoCommit.
289      */
290     public void setDefaultAutoCommit(boolean v)
291     {
292         this.defaultAutoCommit = v;
293     }
294 
295     /***
296      * Get the value of defaultReadOnly, which defines the state of
297      * connections handed out from this pool.  The value can be changed
298      * on the Connection using Connection.setReadOnly(boolean).
299      * The default is false.
300      *
301      * @return value of defaultReadOnly.
302      */
303     public boolean isDefaultReadOnly()
304     {
305         return defaultReadOnly;
306     }
307 
308     /***
309      * Set the value of defaultReadOnly, which defines the state of
310      * connections handed out from this pool.  The value can be changed
311      * on the Connection using Connection.setReadOnly(boolean).
312      * The default is false.
313      *
314      * @param v  Value to assign to defaultReadOnly.
315      */
316     public void setDefaultReadOnly(boolean v)
317     {
318         this.defaultReadOnly = v;
319     }
320 
321     /***
322      * Get the name of the ConnectionPoolDataSource which backs this pool.
323      * This name is used to look up the datasource from a jndi service
324      * provider.
325      *
326      * @return value of dataSourceName.
327      */
328     public String getDataSourceName()
329     {
330         return dataSourceName;
331     }
332 
333     /***
334      * Set the name of the ConnectionPoolDataSource which backs this pool.
335      * This name is used to look up the datasource from a jndi service
336      * provider.
337      *
338      * @param v  Value to assign to dataSourceName.
339      */
340     public void setDataSourceName(String v)
341     {
342         if (getConnectionPoolDataSource() != null)
343         {
344             throw new IllegalStateException("connectionPoolDataSource property"
345                 + " already has a value.  Both dataSourceName and "
346                 + "connectionPoolDataSource properties cannot be set.");
347         }
348 
349         this.dataSourceName = v;
350     }
351 
352 
353     /***
354      * Get the description.  This property is defined by jdbc as for use with
355      * GUI (or other) tools that might deploy the datasource.  It serves no
356      * internal purpose.
357      *
358      * @return value of description.
359      */
360     public String getDescription()
361     {
362         return description;
363     }
364 
365     /***
366      * Set the description.  This property is defined by jdbc as for use with
367      * GUI (or other) tools that might deploy the datasource.  It serves no
368      * internal purpose.
369      *
370      * @param v  Value to assign to description.
371      */
372     public void setDescription(String v)
373     {
374         this.description = v;
375     }
376 
377 
378     /***
379      * Get the value of jndiEnvironment which is used when instantiating
380      * a jndi InitialContext.  This InitialContext is used to locate the
381      * backend ConnectionPoolDataSource.
382      *
383      * @param key environment key
384      * @return value of jndiEnvironment.
385      */
386     public String getJndiEnvironment(String key)
387     {
388         String value = null;
389         if (jndiEnvironment != null)
390         {
391             value = jndiEnvironment.getProperty(key);
392         }
393         return value;
394     }
395 
396     /***
397      * Set the value of jndiEnvironment which is used when instantiating
398      * a jndi InitialContext.  This InitialContext is used to locate the
399      * backend ConnectionPoolDataSource.
400      *
401      * @param key environment key
402      * @param value  Value to assign to jndiEnvironment.
403      */
404     public void setJndiEnvironment(String key, String value)
405     {
406         if (jndiEnvironment == null)
407         {
408             jndiEnvironment = new Properties();
409         }
410         jndiEnvironment.setProperty(key, value);
411     }
412 
413 
414     /***
415      * Get the value of connectionPoolDataSource.  This method will return
416      * null, if the backing datasource is being accessed via jndi.
417      *
418      * @return value of connectionPoolDataSource.
419      */
420     public ConnectionPoolDataSource getConnectionPoolDataSource()
421     {
422         return cpds;
423     }
424 
425     /***
426      * Set the backend ConnectionPoolDataSource.  This property should not be
427      * set if using jndi to access the datasource.
428      *
429      * @param v  Value to assign to connectionPoolDataSource.
430      */
431     public void setConnectionPoolDataSource(ConnectionPoolDataSource  v)
432     {
433         if (v == null)
434         {
435             throw new IllegalArgumentException(
436                 "Null argument value is not allowed.");
437         }
438         if (getDataSourceName() != null)
439         {
440             throw new IllegalStateException("dataSourceName property"
441                 + " already has a value.  Both dataSourceName and "
442                 + "connectionPoolDataSource properties cannot be set.");
443         }
444         this.cpds = v;
445 
446         // set the dataSourceName to a unique value
447         dataSourceName = v.hashCode() + " internal cpds name " + cpdsCounter++;
448     }
449 
450     /***
451      * Attempt to establish a database connection.
452      *
453      * @return A database connection.
454      * @throws SQLException
455      */
456     public Connection getConnection() throws SQLException
457     {
458         return getConnection(null, null);
459     }
460 
461     /***
462      * Attempt to establish a database connection.
463      *
464      * @param username The name of the database user.
465      * @param password The password of the database user.
466      * @return         A database connection.
467      * @throws SQLException
468      */
469     public synchronized Connection getConnection(String username,
470                                                  String password)
471         throws SQLException
472     {
473         String key = getKey(username);
474         ConnectionPool pool = (ConnectionPool) pools.get(key);
475         if (pool == null)
476         {
477             try
478             {
479                 registerPool(username, password);
480                 pool = (ConnectionPool) pools.get(key);
481             }
482             catch (Exception e)
483             {
484                 throw new SQLException(e.getMessage());
485             }
486         }
487 
488         Connection con = pool.getConnection(username, password).getConnection();
489         con.setAutoCommit(defaultAutoCommit);
490         con.setReadOnly(defaultReadOnly);
491         return con;
492     }
493 
494     /***
495      *
496      * @param suffix
497      * @return
498      */
499     private String getKey(String suffix)
500     {
501         String key = getDataSourceName();
502         if (key == null)
503         {
504             throw new IllegalStateException("Attempted to use DataSource "
505                 + "without a backend ConnectionPoolDataSource defined.");
506         }
507 
508         if (suffix != null)
509         {
510             key += suffix;
511         }
512         return key;
513     }
514 
515     /***
516      *
517      * @param username The name of the database user.
518      * @param password The password of the database user.
519      * @throws javax.naming.NamingException
520      */
521     synchronized private void registerPool(String username, String password)
522          throws javax.naming.NamingException
523     {
524         String key = getKey(username);
525         if (!pools.containsKey(key))
526         {
527             ConnectionPoolDataSource cpds = this.cpds;
528             if (cpds == null)
529             {
530                 Context ctx = null;
531                 if (jndiEnvironment == null)
532                 {
533                     ctx = new InitialContext();
534                 }
535                 else
536                 {
537                     ctx = new InitialContext(jndiEnvironment);
538                 }
539                 cpds = (ConnectionPoolDataSource) ctx.lookup(dataSourceName);
540             }
541 
542             int maxConnections = getDefaultMaxConnections();
543             if (username != null)
544             {
545                 String userMaxCon =
546                     (String) getPerUserMaxConnections().get(username);
547                 if (userMaxCon != null)
548                 {
549                     maxConnections = Integer.parseInt(userMaxCon);
550                 }
551             }
552 
553             ConnectionPool pool = new ConnectionPool(cpds, username, password,
554                 maxConnections,
555                 getMaxExpiryTime(),
556                 getConnectionWaitTimeout(),
557                 getLogInterval());
558 
559             // avoid ConcurrentModificationException
560             Map newPools = new HashMap(pools);
561             newPools.put(key, pool);
562             pools = newPools;
563         }
564     }
565 
566     /***
567      * Gets the maximum time in seconds that this data source can wait
568      * while attempting to connect to a database.
569      *
570      * @return the login timeout
571      */
572     public int getLoginTimeout()
573     {
574         return loginTimeout;
575     }
576 
577     /***
578      * Get the log writer for this data source.
579      *
580      * @return the log writer
581      * @deprecated Use correct debugging and logging code from Log4j
582      */
583     public PrintWriter getLogWriter()
584     {
585         if (logWriter == null)
586         {
587             logWriter = new PrintWriter(System.out);
588         }
589         return logWriter;
590     }
591 
592     /***
593      * Sets the maximum time in seconds that this data source will wait
594      * while attempting to connect to a database. NOT USED.
595      *
596      * @param seconds the login timeout
597      */
598     public void setLoginTimeout(int seconds)
599     {
600         loginTimeout = seconds;
601     }
602 
603     /***
604      * Set the log writer for this data source.
605      *
606      * @param out the log writer to use
607      * @deprecated Use correct debugging and logging code from Log4j
608      */
609     public void setLogWriter(java.io.PrintWriter out)
610     {
611         logWriter = out;
612     }
613 
614     /***
615      * <CODE>Referenceable</CODE> implementation.
616      *
617      * @return a reference
618      * @throws NamingException
619      */
620     public Reference getReference() throws NamingException
621     {
622         String factory = getClass().getName();
623 
624         Reference ref = new Reference(getClass().getName(), factory, null);
625 
626         ref.add(new StringRefAddr("defaultMaxConnections",
627                                   String.valueOf(getDefaultMaxConnections())));
628         ref.add(new StringRefAddr("maxExpiryTime",
629                                   String.valueOf(getMaxExpiryTime())));
630         ref.add(new StringRefAddr("connectionWaitTimeout",
631                                   String.valueOf(getConnectionWaitTimeout())));
632         ref.add(new StringRefAddr("logInterval",
633                                   String.valueOf(getLogInterval())));
634         ref.add(new StringRefAddr("dataSourceName", getDataSourceName()));
635         ref.add(new StringRefAddr("description", getDescription()));
636 
637         byte[] serJndiEnv = null;
638         // BinaryRefAddr does not allow null byte[].
639         if (jndiEnvironment != null)
640         {
641             serJndiEnv = SerializationUtils.serialize(jndiEnvironment);
642             ref.add(new BinaryRefAddr("jndiEnvironment", serJndiEnv));
643         }
644 
645         byte[] serPUMC = null;
646         // BinaryRefAddr does not allow null byte[].
647         if (getPerUserMaxConnections() != null)
648         {
649             serPUMC = SerializationUtils.serialize(getPerUserMaxConnections());
650             ref.add(new BinaryRefAddr("perUserMaxConnections", serPUMC));
651         }
652 
653         return ref;
654     }
655 
656     /***
657      * implements ObjectFactory to create an instance of this class
658      *
659      * @param refObj
660      * @param name
661      * @param context
662      * @param env
663      * @return an instance of this class
664      * @throws Exception
665      */
666     public Object getObjectInstance(Object refObj, Name name,
667                                     Context context, Hashtable env)
668         throws Exception
669     {
670         Reference ref = (Reference) refObj;
671 
672         if (ref.getClassName().equals(getClass().getName()))
673         {
674             setDefaultMaxConnections(Integer.parseInt(
675                 (String) ref.get("defaultMaxConnections").getContent()));
676             setMaxExpiryTime(Integer.parseInt(
677                 (String) ref.get("maxExpiryTime").getContent()));
678             setConnectionWaitTimeout(Integer.parseInt(
679                 (String) ref.get("connectionWaitTimeout").getContent()));
680             setLogInterval(Integer.parseInt(
681                 (String) ref.get("logInterval").getContent()));
682             setDataSourceName((String) ref.get("dataSourceName").getContent());
683             setDescription((String) ref.get("description").getContent());
684 
685             RefAddr refAddr = ref.get("jndiEnvironment");
686             if (refAddr != null)
687             {
688                 byte[] serialized = (byte[]) refAddr.getContent();
689                 jndiEnvironment = (Properties)
690                         SerializationUtils.deserialize(serialized);
691             }
692 
693             refAddr = ref.get("perUserMaxConnections");
694             if (refAddr != null)
695             {
696                 byte[] serialized = (byte[]) refAddr.getContent();
697                 setPerUserMaxConnections(
698                     (Properties) SerializationUtils.deserialize(serialized));
699             }
700 
701             return this;
702         }
703         else
704         {
705             // We can't create an instance of the reference
706             return null;
707         }
708     }
709 }