View Javadoc

1   package org.apache.torque;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.commons.configuration.Configuration;
32  import org.apache.commons.configuration.ConfigurationException;
33  import org.apache.commons.configuration.PropertiesConfiguration;
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.torque.adapter.Adapter;
38  import org.apache.torque.adapter.AdapterFactory;
39  import org.apache.torque.adapter.IDMethod;
40  import org.apache.torque.dsfactory.DataSourceFactory;
41  import org.apache.torque.manager.AbstractBaseManager;
42  import org.apache.torque.map.DatabaseMap;
43  import org.apache.torque.oid.IDBroker;
44  import org.apache.torque.oid.IDGeneratorFactory;
45  import org.apache.torque.om.Persistent;
46  import org.apache.torque.util.Transaction;
47  import org.apache.torque.util.TransactionManager;
48  import org.apache.torque.util.TransactionManagerImpl;
49  
50  /**
51   * The core of Torque's implementation.  Both the classic {@link
52   * org.apache.torque.Torque} static wrapper and the {@link
53   * org.apache.torque.avalon.TorqueComponent} <a
54   * href="http://avalon.apache.org/">Avalon</a> implementation leverage
55   * this class.
56   *
57   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
58   * @author <a href="mailto:magnus@handtolvur.is">Magn�s ��r Torfason</a>
59   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
60   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
61   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
62   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
63   * @author <a href="mailto:kschrader@karmalab.org">Kurt Schrader</a>
64   * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
65   * @version $Id: TorqueInstance.java 1375892 2012-08-22 04:11:33Z tfischer $
66   */
67  public class TorqueInstance
68  {
69      /** Logging */
70      private static Log log = LogFactory.getLog(TorqueInstance.class);
71  
72      /** A constant for <code>default</code>. */
73      private static final String DEFAULT_NAME = "default";
74  
75      /** The db name that is specified as the default in the property file */
76      private String defaultDBName = null;
77  
78      /**
79       * The Map which contains all known databases. All iterations over the map
80       * and other operations where the databaase map needs to stay
81       * in a defined state must be synchronized to this map.
82       */
83      private final Map<String, Database> databases
84              = Collections.synchronizedMap(new HashMap<String, Database>());
85  
86      /** A repository of Manager instances. */
87      private Map<String, AbstractBaseManager<?>> managers;
88  
89      /** A repository of idBroker instances. */
90      private final Set<IDBroker> idBrokers = new HashSet<IDBroker>();
91  
92      /** Torque-specific configuration. */
93      private Configuration conf;
94  
95      /** Flag to set to true once this class has been initialized */
96      private boolean isInit = false;
97  
98      /**
99       * A flag which indicates whether the DataSourceFactory in the database
100      * named <code>DEFAULT</code> is a reference to another
101      * DataSourceFactory. This is important to know when closing the
102      * DataSourceFactories on shutdown();
103      */
104     private boolean defaultDsfIsReference = false;
105 
106     /**
107      * Creates a new instance with default configuration.
108      *
109      * @see #resetConfiguration()
110      */
111     public TorqueInstance()
112     {
113         resetConfiguration();
114     }
115 
116     /**
117      * Initializes this instance of Torque.
118      *
119      * @throws TorqueException if Torque is already initialized
120      *         or an error during initialization occurs.
121      */
122     private synchronized void initialize() throws TorqueException
123     {
124         log.debug("initialize()");
125 
126         if (isInit)
127         {
128             throw new TorqueException(
129                     "Multiple initializations of Torque attempted");
130         }
131 
132         if (conf == null || conf.isEmpty())
133         {
134             throw new TorqueException("Torque cannot be initialized without "
135                     + "a valid configuration. Please check the log files "
136                     + "for further details.");
137         }
138 
139         initTransactionManager(conf);
140         initDefaultDbName(conf);
141         initDataSourceFactories(conf);
142         initSchemata(conf);
143         initAdapters(conf);
144 
145         // As there might be a default database configured
146         // to map "default" onto an existing datasource, we
147         // must check, whether there _is_ really an entry for
148         // the "default" in the dsFactoryMap or not. If it is
149         // not, then add a dummy entry for the "default"
150         //
151         // Without this, you can't actually access the "default"
152         // data-source, even if you have an entry like
153         //
154         // database.default = bookstore
155         //
156         // in your Torque.properties
157         //
158 
159         {
160             Database defaultDatabase = databases.get(defaultDBName);
161             Database databaseInfoForKeyDefault
162                     = getOrCreateDatabase(DEFAULT_NAME);
163             if ((!defaultDBName.equals(DEFAULT_NAME))
164                 && databaseInfoForKeyDefault.getDataSourceFactory() == null)
165             {
166                 log.debug("Adding the DatasourceFactory and DatabaseAdapter "
167                         + "from database "
168                         + defaultDBName
169                         + " onto database " + DEFAULT_NAME);
170                 databaseInfoForKeyDefault.setDataSourceFactory(
171                         defaultDatabase.getDataSourceFactory());
172                 databaseInfoForKeyDefault.setAdapter(
173                         defaultDatabase.getAdapter());
174 
175                 this.defaultDsfIsReference = true;
176             }
177         }
178 
179         // setup manager mappings
180         initManagerMappings(conf);
181         startIdBrokers();
182 
183         isInit = true;
184     }
185 
186 
187     /**
188      * Initializes the transaction manager.
189      *
190      * @param conf the configuration representing the torque section.
191      *        of the properties file.
192      *
193      * @throws TorqueException if the transaction manger configuration
194      *         is invalid.
195      */
196     private void initTransactionManager(Configuration conf)
197             throws TorqueException
198     {
199         log.debug("initTransactionManager(" + conf + ")");
200 
201         String transactionManagerClassName =
202                 conf.getString(Torque.TRANSACTION_MANAGER_KEY);
203         TransactionManager transactionManager;
204         if (StringUtils.isEmpty(transactionManagerClassName))
205         {
206             if (log.isTraceEnabled())
207             {
208                 log.trace("Configuration key " + Torque.TORQUE_KEY + "."
209                     + Torque.TRANSACTION_MANAGER_KEY
210                     + " not set, using default transaction manager "
211                     + TransactionManagerImpl.class.getName());
212             }
213             transactionManager = new TransactionManagerImpl();
214         }
215         else
216         {
217             try
218             {
219                 Class<?> transactionManagerClass
220                         = Class.forName(transactionManagerClassName);
221                 transactionManager = (TransactionManager)
222                         transactionManagerClass.newInstance();
223                 if (log.isTraceEnabled())
224                 {
225                     log.trace("Using transaction manager "
226                             + transactionManager.getClass().getName());
227                 }
228             }
229             catch (Exception e)
230             {
231                 log.error("Error handling transaction manager configuration",
232                         e);
233                 throw new TorqueException(e);
234             }
235         }
236         Transaction.setTransactionManager(transactionManager);
237     }
238 
239 
240     /**
241      * Initializes the name of the default database and
242      * associates the database with the name <code>DEFAULT_NAME</code>
243      * to the default database.
244      *
245      * @param conf the configuration representing the torque section.
246      *        of the properties file.
247      *
248      * @throws TorqueException if the appropriate key is not set.
249      */
250     private void initDefaultDbName(Configuration conf)
251             throws TorqueException
252     {
253         log.debug("initDefaultDbName(" + conf + ")");
254 
255         // Determine default database name.
256         defaultDBName =
257                 conf.getString(
258                         Torque.DATABASE_KEY
259                         + "."
260                         + Torque.DEFAULT_KEY);
261         if (defaultDBName == null)
262         {
263             String error = "Invalid configuration: Key "
264                     + Torque.TORQUE_KEY
265                     + "."
266                     + Torque.DATABASE_KEY
267                     + "."
268                     + Torque.DEFAULT_KEY
269                     + " not set";
270             log.error(error);
271             throw new TorqueException(error);
272         }
273     }
274 
275     /**
276      * Reads the adapter settings from the configuration and
277      * assigns the appropriate database adapters and Id Generators
278      * to the databases.
279      *
280      * @param conf the Configuration representing the torque section of the
281      *        properties file.
282      *
283      * @throws TorqueException Any exceptions caught during processing will be
284      *         rethrown wrapped into a TorqueException.
285      */
286     private void initAdapters(Configuration conf)
287             throws TorqueException
288     {
289         log.debug("initAdapters(" + conf + ")");
290 
291         Configuration c = conf.subset(Torque.DATABASE_KEY);
292         if (c == null || c.isEmpty())
293         {
294             String error = "Invalid configuration : "
295                     + "No keys starting with "
296                     + Torque.TORQUE_KEY
297                     + "."
298                     + Torque.DATABASE_KEY
299                     + " found in configuration";
300             log.error(error);
301             throw new TorqueException(error);
302         }
303 
304         try
305         {
306             for (Iterator<?> it = c.getKeys(); it.hasNext();)
307             {
308                 String key = (String) it.next();
309                 if (key.endsWith(Adapter.ADAPTER_KEY)
310                         || key.endsWith(Adapter.DRIVER_KEY))
311                 {
312                     String adapterKey = c.getString(key);
313                     String handle = key.substring(0, key.indexOf('.'));
314 
315                     Database database = getOrCreateDatabase(handle);
316                     Adapter adapter = null;
317 
318                     if (StringUtils.equals(
319                             Adapter.AUTODETECT_ADAPTER,
320                             adapterKey))
321                     {
322                         Connection con = null;
323                         try
324                         {
325                             con = database.getDataSourceFactory()
326                                     .getDataSource().getConnection();
327                             adapter = AdapterFactory.autoDetectAdapter(con);
328                         }
329                         catch (SQLException e)
330                         {
331                             log.error(
332                                 "Could not get product information from JDBC",
333                                 e);
334                         }
335                         finally
336                         {
337                             closeConnection(con);
338                         }
339                     }
340                     else
341                     {
342                         adapter = AdapterFactory.create(adapterKey);
343                     }
344 
345                     // Not supported, try manually defined adapter class
346                     if (adapter == null)
347                     {
348                         String adapterClassName = c.getString(
349                                 key + "." + adapterKey + ".className", null);
350                         adapter = AdapterFactory.create(
351                                 adapterKey,
352                                 adapterClassName);
353                     }
354 
355                     // register the adapter for this name
356                     database.setAdapter(adapter);
357                     log.debug("Adding " + adapterKey + " -> "
358                             + handle + " as Adapter");
359 
360                     // add Id generators
361                     for (IDMethod idMethod
362                             : IDGeneratorFactory.ID_GENERATOR_METHODS)
363                     {
364                         database.addIdGenerator(
365                                 idMethod,
366                                 IDGeneratorFactory.create(adapter, handle));
367                     }
368                 }
369             }
370         }
371         catch (InstantiationException e)
372         {
373             log.error("Error creating a database adapter instance", e);
374             throw new TorqueException(e);
375         }
376 
377         // check that at least the default database has got an adapter.
378         Database defaultDatabase
379                 = databases.get(getDefaultDB());
380         if (defaultDatabase == null
381             || defaultDatabase.getAdapter() == null)
382         {
383             String error = "Invalid configuration : "
384                     + "No adapter definition found for default DB "
385                     + "An adapter must be defined under "
386                     + Torque.TORQUE_KEY
387                     + "."
388                     + Torque.DATABASE_KEY
389                     + "."
390                     + getDefaultDB()
391                     + "."
392                     + Adapter.ADAPTER_KEY;
393             log.error(error);
394             throw new TorqueException(error);
395         }
396     }
397 
398     /**
399      * Reads the settings for the DataSourceFactories from the
400      * configuration and creates and/or configures the DataSourceFactories
401      * and the database objects.
402      * If no DataSorceFactory is assigned to the database with the name
403      * <code>DEFAULT_NAME</code>, a reference to the DataSourceFactory
404      * of the default database is made from the database with the name
405      * <code>DEFAULT_NAME</code>.
406      *
407      * @param conf the Configuration representing the properties file.
408      *
409      * @throws TorqueException Any exceptions caught during processing will be
410      *         rethrown wrapped into a TorqueException.
411      */
412     private void initDataSourceFactories(Configuration conf)
413             throws TorqueException
414     {
415         log.debug("initDataSourceFactories(" + conf + ")");
416 
417         Configuration c = conf.subset(DataSourceFactory.DSFACTORY_KEY);
418         if (c == null || c.isEmpty())
419         {
420             String error = "Invalid configuration: "
421                     + "No keys starting with "
422                     + Torque.TORQUE_KEY
423                     + "."
424                     + DataSourceFactory.DSFACTORY_KEY
425                     + " found in configuration";
426             log.error(error);
427             throw new TorqueException(error);
428         }
429 
430         // read dsfactory config (may contain schema)
431         try
432         {
433             for (Iterator<?> it = c.getKeys(); it.hasNext();)
434             {
435                 String key = (String) it.next();
436                 if (key.endsWith(DataSourceFactory.FACTORY_KEY))
437                 {
438                     String classname = c.getString(key);
439                     String handle = key.substring(0, key.indexOf('.'));
440                     log.debug("handle: " + handle
441                             + " DataSourceFactory: " + classname);
442                     Class<?> dsfClass = Class.forName(classname);
443                     DataSourceFactory dsf =
444                             (DataSourceFactory) dsfClass.newInstance();
445                     Configuration subConf = c.subset(handle);
446                     dsf.initialize(subConf);
447 
448                     Database database = getOrCreateDatabase(handle);
449                     database.setDataSourceFactory(dsf);
450 
451                     // deprecated method of schema configuration
452                     // TODO: remove in Torque 4.1
453                     String schema = subConf.getString(Torque.SCHEMA_KEY, null);
454                     if (!StringUtils.isEmpty(schema))
455                     {
456                         log.warn("Defining the schema in the dsfactory "
457                                 + "is deprecated, please configure it "
458                                 + "via the config key "
459                                 + "torque.database.${databasename}.schema");
460                     }
461                     database.setSchema(schema);
462                 }
463             }
464         }
465         catch (RuntimeException e)
466         {
467             log.error("Error reading DataSourceFactory configuration", e);
468             throw new TorqueRuntimeException(e);
469         }
470         catch (Exception e)
471         {
472             log.error("Error reading DataSourceFactory configuration", e);
473             throw new TorqueException(e);
474         }
475 
476         Database defaultDatabase
477                 = databases.get(defaultDBName);
478         if (defaultDatabase == null
479             || defaultDatabase.getDataSourceFactory() == null)
480         {
481             String error = "Invalid configuration : "
482                     + "No DataSourceFactory definition for default DB found. "
483                     + "A DataSourceFactory must be defined under the key"
484                     + Torque.TORQUE_KEY
485                     + "."
486                     + DataSourceFactory.DSFACTORY_KEY
487                     + "."
488                     + defaultDBName
489                     + "."
490                     + DataSourceFactory.FACTORY_KEY;
491             log.error(error);
492             throw new TorqueException(error);
493         }
494     }
495 
496     /**
497      * Reads the schema configuration from the database definitions in
498      * the configuration and assigns the defined schemata to the databases.
499      *
500      * @param conf the Configuration representing the properties file.
501      *
502      * @throws TorqueException Any exceptions caught during processing will be
503      *         rethrown wrapped into a TorqueException.
504      */
505     private void initSchemata(Configuration conf)
506             throws TorqueException
507     {
508         log.debug("initSchemata(" + conf + ")");
509 
510         // read schema configuration from database setting
511         Configuration c = conf.subset(Torque.DATABASE_KEY);
512         if (c == null || c.isEmpty())
513         {
514             String error = "Invalid configuration: "
515                     + "No keys starting with "
516                     + Torque.TORQUE_KEY
517                     + "."
518                     + Torque.DATABASE_KEY
519                     + " found in configuration";
520             log.error(error);
521             throw new TorqueException(error);
522         }
523         try
524         {
525             for (Iterator<?> it = c.getKeys(); it.hasNext();)
526             {
527                 String key = (String) it.next();
528                 int indexOfDot = key.indexOf('.');
529                 if (indexOfDot == -1)
530                 {
531                     continue;
532                 }
533                 String handle = key.substring(0, indexOfDot);
534 
535                 log.debug("database handle: " + handle);
536                 Configuration subConf = c.subset(handle);
537 
538                 Database database = getOrCreateDatabase(handle);
539 
540                 String schema = subConf.getString(Torque.SCHEMA_KEY, null);
541                 // check database schema because schema may have already been
542                 // set via the dsfactory
543                 if (StringUtils.isEmpty(schema))
544                 {
545                     schema = database.getSchema();
546                 }
547                 if (StringUtils.isEmpty(schema))
548                 {
549                     schema = conf.getString(
550                             Torque.DEFAULT_SCHEMA_KEY,
551                             null);
552                 }
553                 database.setSchema(schema);
554             }
555         }
556         catch (RuntimeException e)
557         {
558             log.error("Error reading DataSourceFactory configuration", e);
559             throw new TorqueRuntimeException(e);
560         }
561         catch (Exception e)
562         {
563             log.error("Error reading DataSourceFactory configuration", e);
564             throw new TorqueException(e);
565         }
566 
567     }
568 
569     /**
570      * Initialization of Torque with a path to a properties file.
571      *
572      * @param configFile The absolute path to the configuration file.
573      *
574      * @throws TorqueException Any exceptions caught during processing will be
575      *         rethrown wrapped into a TorqueException.
576      */
577     public void init(String configFile)
578             throws TorqueException
579     {
580         log.debug("init(" + configFile + ")");
581         try
582         {
583             Configuration configuration
584                     = new PropertiesConfiguration(configFile);
585 
586             log.debug("Config Object is " + configuration);
587             init(configuration);
588         }
589         catch (ConfigurationException e)
590         {
591             throw new TorqueException(e);
592         }
593     }
594 
595     /**
596      * Initialization of Torque with a Configuration object.
597      *
598      * @param conf The Torque configuration.
599      *
600      * @throws TorqueException Any exceptions caught during processing will be
601      *         rethrown wrapped into a TorqueException.
602      */
603     public synchronized void init(Configuration conf)
604             throws TorqueException
605     {
606         log.debug("init(" + conf + ")");
607         setConfiguration(conf);
608         initialize();
609     }
610 
611 
612     /**
613      * Creates a mapping between classes and their manager classes.
614      *
615      * The mapping is built according to settings present in
616      * properties file.  The entries should have the
617      * following form:
618      *
619      * <pre>
620      * torque.managed_class.com.mycompany.Myclass.manager= \
621      *          com.mycompany.MyManagerImpl
622      * services.managed_class.com.mycompany.Myotherclass.manager= \
623      *          com.mycompany.MyOtherManagerImpl
624      * </pre>
625      *
626      * <br>
627      *
628      * Generic ServiceBroker provides no Services.
629      *
630      * @param conf the Configuration representing the properties file
631      * @throws TorqueException Any exceptions caught during processing will be
632      *         rethrown wrapped into a TorqueException.
633      */
634     protected synchronized void initManagerMappings(Configuration conf)
635             throws TorqueException
636     {
637         int pref = Torque.MANAGER_PREFIX.length();
638         int suff = Torque.MANAGER_SUFFIX.length();
639 
640         for (Iterator<?> it = conf.getKeys(); it.hasNext();)
641         {
642             String key = (String) it.next();
643 
644             if (key.startsWith(Torque.MANAGER_PREFIX)
645                     && key.endsWith(Torque.MANAGER_SUFFIX))
646             {
647                 String managedClassKey = key.substring(pref,
648                         key.length() - suff);
649                 if (!managers.containsKey(managedClassKey))
650                 {
651                     String managerClass = conf.getString(key);
652                     log.info("Added Manager for Class: " + managedClassKey
653                             + " -> " + managerClass);
654                     try
655                     {
656                         initManager(managedClassKey, managerClass);
657                     }
658                     catch (TorqueException e)
659                     {
660                         // the exception thrown here seems to disappear.
661                         // At least when initialized by Turbine, should find
662                         // out why, but for now make sure it is noticed.
663                         log.error("", e);
664                         e.printStackTrace();
665                         throw e;
666                     }
667                 }
668             }
669         }
670     }
671 
672     /**
673      * Initialize a manager
674      *
675      * @param name name of the manager
676      * @param className name of the manager class
677      * @throws TorqueException Any exceptions caught during processing will be
678      *         rethrown wrapped into a TorqueException.
679      */
680     private synchronized void initManager(String name, String className)
681             throws TorqueException
682     {
683         AbstractBaseManager<?> manager = managers.get(name);
684 
685         if (manager == null)
686         {
687             if (className != null && className.length() != 0)
688             {
689                 try
690                 {
691                     manager = (AbstractBaseManager<?>)
692                             Class.forName(className).newInstance();
693                     managers.put(name, manager);
694                 }
695                 catch (Exception e)
696                 {
697                     throw new TorqueException("Could not instantiate "
698                             + "manager associated with class: "
699                             + name, e);
700                 }
701             }
702         }
703     }
704 
705     /**
706      * Starts all registered IdBrokers.
707      */
708     private void startIdBrokers()
709     {
710         for (IDBroker idBroker : idBrokers)
711         {
712             idBroker.start();
713         }
714     }
715 
716     /**
717      * Determine whether Torque has already been initialized.
718      *
719      * @return true if Torque is already initialized
720      */
721     public boolean isInit()
722     {
723         return isInit;
724     }
725 
726     /**
727      * Sets the configuration for Torque and all dependencies.
728      * The prefix <code>TORQUE_KEY</code> will be removed from the
729      * configuration keys for the provided configuration.
730      *
731      * @param conf the Configuration.
732      *
733      * @throws TorqueException if the configuration does not contain
734      *         any keys starting with <code>Torque.TORQUE_KEY</code>.
735      */
736     public void setConfiguration(Configuration conf)
737             throws TorqueException
738     {
739         log.debug("setConfiguration(" + conf + ")");
740 
741         Configuration subConf = conf.subset(Torque.TORQUE_KEY);
742         if (subConf == null || subConf.isEmpty())
743         {
744             String error = ("Invalid configuration. No keys starting with "
745                     + Torque.TORQUE_KEY
746                     + " found in configuration");
747             log.error(error);
748             throw new TorqueException(error);
749         }
750         this.conf = subConf;
751     }
752 
753     /**
754      * Get the configuration for this component.
755      *
756      * @return the Configuration
757      */
758     public Configuration getConfiguration()
759     {
760         log.debug("getConfiguration() = " + conf);
761         return conf;
762     }
763 
764     /**
765      * This method returns a Manager for the given name.
766      *
767      * @param name name of the manager
768      * @return a Manager
769      */
770     public <T extends AbstractBaseManager<? extends Persistent>> T getManager(String name)
771     {
772         @SuppressWarnings("unchecked")
773         T m = (T) managers.get(name);
774         if (m == null)
775         {
776             log.error("No configured manager for key " + name + ".");
777         }
778         return m;
779     }
780 
781     /**
782      * This methods returns either the Manager from the configuration file,
783      * or the default one provided by the generated code.
784      *
785      * @param name name of the manager.
786      * @param defaultClassName the class to use if name has not been configured.
787      *
788      * @return a Manager
789      */
790     @SuppressWarnings("unchecked")
791     public <T extends AbstractBaseManager<? extends Persistent>> T getManager(String name,
792             String defaultClassName)
793     {
794         T m = (T) managers.get(name);
795         if (m == null)
796         {
797             log.debug("Added late Manager mapping for Class: "
798                     + name + " -> " + defaultClassName);
799 
800             try
801             {
802                 initManager(name, defaultClassName);
803             }
804             catch (TorqueException e)
805             {
806                 log.error(e.getMessage(), e);
807             }
808 
809             // Try again now that the default manager should be in the map
810             m = (T) managers.get(name);
811         }
812 
813         return m;
814     }
815 
816     /**
817      * Shuts down Torque.
818      *
819      * This method halts the IDBroker's daemon thread in all of
820      * the DatabaseMap's. It also closes all SharedPoolDataSourceFactories
821      * and PerUserPoolDataSourceFactories initialized by Torque.
822      *
823      * @throws TorqueException if a DataSourceFactory could not be closed
824      *            cleanly. Only the first exception is rethrown, any following
825      *            exceptions are logged but ignored.
826      */
827     public synchronized void shutdown()
828         throws TorqueException
829     {
830         for (IDBroker idBroker : idBrokers)
831         {
832             idBroker.stop();
833             // do not remove idbrokers because they will not be
834             // re-registered on a new startup.
835         }
836 
837         // shut down the cache managers
838         for (Iterator<Map.Entry<String, AbstractBaseManager<?>>> it
839                 = managers.entrySet().iterator(); it.hasNext();)
840         {
841             Map.Entry<String, AbstractBaseManager<?>> mentry
842                     = it.next();
843 
844             AbstractBaseManager<?> manager = mentry.getValue();
845             manager.dispose();
846             it.remove();
847         }
848 
849         // shut down the data source factories
850         TorqueException exception = null;
851         synchronized (databases)
852         {
853             for (Map.Entry<String, Database> databaseMapEntry
854                     : databases.entrySet())
855             {
856                 Object databaseKey = databaseMapEntry.getKey();
857                 Database database = databaseMapEntry.getValue();
858                 if (DEFAULT_NAME.equals(databaseKey) && defaultDsfIsReference)
859                 {
860                     // the DataSourceFactory of the database with the name
861                     // DEFAULT_NAME is just a reference to another entry.
862                     // Do not close because this leads to closing
863                     // the same DataSourceFactory twice.
864                     database.setDataSourceFactory(null);
865                     continue;
866                 }
867 
868                 try
869                 {
870                     DataSourceFactory dataSourceFactory
871                             = database.getDataSourceFactory();
872                     if (dataSourceFactory != null)
873                     {
874                         dataSourceFactory.close();
875                         database.setDataSourceFactory(null);
876                     }
877                 }
878                 catch (TorqueException e)
879                 {
880                     log.error("Error while closing the DataSourceFactory "
881                             + databaseKey,
882                             e);
883                     if (exception == null)
884                     {
885                         exception = e;
886                     }
887                 }
888             }
889         }
890         if (exception != null)
891         {
892             throw exception;
893         }
894         resetConfiguration();
895     }
896 
897     /**
898      * Resets some internal configuration variables to their defaults.
899      */
900     private void resetConfiguration()
901     {
902         managers = new HashMap<String, AbstractBaseManager<?>>();
903         isInit = false;
904     }
905 
906     /**
907      * Returns the database map information for the default db.
908      *
909      * @return the requested DatabaseMap, not null.
910      *
911      * @throws TorqueException if Torque is not initialized.
912      */
913     public DatabaseMap getDatabaseMap()
914             throws TorqueException
915     {
916         String name = getDefaultDB();
917         if (name == null)
918         {
919             throw new TorqueException("Torque is not initialized");
920         }
921         return getDatabaseMap(getDefaultDB());
922     }
923 
924     /**
925      * Returns the database map information for the given database name.
926      *
927      * @param name The name of the database corresponding to the
928      *        <code>DatabaseMap</code> to retrieve, or null
929      *        for the default database.
930      *
931      * @return The named <code>DatabaseMap</code>, not null.
932      *
933      * @throws TorqueException if Torque is not initialized and name is null.
934      */
935     public DatabaseMap getDatabaseMap(String name)
936             throws TorqueException
937     {
938         if (name == null)
939         {
940             if (!Torque.isInit())
941             {
942                 throw new TorqueException("Torque is not initialized");
943             }
944             name = getDefaultDB();
945         }
946         Database database = getOrCreateDatabase(name);
947         return database.getDatabaseMap();
948     }
949 
950     /**
951      * Registers an id broker. If Torque is already initialized,
952      * the id broker is started. If Torque is not initialized,
953      * the id broker will be started on initialization.
954      *
955      * @param idBroker the id broker to register, not null.
956      *
957      * @throws NullPointerException if idBroker is null.
958      */
959     public void registerIDBroker(IDBroker idBroker)
960     {
961         idBrokers.add(idBroker);
962         if (isInit())
963         {
964             idBroker.start();
965         }
966     }
967 
968     /**
969      * This method returns a Connection from the default pool.
970      *
971      * @return The requested connection, never null.
972      *
973      * @throws TorqueException Any exceptions caught during processing will be
974      *         rethrown wrapped into a TorqueException.
975      */
976     public Connection getConnection()
977             throws TorqueException
978     {
979         return getConnection(getDefaultDB());
980     }
981 
982     /**
983      * Returns a database connection to the database with the key
984      * <code>name</code>.
985      *
986      * @param name The database name.
987      *
988      * @return a database connection to the named database, never null.
989      *
990      * @throws TorqueException if Torque is not initialized,
991      *         if no DataSourceFactory is configured for the
992      *         named database, the connection information is wrong, or the
993      *         connection cannot be returned for any other reason.
994      */
995     public Connection getConnection(String name)
996             throws TorqueException
997     {
998         if (!isInit())
999         {
1000             throw new TorqueException("Torque is not initialized");
1001         }
1002         try
1003         {
1004             return getDatabase(name)
1005                     .getDataSourceFactory()
1006                     .getDataSource()
1007                     .getConnection();
1008         }
1009         catch (SQLException se)
1010         {
1011             throw new TorqueException(se);
1012         }
1013     }
1014 
1015     /**
1016      * Returns the DataSourceFactory for the database with the name
1017      * <code>name</code>.
1018      *
1019      * @param name The name of the database to get the DSF for.
1020      *
1021      * @return A DataSourceFactory object, never null.
1022      *
1023      * @throws TorqueException if Torque is not initialized, or
1024      *         no DatasourceFactory is configured for the given name.
1025      */
1026     public DataSourceFactory getDataSourceFactory(String name)
1027             throws TorqueException
1028     {
1029         if (!isInit())
1030         {
1031             throw new TorqueException("Torque is not initialized");
1032         }
1033 
1034         Database database = getDatabase(name);
1035 
1036         DataSourceFactory dsf = null;
1037         if (database != null)
1038         {
1039             dsf = database.getDataSourceFactory();
1040         }
1041 
1042         if (dsf == null)
1043         {
1044             throw new TorqueException(
1045                     "There was no DataSourceFactory "
1046                     + "configured for the connection " + name);
1047         }
1048 
1049         return dsf;
1050     }
1051 
1052     /**
1053      * This method returns a Connection using the given parameters.
1054      * You should only use this method if you need user based access to the
1055      * database!
1056      *
1057      * @param name The database name.
1058      * @param username The name of the database user.
1059      * @param password The password of the database user.
1060      *
1061      * @return A Connection to the named database.
1062      *
1063      * @throws TorqueException Any exceptions caught during processing will be
1064      *         rethrown wrapped into a TorqueException.
1065      */
1066     public Connection getConnection(String name, String username,
1067             String password)
1068             throws TorqueException
1069     {
1070         if (!isInit())
1071         {
1072             throw new TorqueException("Torque is not initialized");
1073         }
1074         try
1075         {
1076             return getDataSourceFactory(name)
1077                     .getDataSource().getConnection(username, password);
1078         }
1079         catch (SQLException se)
1080         {
1081             throw new TorqueException(se);
1082         }
1083     }
1084 
1085     /**
1086      * Returns the database adapter for a specific database.
1087      *
1088      * @param name the database name, or null for the default db.
1089      *
1090      * @return The corresponding database adapter, or null if no database
1091      *         adapter is defined for the given database.
1092      *
1093      * @throws TorqueException if Torque is not initialized.
1094      *
1095      * @deprecated use getAdapter(String) instead.
1096      *             This method will be removed in a future version of Torque.
1097      */
1098     @Deprecated
1099     public Adapter getDB(String name) throws TorqueException
1100     {
1101         if (!isInit())
1102         {
1103             throw new TorqueException("Torque is not initialized");
1104         }
1105         Database database = getDatabase(name);
1106         if (database == null)
1107         {
1108             return null;
1109         }
1110         return database.getAdapter();
1111     }
1112 
1113     /**
1114      * Returns the database adapter for a specific database.
1115      *
1116      * @param name the database name, or null for the default db.
1117      *
1118      * @return The corresponding database adapter, or null if no database
1119      *         adapter is defined for the given database.
1120      *
1121      * @throws TorqueException if Torque is not initialized.
1122      */
1123     public Adapter getAdapter(String name) throws TorqueException
1124     {
1125         if (!isInit())
1126         {
1127             throw new TorqueException("Torque is not initialized");
1128         }
1129         Database database = getDatabase(name);
1130         if (database == null)
1131         {
1132             return null;
1133         }
1134         return database.getAdapter();
1135     }
1136 
1137     ///////////////////////////////////////////////////////////////////////////
1138 
1139     /**
1140      * Returns the name of the default database.
1141      *
1142      * @return name of the default DB, or null if Torque is not initialized yet
1143      */
1144     public String getDefaultDB()
1145     {
1146         return defaultDBName;
1147     }
1148 
1149     /**
1150      * Closes a connection.
1151      *
1152      * @param con A Connection to close.
1153      */
1154     public void closeConnection(Connection con)
1155     {
1156         if (con != null)
1157         {
1158             try
1159             {
1160                 con.close();
1161             }
1162             catch (SQLException e)
1163             {
1164                 log.error("Error occured while closing connection.", e);
1165             }
1166         }
1167     }
1168 
1169     /**
1170      * Sets the current schema for a database connection
1171      *
1172      * @param name The database name, not null.
1173      * @param schema The current schema name.
1174      *
1175      * @throws NullPointerException if databaseName is null.
1176      */
1177     public void setSchema(String name, String schema)
1178     {
1179         getOrCreateDatabase(name).setSchema(schema);
1180     }
1181 
1182     /**
1183      * This method returns the current schema for a database connection
1184      *
1185      * @param name The database name.
1186      *
1187      * @return The current schema name. Null means, no schema has been set
1188      *         or no database with the given name exists.
1189      *
1190      * @throws TorqueException if Torque is not yet initialized.
1191      */
1192     public String getSchema(String name)
1193             throws TorqueException
1194     {
1195         if (!isInit())
1196         {
1197             throw new TorqueException("Torque is not initialized");
1198         }
1199         Database database = getDatabase(name);
1200         if (database == null)
1201         {
1202             return null;
1203         }
1204         return database.getSchema();
1205     }
1206 
1207     /**
1208      * Returns the database for the key <code>databaseName</code>.
1209      *
1210      * @param databaseName the key to get the database for,
1211      *        or null for the default database.
1212      *
1213      * @return the database for the specified key, or null if the database
1214      *         does not exist.
1215      *
1216      * @throws TorqueException if Torque is not yet initialized.
1217      */
1218     public Database getDatabase(String databaseName) throws TorqueException
1219     {
1220         if (!isInit())
1221         {
1222             throw new TorqueException("Torque is not initialized.");
1223         }
1224         if (databaseName == null)
1225         {
1226             databaseName = getDefaultDB();
1227         }
1228         return databases.get(databaseName);
1229     }
1230 
1231     /**
1232      * Returns a Map containing all Databases registered to Torque.
1233      * The key of the Map is the name of the database, and the value is the
1234      * database instance. <br/>
1235      * Note that in the very special case where a new database which
1236      * is not configured in Torque's configuration gets known to Torque
1237      * at a later time, the returned map may change, and there is no way to
1238      * protect you against this.
1239      *
1240      * @return a Map containing all Databases known to Torque, never null.
1241      *
1242      * @throws TorqueException if Torque is not yet initialized.
1243      */
1244     public Map<String, Database> getDatabases() throws TorqueException
1245     {
1246         if (!isInit())
1247         {
1248             throw new TorqueException("Torque is not initialized.");
1249         }
1250         return Collections.unmodifiableMap(databases);
1251     }
1252 
1253     /**
1254      * Returns the database for the key <code>databaseName</code>.
1255      * If no database is associated to the specified key,
1256      * a new database is created, mapped to the specified key, and returned.
1257      *
1258      * @param databaseName the key to get the database for, not null.
1259      *
1260      * @return the database associated with specified key, or the newly created
1261      *         database, never null.
1262      *
1263      * @throws NullPointerException if databaseName is null.
1264      */
1265     public Database getOrCreateDatabase(String databaseName)
1266     {
1267         if (databaseName == null)
1268         {
1269             throw new NullPointerException("databaseName is null");
1270         }
1271         synchronized (databases)
1272         {
1273             Database result = databases.get(databaseName);
1274             if (result == null)
1275             {
1276                 result = new Database(databaseName);
1277                 databases.put(databaseName, result);
1278             }
1279             return result;
1280         }
1281     }
1282 }