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.8 2004/08/23 02:54:31 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 = (Configuration)
302                     new PropertiesConfiguration(configFile);
303 
304             log.debug("Config Object is " + conf);
305             init(conf);
306         }
307         catch (ConfigurationException e)
308         {
309             throw new TorqueException(e);
310         }
311     }
312 
313     /***
314      * Initialization of Torque with a properties file.
315      *
316      * @param conf The Torque configuration.
317      * @throws TorqueException Any exceptions caught during processing will be
318      *         rethrown wrapped into a TorqueException.
319      */
320     public void init(Configuration conf)
321             throws TorqueException
322     {
323         log.debug("init(" + conf + ")");
324         setConfiguration(conf);
325         initialize();
326     }
327 
328 
329     /***
330      * Creates a mapping between classes and their manager classes.
331      *
332      * The mapping is built according to settings present in
333      * properties file.  The entries should have the
334      * following form:
335      *
336      * <pre>
337      * torque.managed_class.com.mycompany.Myclass.manager= \
338      *          com.mycompany.MyManagerImpl
339      * services.managed_class.com.mycompany.Myotherclass.manager= \
340      *          com.mycompany.MyOtherManagerImpl
341      * </pre>
342      *
343      * <br>
344      *
345      * Generic ServiceBroker provides no Services.
346      *
347      * @param conf the Configuration representing the properties file
348      * @throws TorqueException Any exceptions caught during processing will be
349      *         rethrown wrapped into a TorqueException.
350      */
351     protected void initManagerMappings(Configuration conf)
352             throws TorqueException
353     {
354         int pref = Torque.MANAGER_PREFIX.length();
355         int suff = Torque.MANAGER_SUFFIX.length();
356 
357         for (Iterator it = conf.getKeys(); it.hasNext();)
358         {
359             String key = (String) it.next();
360 
361             if (key.startsWith(Torque.MANAGER_PREFIX)
362                     && key.endsWith(Torque.MANAGER_SUFFIX))
363             {
364                 String managedClassKey = key.substring(pref,
365                         key.length() - suff);
366                 if (!managers.containsKey(managedClassKey))
367                 {
368                     String managerClass = conf.getString(key);
369                     log.info("Added Manager for Class: " + managedClassKey
370                             + " -> " + managerClass);
371                     try
372                     {
373                         initManager(managedClassKey, managerClass);
374                     }
375                     catch (TorqueException e)
376                     {
377                         // the exception thrown here seems to disappear.
378                         // At least when initialized by Turbine, should find
379                         // out why, but for now make sure it is noticed.
380                         log.error("", e);
381                         e.printStackTrace();
382                         throw e;
383                     }
384                 }
385             }
386         }
387     }
388 
389     /***
390      * Initialize a manager
391      *
392      * @param name name of the manager
393      * @param className name of the manager class
394      * @throws TorqueException Any exceptions caught during processing will be
395      *         rethrown wrapped into a TorqueException.
396      */
397     private synchronized void initManager(String name, String className)
398             throws TorqueException
399     {
400         AbstractBaseManager manager = (AbstractBaseManager) managers.get(name);
401 
402         if (manager == null)
403         {
404             if (className != null && className.length() != 0)
405             {
406                 try
407                 {
408                     manager = (AbstractBaseManager)
409                             Class.forName(className).newInstance();
410                     managers.put(name, manager);
411                 }
412                 catch (Exception e)
413                 {
414                     throw new TorqueException("Could not instantiate "
415                             + "manager associated with class: "
416                             + name, e);
417                 }
418             }
419         }
420     }
421 
422     /***
423      * Determine whether Torque has already been initialized.
424      *
425      * @return true if Torque is already initialized
426      */
427     public boolean isInit()
428     {
429         return isInit;
430     }
431 
432     /***
433      * Sets the configuration for Torque and all dependencies.
434      *
435      * @param conf the Configuration
436      */
437     public void setConfiguration(Configuration conf)
438     {
439         log.debug("setConfiguration(" + conf + ")");
440         this.conf = conf;
441     }
442 
443     /***
444      * Get the configuration for this component.
445      *
446      * @return the Configuration
447      */
448     public Configuration getConfiguration()
449     {
450         log.debug("getConfiguration() = " + conf);
451         return conf;
452     }
453 
454     /***
455      * This method returns a Manager for the given name.
456      *
457      * @param name name of the manager
458      * @return a Manager
459      */
460     public AbstractBaseManager getManager(String name)
461     {
462         AbstractBaseManager m = (AbstractBaseManager) managers.get(name);
463         if (m == null)
464         {
465             log.error("No configured manager for key " + name + ".");
466         }
467         return m;
468     }
469 
470     /***
471      * This methods returns either the Manager from the configuration file,
472      * or the default one provided by the generated code.
473      *
474      * @param name name of the manager
475      * @param defaultClassName the class to use if name has not been configured
476      * @return a Manager
477      */
478     public AbstractBaseManager getManager(String name,
479             String defaultClassName)
480     {
481         AbstractBaseManager m = (AbstractBaseManager) managers.get(name);
482         if (m == null)
483         {
484             log.debug("Added late Manager mapping for Class: "
485                     + name + " -> " + defaultClassName);
486 
487             try
488             {
489                 initManager(name, defaultClassName);
490             }
491             catch (TorqueException e)
492             {
493                 log.error(e.getMessage(), e);
494             }
495 
496             // Try again now that the default manager should be in the map
497             m = (AbstractBaseManager) managers.get(name);
498         }
499 
500         return m;
501     }
502 
503     /***
504      * Shuts down the service.
505      *
506      * This method halts the IDBroker's daemon thread in all of
507      * the DatabaseMap's.
508      */
509     public synchronized void shutdown()
510     {
511         if (dbMaps != null)
512         {
513             for (Iterator it = dbMaps.values().iterator(); it.hasNext();)
514             {
515                 DatabaseMap map = (DatabaseMap) it.next();
516                 IDBroker idBroker = map.getIDBroker();
517                 if (idBroker != null)
518                 {
519                     idBroker.stop();
520                 }
521             }
522         }
523         resetConfiguration();
524     }
525 
526     /***
527      * Resets some internal configuration variables to
528      * their defaults.
529      */
530     private void resetConfiguration()
531     {
532         mapBuilders = Collections.synchronizedList(new ArrayList());
533         managers = new HashMap();
534         isInit = false;
535     }
536 
537     /***
538      * Returns the default database map information.
539      *
540      * @return A DatabaseMap.
541      * @throws TorqueException Any exceptions caught during processing will be
542      *         rethrown wrapped into a TorqueException.
543      */
544     public DatabaseMap getDatabaseMap()
545             throws TorqueException
546     {
547         return getDatabaseMap(getDefaultDB());
548     }
549 
550     /***
551      * Returns the database map information. Name relates to the name
552      * of the connection pool to associate with the map.
553      *
554      * @param name The name of the database corresponding to the
555      *        <code>DatabaseMap</code> to retrieve.
556      * @return The named <code>DatabaseMap</code>.
557      * @throws TorqueException Any exceptions caught during processing will be
558      *         rethrown wrapped into a TorqueException.
559      */
560     public DatabaseMap getDatabaseMap(String name)
561             throws TorqueException
562     {
563         if (name == null)
564         {
565             throw new TorqueException ("DatabaseMap name was null!");
566         }
567 
568         if (dbMaps == null)
569         {
570             throw new TorqueException("Torque was not initialized properly.");
571         }
572 
573         synchronized (dbMaps)
574         {
575             DatabaseMap map = (DatabaseMap) dbMaps.get(name);
576             if (map == null)
577             {
578                 // Still not there.  Create and add.
579                 map = initDatabaseMap(name);
580             }
581             return map;
582         }
583     }
584 
585     /***
586      * Creates and initializes the mape for the named database.
587      * Assumes that <code>dbMaps</code> member is sync'd.
588      *
589      * @param name The name of the database to map.
590      * @return The desired map.
591      * @throws TorqueException Any exceptions caught during processing will be
592      *         rethrown wrapped into a TorqueException.
593      */
594     private final DatabaseMap initDatabaseMap(String name)
595             throws TorqueException
596     {
597         DatabaseMap map = new DatabaseMap(name);
598 
599         // Add info about IDBroker's table.
600         setupIdTable(map);
601 
602         // Setup other ID generators for this map.
603         try
604         {
605             String key = getDatabaseProperty(name, "adapter");
606             if (StringUtils.isEmpty(key))
607             {
608                 key = getDatabaseProperty(name, "driver");
609             }
610             DB db = DBFactory.create(key);
611             for (int i = 0; i < IDGeneratorFactory.ID_GENERATOR_METHODS.length;
612                  i++)
613             {
614                 map.addIdGenerator(IDGeneratorFactory.ID_GENERATOR_METHODS[i],
615                         IDGeneratorFactory.create(db));
616             }
617         }
618         catch (java.lang.InstantiationException e)
619         {
620             throw new TorqueException(e);
621         }
622 
623         // Avoid possible ConcurrentModificationException by
624         // constructing a copy of dbMaps.
625         Map newMaps = new HashMap(dbMaps);
626         newMaps.put(name, map);
627         dbMaps = newMaps;
628 
629         return map;
630     }
631 
632     /***
633      * Register a MapBuilder
634      *
635      * @param className the MapBuilder
636      */
637     public void registerMapBuilder(String className)
638     {
639         mapBuilders.add(className);
640     }
641 
642     /***
643      * Returns the specified property of the given database, or the empty
644      * string if no value is set for the property.
645      *
646      * @param db   The name of the database whose property to get.
647      * @param prop The name of the property to get.
648      * @return     The property's value.
649      */
650     private String getDatabaseProperty(String db, String prop)
651     {
652         return conf.getString(new StringBuffer("database.")
653                 .append(db)
654                 .append('.')
655                 .append(prop)
656                 .toString(), "");
657     }
658 
659     /***
660      * Setup IDBroker's table information within given database map.
661      *
662      * This method should be called on all new database map to ensure that
663      * IDBroker functionality is available in all databases used by the
664      * application.
665      *
666      * @param map the DataBaseMap to setup.
667      */
668     private final void setupIdTable(DatabaseMap map)
669     {
670         map.setIdTable("ID_TABLE");
671         TableMap tMap = map.getIdTable();
672         tMap.addPrimaryKey("ID_TABLE_ID", new Integer(0));
673         tMap.addColumn("TABLE_NAME", "");
674         tMap.addColumn("NEXT_ID", new Integer(0));
675         tMap.addColumn("QUANTITY", new Integer(0));
676     }
677 
678     /***
679      * This method returns a Connection from the default pool.
680      *
681      * @return The requested connection.
682      * @throws TorqueException Any exceptions caught during processing will be
683      *         rethrown wrapped into a TorqueException.
684      */
685     public Connection getConnection()
686             throws TorqueException
687     {
688         return getConnection(getDefaultDB());
689     }
690 
691     /***
692      *
693      * @param name The database name.
694      * @return a database connection
695      * @throws TorqueException Any exceptions caught during processing will be
696      *         rethrown wrapped into a TorqueException.
697      */
698     public Connection getConnection(String name)
699             throws TorqueException
700     {
701         Connection con = null;
702         DataSourceFactory dsf = null;
703         try
704         {
705             dsf = (DataSourceFactory) dsFactoryMap.get(name);
706             con = dsf.getDataSource().getConnection();
707         }
708         catch (Exception e)
709         {
710             if (dsf == null && e instanceof NullPointerException)
711             {
712                 throw new NullPointerException(
713                         "There was no DataSourceFactory "
714                         + "configured for the connection " + name);
715             }
716             else
717             {
718                 throw new TorqueException(e);
719             }
720         }
721         return con;
722     }
723 
724     /***
725      * This method returns a Connecton using the given parameters.
726      * You should only use this method if you need user based access to the
727      * database!
728      *
729      * @param name The database name.
730      * @param username The name of the database user.
731      * @param password The password of the database user.
732      * @return A Connection.
733      * @throws TorqueException Any exceptions caught during processing will be
734      *         rethrown wrapped into a TorqueException.
735      */
736     public Connection getConnection(String name, String username,
737             String password)
738             throws TorqueException
739     {
740         Connection con = null;
741         DataSourceFactory dsf = null;
742         try
743         {
744             dsf = (DataSourceFactory) dsFactoryMap.get(name);
745             con = dsf.getDataSource().getConnection(username, password);
746         }
747         catch (Exception e)
748         {
749             if (dsf == null && e instanceof NullPointerException)
750             {
751                 throw new NullPointerException(
752                         "There was no DataSourceFactory "
753                         + "configured for the connection " + name);
754             }
755             else
756             {
757                 throw new TorqueException(e);
758             }
759         }
760         return con;
761     }
762 
763     /***
764      * Returns database adapter for a specific connection pool.
765      *
766      * @param name A pool name.
767      * @return The corresponding database adapter.
768      * @throws TorqueException Any exceptions caught during processing will be
769      *         rethrown wrapped into a TorqueException.
770      */
771     public DB getDB(String name) throws TorqueException
772     {
773         return (DB) adapterMap.get(name);
774     }
775 
776     ///////////////////////////////////////////////////////////////////////////
777 
778     /***
779      * Returns the name of the default database.
780      *
781      * @return name of the default DB
782      */
783     public String getDefaultDB()
784     {
785         if (conf == null)
786         {
787             return DEFAULT_NAME;
788         }
789         else if (defaultDBName == null)
790         {
791             // Determine default database name.
792             defaultDBName =
793                     conf.getString(Torque.DATABASE_DEFAULT, 
794                             DEFAULT_NAME).trim();
795         }
796 
797         return defaultDBName;
798     }
799 
800     /***
801      * Closes a connection.
802      *
803      * @param con A Connection to close.
804      */
805     public void closeConnection(Connection con)
806     {
807         if (con != null)
808         {
809             try
810             {
811                 con.close();
812             }
813             catch (SQLException e)
814             {
815                 log.error("Error occured while closing connection.", e);
816             }
817         }
818     }
819 }