View Javadoc

1   package com.atlassian.config.bootstrap;
2   
3   import com.atlassian.config.ApplicationConfiguration;
4   import com.atlassian.config.ConfigurationException;
5   import com.atlassian.config.HomeLocator;
6   import com.atlassian.config.db.DatabaseDetails;
7   import com.atlassian.config.db.HibernateConfig;
8   import com.atlassian.config.db.HibernateConfigurator;
9   import com.atlassian.config.setup.SetupPersister;
10  import org.apache.commons.lang.StringUtils;
11  import org.apache.log4j.Logger;
12  
13  import javax.naming.InitialContext;
14  import javax.naming.NamingException;
15  import javax.sql.DataSource;
16  import java.sql.Connection;
17  import java.sql.DriverManager;
18  import java.sql.SQLException;
19  import java.sql.Statement;
20  import java.util.*;
21  
22  /**
23   * Generic Bootstrap Manager using Spring & Hibernate
24   */
25  public class DefaultAtlassianBootstrapManager implements AtlassianBootstrapManager
26  {
27      public static final Logger log = Logger.getLogger(DefaultAtlassianBootstrapManager.class);
28  
29      // ------------------------------------------------------------------------------------------------------- Constants
30      // ------------------------------------------------------------------------------------------------- Type Properties
31      protected boolean bootstrapped;
32      protected String bootstrapFailureReason;
33      /**
34       * A helper operation string, used for conditional moves throughout the bootstrapManager in the controller
35       */
36      private String operation;
37  
38      // --------------------------------------------------------------------------------------------- Injected Properties
39      private List tables;
40  
41      // ---------------------------------------------------------------------------------------------------- Dependencies
42      protected ApplicationConfiguration applicationConfig;
43      protected SetupPersister setupPersister;
44      protected HomeLocator homeLocator;
45      protected HibernateConfigurator hibernateConfigurator;
46      protected HibernateConfig hibernateConfig;
47  
48      // ---------------------------------------------------------------------------------------------------- Constructors
49  
50      // -------------------------------------------------------------------------------------------------- Public Methods
51      public void init() throws BootstrapException
52      {
53          /**
54           * Importantly, we do not throw a bootstrap exception if the home variable has yet to be set.
55           * So, if we can't build a home  we'll let the user know about this via the normal process,
56           * CheckListAction
57           */
58          try
59          {
60              if (StringUtils.isNotEmpty(homeLocator.getHomePath()))
61              {
62                  applicationConfig.setApplicationHome(homeLocator.getHomePath());
63                  applicationConfig.setConfigurationFileName(homeLocator.getConfigFileName());
64  
65                  if (applicationConfig.configFileExists())
66                  {
67                      applicationConfig.load();
68                  }
69  
70                  afterConfigurationLoaded();
71  
72                  setupPersister.setSetupType(applicationConfig.getSetupType());
73                  if (SetupPersister.SETUP_STATE_COMPLETE.equals(setupPersister.getCurrentStep()))
74                  {
75                      // If persistence upgrade fails, do not publish
76                      if (!performPersistenceUpgrade())
77                      {
78                          return;
79                      }
80  
81                      applicationConfig.setSetupComplete(true);
82                      publishConfiguration();
83                  }
84              }
85              else
86              {
87                  // If no Home is set, the bootstrap is still considered successful,
88                  // we just move on to the checklist action that says there's no home set.
89                  log.warn("Unable to set up application config: no home set");
90              }
91  
92              finishBootstrapInitialisation();
93  
94              bootstrapped = true;
95          }
96          catch (ConfigurationException e)
97          {
98              log.error("Home is not configured properly: " + e);
99              bootstrapped = false;
100             bootstrapFailureReason = e.getMessage();
101         }
102     }
103 
104     public void publishConfiguration()
105     {
106         // default implementation does nothing
107     }
108 
109 
110     // --------------------------------------------------------------------------- ApplicationConfig convenience methods
111     /**
112      * Get a single property.
113      */
114     public Object getProperty(String key)
115     {
116         Object o = null;
117         try
118         {
119             o = applicationConfig.getProperty(key);
120         }
121         catch (NullPointerException e)
122         {
123             log.error("BootstrapManager was asked to fetch property (" + key + ") and found a NullPointer");
124         }
125 
126         return o;
127     }
128 
129     /**
130      * Set a single property.
131      */
132     public void setProperty(String key, Object value)
133     {
134         if (value == null)
135         {
136             applicationConfig.removeProperty(key);
137         }
138         else
139         {
140             applicationConfig.setProperty(key, value);
141         }
142         if (isSetupComplete())
143         {
144             publishConfiguration();
145         }
146     }
147 
148     public boolean isPropertyTrue(String prop)
149     {
150         return "true".equals(getString(prop));
151     }
152 
153     /**
154      * Remove a single property.
155      */
156     public void removeProperty(String key)
157     {
158         applicationConfig.removeProperty(key);
159     }
160 
161     /**
162      * Convenience method to retrieve a property as a string.
163      */
164     public String getString(String key)
165     {
166         return (String) applicationConfig.getProperty(key);
167     }
168 
169     public String getFilePathProperty(String key)
170     {
171         return getString(key);
172     }
173 
174     /**
175      * Retrieve all property keys.
176      */
177     public Collection getPropertyKeys()
178     {
179         return applicationConfig.getProperties().keySet();
180     }
181 
182     /**
183      * Get a map of all properties with a given prefix.
184      */
185     public Map getPropertiesWithPrefix(String prefix)
186     {
187         return applicationConfig.getPropertiesWithPrefix(prefix);
188     }
189 
190     public void save() throws ConfigurationException
191     {
192         applicationConfig.save();
193     }
194 
195     public String getConfiguredApplicationHome()
196     {
197         return homeLocator.getHomePath();
198     }
199 
200     /**
201      * This should be the first method called before any bootstrapManager logic is performed.
202      *
203      * @return true for a complete bootstrapManager, otherwise false
204      */
205     public boolean isSetupComplete()
206     {
207         return isBootstrapped() && applicationConfig.isSetupComplete();
208     }
209 
210     public void setSetupComplete(boolean complete)
211     {
212         applicationConfig.setSetupComplete(complete);
213     }
214 
215     // ---------------------------------------------------------------------------------------------------- Build Number
216     public String getBuildNumber()
217     {
218         return applicationConfig.getBuildNumber();
219     }
220 
221     public void setBuildNumber(String buildNumber)
222     {
223         applicationConfig.setBuildNumber(buildNumber);
224     }
225 
226 
227     // -------------------------------------------------------------------------------------- Hibernate / Database Setup
228 
229     /**
230      * Gets all hibernate properties from the config starting 'hibernate.'
231      *
232      * @return all hibernate properties in a Properties map
233      */
234     public Properties getHibernateProperties()
235     {
236         Properties props = new Properties();
237         props.putAll(applicationConfig.getPropertiesWithPrefix("hibernate."));
238         return props;
239     }
240 
241     /**
242      * Mediates the call to HibernateConfigurator's instance method, configureDatabase(dbDetails, embedded)
243      *
244      * @param dbDetails - DatabaseDetails object holding connection details
245      * @param embedded  - true if using the default Confluence database, HSQLDB
246      * @throws BootstrapException
247      */
248     public void bootstrapDatabase(DatabaseDetails dbDetails, boolean embedded)
249             throws BootstrapException
250     {
251         try
252         {
253             hibernateConfigurator.configureDatabase(dbDetails, embedded);
254         }
255         catch (ConfigurationException e)
256         {
257             log.fatal("Could not successfully configure database: \n db: " + dbDetails + " \n embedded = " + embedded);
258             log.fatal("ConfigurationException reads thus: " + e);
259             hibernateConfigurator.unconfigureDatabase();
260             throw new BootstrapException(e);
261         }
262 
263         Connection conn = null;
264         try
265         {
266             conn = getTestDatabaseConnection(dbDetails);
267             if (!databaseContainsExistingData(conn))
268             {
269                 throw new BootstrapException("Schema creation complete, but database tables don't seem to exist.");
270             }
271         }
272         finally
273         {
274             try
275             {
276                 if (conn != null) conn.close();
277             }
278             catch (SQLException e)
279             {
280                 // ignore exception in finally
281             }
282         }
283 
284         postBootstrapDatabase();
285     }
286 
287 
288 
289     /**
290      * Mediates the call to HibernateConfigurator's instance method, configureDatasource(datasourceName,
291      * hibernateDialect)
292      *
293      * @param datasourceName
294      * @param hibernateDialect
295      * @throws BootstrapException
296      */
297     public void bootstrapDatasource(String datasourceName, String hibernateDialect)
298             throws BootstrapException
299     {
300         try
301         {
302             hibernateConfigurator.configureDatasource(datasourceName, hibernateDialect);
303         }
304         catch (ConfigurationException e)
305         {
306             log.fatal("Could not successfully configure datasource: \n db: " + datasourceName + " \n dialect = "
307                       + hibernateDialect);
308             log.fatal("ConfigurationException reads thus: " + e);
309             hibernateConfigurator.unconfigureDatabase();
310             throw new BootstrapException(e);
311         }
312         Connection connection = null;
313         try
314         {
315             connection = getTestDatasourceConnection(datasourceName);
316             if (!databaseContainsExistingData(connection))
317             {
318                 throw new BootstrapException("Schema creation complete, but tables could not be found.");
319             }
320         }
321         finally
322         {
323             try
324             {
325                 if (connection != null) connection.close();
326             }
327             catch (SQLException e)
328             {
329                 // ignore in finally
330             }
331         }
332         postBootstrapDatabase();
333     }
334 
335     /**
336      * Ensure we can open a connection to the configured database. If this fails, then the database is down or we got
337      * the connection string wrong.
338      *
339      * @throws BootstrapException if the connection fails for any reason.
340      */
341     public Connection getTestDatabaseConnection(DatabaseDetails databaseDetails) throws BootstrapException
342     {
343         Connection conn = null;
344         try
345         {
346             Class.forName(databaseDetails.getDriverClassName());
347             conn = DriverManager.getConnection(getDbUrl(databaseDetails),
348                                                databaseDetails.getUserName(),
349                                                databaseDetails.getPassword());
350             if (conn == null)
351             {
352                 throw new BootstrapException("Connection was null. We could not successfully connect to the specified database!");
353             }
354             return conn;
355         }
356         catch (SQLException e)
357         {
358             log.error("Could not successfully test your database: ", e);
359             throw new BootstrapException(e);
360         }
361         catch (ClassNotFoundException e)
362         {
363             log.error("Could not successfully test your database: ", e);
364             throw new BootstrapException(e);
365         }
366     }
367 
368 
369     /**
370      * Gets a test connection to the datasource.
371      *
372      * @throws BootstrapException if a connection cannot be made.
373      */
374     public Connection getTestDatasourceConnection(String datasourceName) throws BootstrapException
375     {
376         DataSource dsrc;
377 
378         log.debug("datasource is " + datasourceName);
379 
380         try
381         {
382             InitialContext ctx = new InitialContext();
383             dsrc = (DataSource) ctx.lookup(datasourceName);
384 
385             if (dsrc == null)
386             {
387                 throw new NamingException("Could not locate " + datasourceName);
388             }
389         }
390         catch (NamingException e)
391         {
392             log.error("Could not locate datasource: " + datasourceName, e);
393             throw new BootstrapException("Could not locate datasource: " + datasourceName, e);
394         }
395         catch (ClassCastException e)
396         {
397             log.error("Couldn't locate Datasource (" + datasourceName + ") in the initial context. An object was bound to this name but whatever we found, it wasn't a Datasource: " + e);
398             throw new BootstrapException("Couldn't locate Datasource (" + datasourceName + ") in the initial context. An object was bound to this name but whatever we found, it wasn't a Datasource: ", e);
399         }
400 
401         try
402         {
403             Connection conn = dsrc.getConnection();
404             conn.createStatement();
405             return conn;
406         }
407         catch (SQLException e)
408         {
409             log.error("Couldn't open a connection on Datasource (" + datasourceName + "): " + e);
410             throw new BootstrapException("Couldn't open a connection on Datasource (" + datasourceName + "): ", e);
411         }
412         catch (NullPointerException e)
413         {
414             log.error("Couldn't open a connection on Datasource (" + datasourceName + "): " + e);
415             throw new BootstrapException("Couldn't open a connection on Datasource (" + datasourceName + "): ", e);
416         }
417     }
418 
419     /**
420      * Returns true if any of the specified tables exists, otherwise false.
421      *
422      * @param connection the Connection to the Db to check
423      * @see {@link #setTables(List)}
424      */
425     public boolean databaseContainsExistingData(Connection connection)
426     {
427         for (Iterator iterator = getTables().iterator(); iterator.hasNext();)
428         {
429             String table = (String) iterator.next();
430             if (tableExists(connection, table))
431                 return true;
432         }
433         return false;
434     }
435 
436     private boolean tableExists(Connection conn, String table)
437     {
438         Statement st = null;
439         try
440         {
441             st = conn.createStatement();
442             st.executeQuery("select count(*) from " + table);
443             return true;
444         }
445         catch (SQLException e)
446         {
447             return false;
448         }
449         finally
450         {
451             if (st != null)
452             {
453                 try
454                 {
455                     st.close();
456                 }
457                 catch (SQLException e)
458                 {
459                     // ignore
460                 }
461             }
462         }
463     }
464 
465     // ------------------------------------------------------------------------------------------------ Application Home
466     public boolean isApplicationHomeValid()
467     {
468         return applicationConfig.isApplicationHomeValid();
469     }
470 
471     // ------------------------------------------------------------------------------------------------ Extension points
472 
473     /**
474      * Extension point for peforming custom upgardes of dB
475      * @return true if successful, false if failed
476      */
477     protected boolean performPersistenceUpgrade()
478     {
479         // do nudda
480         return true;
481     }
482 
483     /**
484      * Generic extension point to run before bootstrapping can be successful. Common things to do here includes setting
485      * up the license
486      * @throws ConfigurationException is thrown if bootstrapping should fail
487      */
488     protected void finishBootstrapInitialisation() throws ConfigurationException
489     {
490         // Do nudda
491     }
492 
493     /**
494      * Get the URL form the {@link DatabaseDetails} object. Allows sub-classes to post-process the URL
495      * @param dbDetails
496      * @return database URL
497      */
498     protected String getDbUrl(DatabaseDetails dbDetails)
499     {
500         return dbDetails.getDatabaseUrl();
501     }
502 
503     /**
504      * Allows a custom actions to be performed after bootstrapping the database
505      */
506     protected void postBootstrapDatabase()
507     {
508         // Do nudda
509     }
510 
511     /**
512      * Extension point for initialization performed after configuration is loaded
513      */
514     protected void afterConfigurationLoaded() throws ConfigurationException
515     {
516         // Do nudda
517     }
518 
519 
520 
521     // -------------------------------------------------------------------------------------- Basic accessors & mutators
522     public void setApplicationConfig(ApplicationConfiguration applicationConfig)
523     {
524         this.applicationConfig = applicationConfig;
525     }
526 
527     public void setHomeLocator(HomeLocator homeLocator)
528     {
529         this.homeLocator = homeLocator;
530     }
531 
532     public void setSetupPersister(SetupPersister setupPersister)
533     {
534         this.setupPersister = setupPersister;
535     }
536 
537     public HomeLocator getHomeLocator()
538     {
539         return homeLocator;
540     }
541 
542     public ApplicationConfiguration getApplicationConfig()
543     {
544         return applicationConfig;
545     }
546 
547     public String getApplicationHome()
548     {
549         return applicationConfig.getApplicationHome();
550     }
551 
552     public SetupPersister getSetupPersister()
553     {
554         return setupPersister;
555     }
556 
557     /**
558      * @return boolean indicating whether Confluence is bootstrapped.
559      */
560     public boolean isBootstrapped()
561     {
562         return bootstrapped;
563     }
564 
565     public String getOperation()
566     {
567         return operation;
568     }
569 
570     public void setOperation(String operation)
571     {
572         this.operation = operation;
573     }
574 
575     public HibernateConfigurator getHibernateConfigurator()
576     {
577         return hibernateConfigurator;
578     }
579 
580     public void setHibernateConfigurator(HibernateConfigurator hibernateConfigurator)
581     {
582         this.hibernateConfigurator = hibernateConfigurator;
583     }
584 
585     public HibernateConfig getHibernateConfig()
586     {
587         return hibernateConfig;
588     }
589 
590     public void setHibernateConfig(HibernateConfig hibernateConfig)
591     {
592         this.hibernateConfig = hibernateConfig;
593     }
594 
595     public String getBootstrapFailureReason()
596     {
597         return bootstrapFailureReason;
598     }
599 
600     public List getTables()
601     {
602         return tables;
603     }
604 
605     public void setTables(List tables)
606     {
607         this.tables = tables;
608     }
609 }