View Javadoc

1   package org.apache.torque;
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.sql.Connection;
20  import java.sql.SQLException;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.configuration.Configuration;
29  import org.apache.commons.configuration.ConfigurationException;
30  import org.apache.commons.configuration.PropertiesConfiguration;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  import org.apache.torque.adapter.DB;
37  import org.apache.torque.adapter.DBFactory;
38  import org.apache.torque.dsfactory.DataSourceFactory;
39  import org.apache.torque.manager.AbstractBaseManager;
40  import org.apache.torque.map.DatabaseMap;
41  import org.apache.torque.map.TableMap;
42  import org.apache.torque.oid.IDBroker;
43  import org.apache.torque.oid.IDGeneratorFactory;
44  import org.apache.torque.util.BasePeer;
45  
46  /***
47   * The core of Torque's implementation.  Both the classic {@link
48   * org.apache.torque.Torque} static wrapper and the {@link
49   * org.apache.torque.avalon.TorqueComponent} <a
50   * href="http://avalon.apache.org/">Avalon</a> implementation leverage
51   * this class.
52   *
53   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
54   * @author <a href="mailto:magnus@handtolvur.is">Magn�s ��r Torfason</a>
55   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
56   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
57   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
58   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
59   * @author <a href="mailto:kschrader@karmalab.org">Kurt Schrader</a>
60   * @version $Id: TorqueInstance.java,v 1.5.2.5 2004/08/23 02:54:16 seade Exp $
61   */
62  public class TorqueInstance
63  {
64      /*** Logging */
65      private static Log log = LogFactory.getLog(TorqueInstance.class);
66  
67      /*** A constant for <code>default</code>. */
68      private static final String DEFAULT_NAME = "default";
69  
70      /*** The db name that is specified as the default in the property file */
71      private String defaultDBName;
72  
73      /*** The global cache of database maps */
74      private Map dbMaps;
75  
76      /*** The cache of DataSourceFactory's */
77      private Map dsFactoryMap;
78  
79      /*** The cache of DB adapter keys */
80      private Map adapterMap;
81  
82      /*** A repository of Manager instances. */
83      private Map managers;
84  
85      /*** Torque-specific configuration. */
86      private Configuration conf;
87  
88      /*** flag to set to true once this class has been initialized */
89      private boolean isInit = false;
90  
91      /***
92       * Store mapbuilder classnames for peers that have been referenced prior
93       * to Torque being initialized.  This can happen if torque om/peer objects
94       * are serialized then unserialized prior to Torque being reinitialized.
95       * This condition exists in a normal catalina restart.
96       */
97      private List mapBuilders = null;
98  
99      /***
100      * Creates a new instance with default configuration.
101      *
102      * @see #resetConfiguration()
103      */
104     public TorqueInstance()
105     {
106         resetConfiguration();
107     }
108 
109     /***
110      * Initializes this instance of Torque.
111      *
112      * @see org.apache.stratum.lifecycle.Initializable
113      * @throws TorqueException Any exceptions caught during processing will be
114      *         rethrown wrapped into a TorqueException.
115      */
116     private synchronized void initialize() throws TorqueException
117     {
118         log.debug("initialize()");
119 
120         if (isInit)
121         {
122             log.debug("Multiple initializations of Torque attempted");
123             return;
124         }
125 
126         if (conf == null)
127         {
128             throw new TorqueException("Torque cannot be initialized without "
129                     + "a valid configuration. Please check the log files "
130                     + "for further details.");
131         }
132 
133         // Now that we have dealt with processing the log4j properties
134         // that may be contained in the configuration we will make the
135         // configuration consist only of the remain torque specific
136         // properties that are contained in the configuration. First
137         // look for properties that are in the "torque" namespace.
138 
139         Configuration subConf = conf.subset("torque");
140 
141         if (!subConf.isEmpty())
142         {
143             setConfiguration(subConf);
144         }
145 
146         dbMaps = new HashMap();
147         initAdapters(conf);
148         initDataSourceFactories(conf);
149 
150         for (Iterator i = mapBuilders.iterator(); i.hasNext();)
151         {
152             //this will add any maps in this builder to the proper database map
153             BasePeer.getMapBuilder((String) i.next());
154         }
155         // any further mapBuilders will be called/built on demand
156         mapBuilders = null;
157 
158         // setup manager mappings
159         initManagerMappings(conf);
160 
161         isInit = true;
162     }
163 
164     /***
165      *
166      * @param conf the Configuration representing the properties file
167      * @throws TorqueException Any exceptions caught during processing will be
168      *         rethrown wrapped into a TorqueException.
169      */
170     private final void initAdapters(Configuration conf)
171             throws TorqueException
172     {
173         log.debug("initAdapters(" + conf + ")");
174         adapterMap = new HashMap();
175         Configuration c = conf.subset("database");
176 
177         if (c != null)
178         {
179             boolean foundAdapters = false;
180 
181             try
182             {
183                 for (Iterator it = c.getKeys(); it.hasNext(); )
184                 {
185                     String key = (String) it.next();
186                     if (key.endsWith("adapter"))
187                     {
188                         String adapter = c.getString(key);
189                         String handle = key.substring(0, key.indexOf('.'));
190                         DB db = DBFactory.create(adapter);
191                         // register the adapter for this name
192                         adapterMap.put(handle, db);
193                         log.debug("Adding " + adapter + " -> " + handle + " as Adapter");
194                         foundAdapters = true;
195                     }
196                 }
197                 if (!foundAdapters)
198                 {
199                     log.warn("Databases defined but no adapter "
200                              + "configurations found!");
201                 }
202             }
203             catch (Exception e)
204             {
205                 log.error("Error reading configuration seeking database "
206                           + "adapters", e);
207                 throw new TorqueException(e);
208             }
209         }
210         else
211         {
212             log.warn("No Database definitions found!");
213         }
214 
215     }
216 
217     /***
218      *
219      * @param conf the Configuration representing the properties file
220      * @throws TorqueException Any exceptions caught during processing will be
221      *         rethrown wrapped into a TorqueException.
222      */
223     private void initDataSourceFactories(Configuration conf)
224             throws TorqueException
225     {
226         log.debug("initDataSourceFactories(" + conf + ")");
227         dsFactoryMap = new HashMap();
228         Configuration c = conf.subset("dsfactory");
229         if (c != null)
230         {
231             boolean foundFactories = false;
232 
233             try
234             {
235                 for (Iterator it = c.getKeys(); it.hasNext();)
236                 {
237                     String key = (String) it.next();
238                     if (key.endsWith("factory"))
239                     {
240                         String classname = c.getString(key);
241                         String handle = key.substring(0, key.indexOf('.'));
242                         log.debug("handle: " + handle
243                                 + " DataSourceFactory: " + classname);
244                         Class dsfClass = Class.forName(classname);
245                         DataSourceFactory dsf =
246                                 (DataSourceFactory) dsfClass.newInstance();
247                         dsf.initialize(c.subset(handle));
248                         dsFactoryMap.put(handle, dsf);
249                         foundFactories = true;
250                     }
251                 }
252                 if (!foundFactories)
253                 {
254                     log.warn("Data Sources configured but no factories found!");
255                 }
256             }
257             catch (Exception e)
258             {
259                 log.error("Error reading adapter configuration", e);
260                 throw new TorqueException(e);
261             }
262         }
263 
264         // As there might be a default database configured
265         // to map "default" onto an existing datasource, we
266         // must check, whether there _is_ really an entry for
267         // the "default" in the dsFactoryMap or not. If it is
268         // not, then add a dummy entry for the "default"
269         //
270         // Without this, you can't actually access the "default"
271         // data-source, even if you have an entry like
272         //
273         // database.default = bookstore
274         //
275         // in your Torque.properties
276         //
277         String defaultDB = getDefaultDB();
278 
279         if (dsFactoryMap.get(DEFAULT_NAME) == null
280                 && !defaultDB.equals(DEFAULT_NAME))
281         {
282             log.debug("Adding a dummy entry for "
283                     + DEFAULT_NAME + ", mapped onto " + defaultDB);
284             dsFactoryMap.put(DEFAULT_NAME, dsFactoryMap.get(defaultDB));
285         }
286     }
287 
288     /***
289      * Initialization of Torque with a properties file.
290      *
291      * @param configFile The absolute path to the configuration file.
292      * @throws TorqueException Any exceptions caught during processing will be
293      *         rethrown wrapped into a TorqueException.
294      */
295     public void init(String configFile)
296             throws TorqueException
297     {
298         log.debug("init(" + configFile + ")");
299         try
300         {
301             Configuration conf = new PropertiesConfiguration(configFile);
302 
303             log.debug("Config Object is " + conf);
304             init(conf);
305         }
306         catch (ConfigurationException e)
307         {
308             throw new TorqueException(e);
309         }
310     }
311 
312     /***
313      * Initialization of Torque with a properties file.
314      *
315      * @param conf The Torque configuration.
316      * @throws TorqueException Any exceptions caught during processing will be
317      *         rethrown wrapped into a TorqueException.
318      */
319     public void init(Configuration conf)
320             throws TorqueException
321     {
322         log.debug("init(" + conf + ")");
323         setConfiguration(conf);
324         initialize();
325     }
326 
327 
328     /***
329      * Creates a mapping between classes and their manager classes.
330      *
331      * The mapping is built according to settings present in
332      * properties file.  The entries should have the
333      * following form:
334      *
335      * <pre>
336      * torque.managed_class.com.mycompany.Myclass.manager= \
337      *          com.mycompany.MyManagerImpl
338      * services.managed_class.com.mycompany.Myotherclass.manager= \
339      *          com.mycompany.MyOtherManagerImpl
340      * </pre>
341      *
342      * <br>
343      *
344      * Generic ServiceBroker provides no Services.
345      *
346      * @param conf the Configuration representing the properties file
347      * @throws TorqueException Any exceptions caught during processing will be
348      *         rethrown wrapped into a TorqueException.
349      */
350     protected void initManagerMappings(Configuration conf)
351             throws TorqueException
352     {
353         int pref = Torque.MANAGER_PREFIX.length();
354         int suff = Torque.MANAGER_SUFFIX.length();
355 
356         for (Iterator it = conf.getKeys(); it.hasNext();)
357         {
358             String key = (String) it.next();
359 
360             if (key.startsWith(Torque.MANAGER_PREFIX)
361                     && key.endsWith(Torque.MANAGER_SUFFIX))
362             {
363                 String managedClassKey = key.substring(pref,
364                         key.length() - suff);
365                 if (!managers.containsKey(managedClassKey))
366                 {
367                     String managerClass = conf.getString(key);
368                     log.info("Added Manager for Class: " + managedClassKey
369                             + " -> " + managerClass);
370                     try
371                     {
372                         initManager(managedClassKey, managerClass);
373                     }
374                     catch (TorqueException e)
375                     {
376                         // the exception thrown here seems to disappear.
377                         // At least when initialized by Turbine, should find
378                         // out why, but for now make sure it is noticed.
379                         log.error("", e);
380                         e.printStackTrace();
381                         throw e;
382                     }
383                 }
384             }
385         }
386     }
387 
388     /***
389      * Initialize a manager
390      *
391      * @param name name of the manager
392      * @param className name of the manager class
393      * @throws TorqueException Any exceptions caught during processing will be
394      *         rethrown wrapped into a TorqueException.
395      */
396     private synchronized void initManager(String name, String className)
397             throws TorqueException
398     {
399         AbstractBaseManager manager = (AbstractBaseManager) managers.get(name);
400 
401         if (manager == null)
402         {
403             if (className != null && className.length() != 0)
404             {
405                 try
406                 {
407                     manager = (AbstractBaseManager)
408                             Class.forName(className).newInstance();
409                     managers.put(name, manager);
410                 }
411                 catch (Exception e)
412                 {
413                     throw new TorqueException("Could not instantiate "
414                             + "manager associated with class: "
415                             + name, e);
416                 }
417             }
418         }
419     }
420 
421     /***
422      * Determine whether Torque has already been initialized.
423      *
424      * @return true if Torque is already initialized
425      */
426     public boolean isInit()
427     {
428         return isInit;
429     }
430 
431     /***
432      * Sets the configuration for Torque and all dependencies.
433      *
434      * @param conf the Configuration
435      */
436     public void setConfiguration(Configuration conf)
437     {
438         log.debug("setConfiguration(" + conf + ")");
439         this.conf = conf;
440     }
441 
442     /***
443      * Get the configuration for this component.
444      *
445      * @return the Configuration
446      */
447     public Configuration getConfiguration()
448     {
449         log.debug("getConfiguration() = " + conf);
450         return conf;
451     }
452 
453     /***
454      * This method returns a Manager for the given name.
455      *
456      * @param name name of the manager
457      * @return a Manager
458      */
459     public AbstractBaseManager getManager(String name)
460     {
461         AbstractBaseManager m = (AbstractBaseManager) managers.get(name);
462         if (m == null)
463         {
464             log.error("No configured manager for key " + name + ".");
465         }
466         return m;
467     }
468 
469     /***
470      * This methods returns either the Manager from the configuration file,
471      * or the default one provided by the generated code.
472      *
473      * @param name name of the manager
474      * @param defaultClassName the class to use if name has not been configured
475      * @return a Manager
476      */
477     public AbstractBaseManager getManager(String name,
478             String defaultClassName)
479     {
480         AbstractBaseManager m = (AbstractBaseManager) managers.get(name);
481         if (m == null)
482         {
483             log.debug("Added late Manager mapping for Class: "
484                     + name + " -> " + defaultClassName);
485 
486             try
487             {
488                 initManager(name, defaultClassName);
489             }
490             catch (TorqueException e)
491             {
492                 log.error(e.getMessage(), e);
493             }
494 
495             // Try again now that the default manager should be in the map
496             m = (AbstractBaseManager) managers.get(name);
497         }
498 
499         return m;
500     }
501 
502     /***
503      * Shuts down the service.
504      *
505      * This method halts the IDBroker's daemon thread in all of
506      * the DatabaseMap's.
507      */
508     public synchronized void shutdown()
509     {
510         if (dbMaps != null)
511         {
512             for (Iterator it = dbMaps.values().iterator(); it.hasNext();)
513             {
514                 DatabaseMap map = (DatabaseMap) it.next();
515                 IDBroker idBroker = map.getIDBroker();
516                 if (idBroker != null)
517                 {
518                     idBroker.stop();
519                 }
520             }
521         }
522         resetConfiguration();
523     }
524 
525     /***
526      * Resets some internal configuration variables to
527      * their defaults.
528      */
529     private void resetConfiguration()
530     {
531         mapBuilders = Collections.synchronizedList(new ArrayList());
532         managers = new HashMap();
533         isInit = false;
534     }
535 
536     /***
537      * Returns the default database map information.
538      *
539      * @return A DatabaseMap.
540      * @throws TorqueException Any exceptions caught during processing will be
541      *         rethrown wrapped into a TorqueException.
542      */
543     public DatabaseMap getDatabaseMap()
544             throws TorqueException
545     {
546         return getDatabaseMap(getDefaultDB());
547     }
548 
549     /***
550      * Returns the database map information. Name relates to the name
551      * of the connection pool to associate with the map.
552      *
553      * @param name The name of the database corresponding to the
554      *        <code>DatabaseMap</code> to retrieve.
555      * @return The named <code>DatabaseMap</code>.
556      * @throws TorqueException Any exceptions caught during processing will be
557      *         rethrown wrapped into a TorqueException.
558      */
559     public DatabaseMap getDatabaseMap(String name)
560             throws TorqueException
561     {
562         if (name == null)
563         {
564             throw new TorqueException ("DatabaseMap name was null!");
565         }
566 
567         if (dbMaps == null)
568         {
569             throw new TorqueException("Torque was not initialized properly.");
570         }
571 
572         synchronized (dbMaps)
573         {
574             DatabaseMap map = (DatabaseMap) dbMaps.get(name);
575             if (map == null)
576             {
577                 // Still not there.  Create and add.
578                 map = initDatabaseMap(name);
579             }
580             return map;
581         }
582     }
583 
584     /***
585      * Creates and initializes the mape for the named database.
586      * Assumes that <code>dbMaps</code> member is sync'd.
587      *
588      * @param name The name of the database to map.
589      * @return The desired map.
590      * @throws TorqueException Any exceptions caught during processing will be
591      *         rethrown wrapped into a TorqueException.
592      */
593     private final DatabaseMap initDatabaseMap(String name)
594             throws TorqueException
595     {
596         DatabaseMap map = new DatabaseMap(name);
597 
598         // Add info about IDBroker's table.
599         setupIdTable(map);
600 
601         // Setup other ID generators for this map.
602         try
603         {
604             String key = getDatabaseProperty(name, "adapter");
605             if (StringUtils.isEmpty(key))
606             {
607                 key = getDatabaseProperty(name, "driver");
608             }
609             DB db = DBFactory.create(key);
610             for (int i = 0; i < IDGeneratorFactory.ID_GENERATOR_METHODS.length;
611                  i++)
612             {
613                 map.addIdGenerator(IDGeneratorFactory.ID_GENERATOR_METHODS[i],
614                         IDGeneratorFactory.create(db));
615             }
616         }
617         catch (java.lang.InstantiationException e)
618         {
619             throw new TorqueException(e);
620         }
621 
622         // Avoid possible ConcurrentModificationException by
623         // constructing a copy of dbMaps.
624         Map newMaps = new HashMap(dbMaps);
625         newMaps.put(name, map);
626         dbMaps = newMaps;
627 
628         return map;
629     }
630 
631     /***
632      * Register a MapBuilder
633      *
634      * @param className the MapBuilder
635      */
636     public void registerMapBuilder(String className)
637     {
638         mapBuilders.add(className);
639     }
640 
641     /***
642      * Returns the specified property of the given database, or the empty
643      * string if no value is set for the property.
644      *
645      * @param db   The name of the database whose property to get.
646      * @param prop The name of the property to get.
647      * @return     The property's value.
648      */
649     private String getDatabaseProperty(String db, String prop)
650     {
651         return conf.getString(new StringBuffer("database.")
652                 .append(db)
653                 .append('.')
654                 .append(prop)
655                 .toString(), "");
656     }
657 
658     /***
659      * Setup IDBroker's table information within given database map.
660      *
661      * This method should be called on all new database map to ensure that
662      * IDBroker functionality is available in all databases used by the
663      * application.
664      *
665      * @param map the DataBaseMap to setup.
666      */
667     private final void setupIdTable(DatabaseMap map)
668     {
669         map.setIdTable("ID_TABLE");
670         TableMap tMap = map.getIdTable();
671         tMap.addPrimaryKey("ID_TABLE_ID", new Integer(0));
672         tMap.addColumn("TABLE_NAME", "");
673         tMap.addColumn("NEXT_ID", new Integer(0));
674         tMap.addColumn("QUANTITY", new Integer(0));
675     }
676 
677     /***
678      * This method returns a Connection from the default pool.
679      *
680      * @return The requested connection.
681      * @throws TorqueException Any exceptions caught during processing will be
682      *         rethrown wrapped into a TorqueException.
683      */
684     public Connection getConnection()
685             throws TorqueException
686     {
687         return getConnection(getDefaultDB());
688     }
689 
690     /***
691      *
692      * @param name The database name.
693      * @return a database connection
694      * @throws TorqueException Any exceptions caught during processing will be
695      *         rethrown wrapped into a TorqueException.
696      */
697     public Connection getConnection(String name)
698             throws TorqueException
699     {
700         Connection con = null;
701         DataSourceFactory dsf = null;
702         try
703         {
704             dsf = (DataSourceFactory) dsFactoryMap.get(name);
705             con = dsf.getDataSource().getConnection();
706         }
707         catch (Exception e)
708         {
709             if (dsf == null && e instanceof NullPointerException)
710             {
711                 throw new NullPointerException(
712                         "There was no DataSourceFactory "
713                         + "configured for the connection " + name);
714             }
715             else
716             {
717                 throw new TorqueException(e);
718             }
719         }
720         return con;
721     }
722 
723     /***
724      * This method returns a Connecton using the given parameters.
725      * You should only use this method if you need user based access to the
726      * database!
727      *
728      * @param name The database name.
729      * @param username The name of the database user.
730      * @param password The password of the database user.
731      * @return A Connection.
732      * @throws TorqueException Any exceptions caught during processing will be
733      *         rethrown wrapped into a TorqueException.
734      */
735     public Connection getConnection(String name, String username,
736             String password)
737             throws TorqueException
738     {
739         Connection con = null;
740         DataSourceFactory dsf = null;
741         try
742         {
743             dsf = (DataSourceFactory) dsFactoryMap.get(name);
744             con = dsf.getDataSource().getConnection(username, password);
745         }
746         catch (Exception e)
747         {
748             if (dsf == null && e instanceof NullPointerException)
749             {
750                 throw new NullPointerException(
751                         "There was no DataSourceFactory "
752                         + "configured for the connection " + name);
753             }
754             else
755             {
756                 throw new TorqueException(e);
757             }
758         }
759         return con;
760     }
761 
762     /***
763      * Returns database adapter for a specific connection pool.
764      *
765      * @param name A pool name.
766      * @return The corresponding database adapter.
767      * @throws TorqueException Any exceptions caught during processing will be
768      *         rethrown wrapped into a TorqueException.
769      */
770     public DB getDB(String name) throws TorqueException
771     {
772         return (DB) adapterMap.get(name);
773     }
774 
775     ///////////////////////////////////////////////////////////////////////////
776 
777     /***
778      * Returns the name of the default database.
779      *
780      * @return name of the default DB
781      */
782     public String getDefaultDB()
783     {
784         if (conf == null)
785         {
786             return DEFAULT_NAME;
787         }
788         else if (defaultDBName == null)
789         {
790             // Determine default database name.
791             defaultDBName =
792                     conf.getString(Torque.DATABASE_DEFAULT, 
793                             DEFAULT_NAME).trim();
794         }
795 
796         return defaultDBName;
797     }
798 
799     /***
800      * Closes a connection.
801      *
802      * @param con A Connection to close.
803      */
804     public void closeConnection(Connection con)
805     {
806         if (con != null)
807         {
808             try
809             {
810                 con.close();
811             }
812             catch (SQLException e)
813             {
814                 log.error("Error occured while closing connection.", e);
815             }
816         }
817     }
818 }