View Javadoc

1   package org.bitrepository.service.database;
2   
3   import java.sql.Connection;
4   import java.sql.SQLException;
5   
6   import org.bitrepository.settings.referencesettings.DatabaseSpecifics;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  /**
11   * General class for obtaining a database connection to a service's database. 
12   * The manager is responsible for:
13   * - Creating a new database, if none existed on the connection url (derby only).
14   * - Connecting to the database.
15   * - Migrating the database schema if needed (derby only).
16   * 
17   * If the database is unavailable exceptions will be thrown detailing the reason. 
18   * Databases that are not automatically migrated will result in an exception. 
19   */
20  public abstract class DatabaseManager {
21      private Logger log = LoggerFactory.getLogger(getClass());
22      private static final String derbyDriver = "org.apache.derby.jdbc.EmbeddedDriver";
23      private static final String postgressDriver = "org.postgresql.Driver";
24      protected DBConnector connector = null;
25      
26      /**
27       * Method to obtain the DBConnector for the concrete instance of the database. 
28       * When a connection is returned, the database is ensured to be existing, connected 
29       * and in the expected version.
30       * @return {@link DBConnector} DBConnector to the database.
31       */
32      public DBConnector getConnector() {
33          DatabaseSpecifics ds = getDatabaseSpecifics();
34          if(ds.getDriverClass().equals(derbyDriver)) {
35              return getDerbyConnection();    
36          } else if(ds.getDriverClass().equals(postgressDriver)) {
37              return getPostgresConnection();
38          } else {
39              throw new IllegalStateException("The database driver: '" + ds.getDriverClass() + "' is not supported."
40                      + " Supported drivers are: '" + derbyDriver + "' and '" + postgressDriver + "'.");
41          }
42          
43          
44      }
45      
46      /**
47       * Get a connection to a postgres database 
48       * This method won't attempt to create a database if connection is unsuccessful, 
49       * also i won't attempt to migrate the database.
50       * @return {@link DBConnector} The connector to the database
51       */
52      private DBConnector getPostgresConnection() {
53          if(connector == null) {
54              obtainConnection();
55          }
56          if(needsMigration()) {
57              throw new IllegalStateException("Automatic migration of postgres databases are not supported. "+ 
58                      "The database on: " + getDatabaseSpecifics().getDatabaseURL() +  
59                      " needs manuel migration.");
60          }
61          
62          return connector;
63      }
64      
65      /**
66       * Get a connection to a derby database 
67       * Creates the database if the connection to it fails. Migrates the database schema if it is needed.
68       * @return {@link DBConnector} The connector to the database 
69       */
70      private DBConnector getDerbyConnection() {
71          if(connector == null) {
72              try {
73                  obtainConnection();
74              } catch (IllegalStateException e) {
75                  if(allowAutoCreate()) {
76                      log.warn("Failed to connect to database, attempting to create it");
77                      createDatabase();
78                  } else {
79                      log.error("Failed to connect to database, autocreation is disabled");
80                      throw e;
81                  }
82              }
83              if(connector == null) {
84                  obtainConnection();
85              }
86              log.info("Checking if the database needs to be migrated.");
87              if(needsMigration()) {
88                  if(allowAutoMigrate()) {
89                      log.warn("Database needs to be migrated, attempting to automigrate.");
90                      migrateDatabase();
91                  } else {
92                      log.error("Database needs migration, automigrations is disabled.");
93                      throw new IllegalStateException("Database needs migration, automigrations is disabled.");
94                  }
95              } else {
96                  log.info("Database migration was not needed.");
97              }
98          }
99          return connector;
100     }
101     
102     /**
103      * Obtain the database specifics for the database to manage. 
104      * @return DatabaseSpecifics The database specifics for the concrete database 
105      */
106     protected abstract DatabaseSpecifics getDatabaseSpecifics();
107     
108     /**
109      * Get the migrator for the concrete database
110      * @return DatabaseMigrator The concrete database migrator 
111      */
112     protected abstract DatabaseMigrator getMigrator();
113     
114     /**
115      * Method to determine whether the database needs to be migrated. 
116      * @return true if migration should be done, false otherwise 
117      */
118     protected abstract boolean needsMigration();
119     
120     /**
121      * Get the path to the file containing the full database schema for initial creation of 
122      * the concrete database. 
123      * @return String The path to the file with the database in String form.  
124      */
125     protected abstract String getDatabaseCreationScript();
126     
127     /**
128      * Connect to a database using the database specifics from the implementing class. 
129      * @throws IllegalStateException if connection cannot be obtained.
130      */
131     protected void obtainConnection() throws IllegalStateException {
132         log.debug("Obtaining db connection.");
133         connector = new DBConnector(getDatabaseSpecifics()); 
134         Connection connection = connector.getConnection();
135         try {
136             connection.close();
137         } catch (SQLException e) {
138             log.warn("Connection opened for testing connectivaty failed to close", e);
139         }
140         log.debug("Obtained db connection.");
141     }
142     
143     /**
144      * Perform the actual migration of the concrete database 
145      */
146     private void migrateDatabase() {
147         DatabaseMigrator migrator = getMigrator();
148         if(migrator != null) {
149             migrator.migrate();
150         } else {
151             throw new IllegalStateException("The database was attempted migrated, but no migrator was available.");
152         }
153     }
154     
155     private boolean allowAutoMigrate() {
156         DatabaseSpecifics ds = getDatabaseSpecifics();
157         if(ds.isSetAllowAutoMigrate()) {
158             return ds.isAllowAutoMigrate();
159         } else {
160             return true;
161         }
162     }
163     
164     private boolean allowAutoCreate() {
165         DatabaseSpecifics ds = getDatabaseSpecifics();
166         if(ds.isSetAllowAutoCreate()) {
167             return ds.isAllowAutoCreate();
168         } else {
169             return true;
170         }    }
171     
172     /**
173      * Create the database on the given database specifics.  
174      */
175     private void createDatabase() {
176         DatabaseCreator databaseCreator = new DatabaseCreator();
177         databaseCreator.createDatabase(getDatabaseSpecifics(), getDatabaseCreationScript());
178     }
179     
180 }