View Javadoc

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