Coverage Report - org.melati.poem.PoemDatabaseFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
PoemDatabaseFactory
95%
76/80
76%
20/26
3.182
PoemDatabaseFactory$PoemShutdownThread
76%
19/25
50%
1/2
3.182
 
 1  
 /*
 2  
  * $Source$
 3  
  * $Revision$
 4  
  *
 5  
  * Copyright (C) 2007 Tim Pizey
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     Tim Pizey <timp At paneris.org>
 42  
  *     http://paneris.org/~timp
 43  
  */
 44  
 package org.melati.poem;
 45  
 
 46  
 import java.util.Enumeration;
 47  
 import java.util.Hashtable;
 48  
 import java.util.Vector;
 49  
 
 50  
 /**
 51  
  * @author timp
 52  
  * @since 2 Feb 2007
 53  
  * 
 54  
  */
 55  
 public final class PoemDatabaseFactory {
 56  
 
 57  1
   private static final Hashtable<String,Database> databases = new Hashtable<String,Database>();
 58  
 
 59  1
   private static PoemShutdownThread poemShutdownThread = new PoemShutdownThread();
 60  
 
 61  
   /**
 62  
    * Disallow instantiation.
 63  
    */
 64  0
   private PoemDatabaseFactory() {
 65  0
   }
 66  
 
 67  
   /**
 68  
    * Retrieve the databases which have completed initialisation.
 69  
    * 
 70  
    * @return a <code>Vector</code> of the initialised databases
 71  
    */
 72  
   public static Vector<Database> initialisedDatabases() {
 73  2
     Vector<Database> dbs = new Vector<Database>();
 74  2
     Enumeration<String> e = null;
 75  2
     synchronized (databases) {
 76  2
       e = databases.keys();
 77  6
       while (e.hasMoreElements()) {
 78  4
         Database dbOrPending = databases.get(e.nextElement());
 79  4
         if (dbOrPending != pending)
 80  4
           dbs.addElement(dbOrPending);
 81  4
       }
 82  2
     }
 83  2
     return dbs;
 84  
   }
 85  
 
 86  
   /**
 87  
    * Retrieve the names of the databases which have completed initialisation.
 88  
    * Note that a database which has not been used will not have been initialised.
 89  
    * 
 90  
    * @return a <code>Vector</code> of the initialised database names
 91  
    */
 92  
   public static Vector<String> getInitialisedDatabaseNames() {
 93  2
     Vector<String> dbs = new Vector<String>();
 94  2
     Enumeration<String> e = null;
 95  2
     synchronized (databases) {
 96  2
       e = databases.keys();
 97  6
       while (e.hasMoreElements()) {
 98  4
         String key = (String)e.nextElement();
 99  4
         Object dbOrPending = databases.get(key);
 100  4
         if (dbOrPending != pending)
 101  4
           dbs.addElement(key);
 102  4
       }
 103  2
     }
 104  2
     return dbs;
 105  
   }
 106  
 
 107  1
   private static final Database pending = new PoemDatabase();
 108  
 
 109  
   /**
 110  
    * Retrieve a database by name.
 111  
    * 
 112  
    * @param name
 113  
    *          the name of the database
 114  
    * @throws DatabaseInitialisationPoemException
 115  
    *           if any Exception is trapped
 116  
    * @return a <code>Database</code> with the name specified
 117  
    */
 118  
   public static Database getDatabase(String name)
 119  
           throws DatabaseInitialisationPoemException {
 120  4
     if (name == null)
 121  2
       throw new NullPointerException();
 122  
 
 123  
     Object dbOrPending;
 124  
 
 125  2
     synchronized (databases) {
 126  2
       dbOrPending = databases.get(name);
 127  2
     }
 128  2
     if (dbOrPending == pending)
 129  0
       throw new ConnectionPendingException(name);
 130  2
     return (Database)dbOrPending;
 131  
   }
 132  
 
 133  
   /**
 134  
    * Return a database from the cache or create it. NOTE The first sucessful
 135  
    * invocation will determine databases settings.
 136  
    * 
 137  
    * @param name
 138  
    *          a short name of the db
 139  
    * @param url
 140  
    *          a JDBC url
 141  
    * @param user
 142  
    *          user authorised to access the databse through JDBC
 143  
    * @param password
 144  
    *          password for the user
 145  
    * @param clazz
 146  
    *          the name of the (POEM) database class
 147  
    * @param dbmsClass
 148  
    *          the name of the (POEM) dbms class
 149  
    * @param addConstraints
 150  
    *          whether to add constraints to the databases JDBC meta data
 151  
    * @param logSQL
 152  
    *          whether SQL statements should be logged
 153  
    * @param logCommits
 154  
    *          whether commits should be logged
 155  
    * @param maxTransactions
 156  
    *          the number of transactions (one less than the number of
 157  
    *          connections)
 158  
    * @return a new or existing database
 159  
    */
 160  
   public static Database getDatabase(String name, String url, String user,
 161  
           String password, String clazz, String dbmsClass,
 162  
           boolean addConstraints, boolean logSQL, boolean logCommits,
 163  
           int maxTransactions) {
 164  
 
 165  
     Database dbOrPending;
 166  
 
 167  25733
     synchronized (databases) {
 168  25733
       dbOrPending = databases.get(name);
 169  25733
     }
 170  25733
     if (dbOrPending == pending)
 171  0
       throw new ConnectionPendingException(name);
 172  25733
     if (dbOrPending != null)
 173  25694
       return (Database)dbOrPending;
 174  
 
 175  
     // Set an entry whilst we load
 176  39
     databases.put(name, pending);
 177  
 
 178  39
     Database database = null;
 179  
     try {
 180  
 
 181  
       try {
 182  39
         Object databaseObject = null;
 183  
 
 184  
         try {
 185  39
           databaseObject = Thread.currentThread().getContextClassLoader()
 186  39
                   .loadClass(clazz).newInstance();
 187  1
         } catch (Exception e) {
 188  1
           databaseObject = Class.forName(clazz).newInstance();
 189  38
         }
 190  
 
 191  38
         if (!(databaseObject instanceof Database))
 192  1
           throw new ClassCastException("The .class=" + clazz
 193  1
                   + " entry named a class of type " + databaseObject.getClass()
 194  
                   + ", " + "which is not an org.melati.poem.Database");
 195  
 
 196  37
         database = (Database)databaseObject;
 197  
 
 198  
         // Set properties
 199  37
         database.setLogSQL(logSQL);
 200  37
         database.setLogCommits(logCommits);
 201  
 
 202  37
         database.connect(name, dbmsClass, url, user, password, maxTransactions);
 203  
 
 204  37
         if (addConstraints)
 205  16
           database.addConstraints();
 206  
       } finally {
 207  
         // get it removed from the "initialising" state even if an Error, such
 208  
         // as no class found, occurs
 209  39
         databases.remove(name);
 210  37
       }
 211  
 
 212  37
       databases.put(name, database);
 213  2
     } catch (Exception e) {
 214  2
       throw new DatabaseInitialisationPoemException(name, e);
 215  37
     }
 216  37
     return database;
 217  
   }
 218  
 
 219  
   /**
 220  
    * Enable a database to be reinitialised, without 
 221  
    * incurring the full overhead of restarting hsqldb, 
 222  
    * used in tests.
 223  
    * 
 224  
    * @param name
 225  
    *          name of db to remove
 226  
    */
 227  
   public static void removeDatabase(String name) {
 228  35
     databases.remove(name);
 229  35
   }
 230  
 
 231  
   /**
 232  
    * Disconnect and disconnect from a known database.
 233  
    * 
 234  
    * @param name
 235  
    *          name of db to remove
 236  
    */
 237  
   public static void disconnectDatabase(String name) {
 238  2
     Database db = (Database)databases.get(name);
 239  2
     if(db != null && db.getCommittedConnection() != null) {
 240  2
       db.disconnect();
 241  2
       databases.remove(name);
 242  
     } 
 243  2
   }
 244  
 
 245  
   /**
 246  
    * Disconnect from all initialised databases.
 247  
    */
 248  
   public static void disconnectFromDatabases() { 
 249  1
     Vector<String> dbs = PoemDatabaseFactory.getInitialisedDatabaseNames();
 250  1
     Enumeration<String> them = dbs.elements();
 251  3
     while (them.hasMoreElements()) {
 252  2
       disconnectDatabase((String)them.nextElement());
 253  
     }
 254  1
   }
 255  
   /**
 256  
    * Shutdown databases cleanly when JVM exits.
 257  
    * 
 258  
    * This method is called in one of two ways: when the jvm exits or when a 
 259  
    * ServletContext is detroyed.
 260  
    * Jetty, and presumably other servlet containers, registers its listeners as 
 261  
    * shutdown hooks too, so there is no way of determining which is to be executed first.
 262  
    * 
 263  
    * If PoemDatabaseFactory has not been initialised before the ContextListener is 
 264  
    * activated then this thread will be created but will fail to register.
 265  
    * It will then be run by the ContextListener.
 266  
    * 
 267  
    * It may well be that the registered thread will be run before the ContextListener thread, 
 268  
    * or visa versa, so there is a synchronised variable to ensure it actually does something 
 269  
    * only the first time. 
 270  
    * 
 271  
    * @author timp
 272  
    * @since 23 May 2007
 273  
    * See org.melati.servlet.PoemServletContextListener
 274  
    * 
 275  
    */
 276  
   public static class PoemShutdownThread extends Thread {
 277  
     /** Constructor. */
 278  
     public PoemShutdownThread() {
 279  1
       super();
 280  1
       setName("PoemShutdownThread");
 281  
       try { 
 282  1
         Runtime.getRuntime().addShutdownHook(this);
 283  1
         System.err.println("\n*** PoemShutdownThread registered. ***\n");
 284  0
       } catch (IllegalStateException e) { 
 285  0
         System.err.println("\n*** PoemShutdownThread tried to register during shutdown. ***\n");
 286  
         
 287  
         // Happens when the PoemServletContextListener 
 288  
         // tries to shutdown databases when none have yet been 
 289  
         // initialised.
 290  0
         e = null;
 291  1
       }
 292  1
     }
 293  1
     private static Boolean haveRun = Boolean.FALSE;
 294  
     /**
 295  
      * {@inheritDoc}
 296  
      * 
 297  
      * @see java.lang.Thread#run()
 298  
      */
 299  
     public void run() {
 300  1
       synchronized(haveRun) { 
 301  1
         if (!haveRun.booleanValue()) {
 302  1
           haveRun = Boolean.TRUE;
 303  
           try { 
 304  1
             boolean removed = Runtime.getRuntime().removeShutdownHook(this);
 305  0
             System.err.println("\n*** PoemShutdownThread removed: " + removed + " ***\n");
 306  1
           } catch (IllegalStateException e) { 
 307  1
             System.err.println("\n*** PoemShutdownThread cannot be removed at this stage. ***\n");
 308  
             // I think this happens during normal termination, 
 309  
             // you cannot remove a hook during shutdown, 
 310  
             // but if we are run when not in shutdown 
 311  
             // we do want to be removed.
 312  1
             e = null;
 313  0
           }
 314  1
           System.err.println("*** PoemShutdownThread starting to shutdown dbs. ***");
 315  1
           disconnectFromDatabases();
 316  1
           System.err.println("*** PoemShutdownThread has shutdown dbs. ***");
 317  
         } else 
 318  0
           System.err.println("*** PoemShutdownThread has already run. ***");
 319  1
       }
 320  1
     }
 321  
   }
 322  
   /**
 323  
    * @return the poemShutdownThread
 324  
    */
 325  
   public static PoemShutdownThread getPoemShutdownThread() {
 326  1
     return poemShutdownThread;
 327  
   }
 328  
 
 329  
 }