View Javadoc

1   /*
2    * <cvs:source>$Source: /devel/astrogrid/community/server/src/java/org/astrogrid/community/server/database/configuration/DatabaseConfiguration.java,v $</cvs:source>
3    * <cvs:author>$Author: dave $</cvs:author>
4    * <cvs:date>$Date: 2004/09/16 23:18:08 $</cvs:date>
5    * <cvs:version>$Revision: 1.6 $</cvs:version>
6    *
7    * <cvs:log>
8    *   $Log: DatabaseConfiguration.java,v $
9    *   Revision 1.6  2004/09/16 23:18:08  dave
10   *   Replaced debug logging in Community.
11   *   Added stream close() to FileStore.
12   *
13   *   Revision 1.5.82.1  2004/09/16 09:58:48  dave
14   *   Replaced debug with commons logging ....
15   *
16   *   Revision 1.5  2004/06/18 13:45:20  dave
17   *   Merged development branch, dave-dev-200406081614, into HEAD
18   *
19   *   Revision 1.4.36.1  2004/06/17 13:38:59  dave
20   *   Tidied up old CVS log entries
21   *
22   * </cvs:log>
23   *
24   *
25   */
26  package org.astrogrid.community.server.database.configuration ;
27  
28  import org.apache.commons.logging.Log ;
29  import org.apache.commons.logging.LogFactory ;
30  
31  import java.net.URL ;
32  
33  import org.exolab.castor.jdo.JDO ;
34  import org.exolab.castor.jdo.Database ;
35  import org.exolab.castor.jdo.DatabaseNotFoundException ;
36  import org.exolab.castor.jdo.PersistenceException ;
37  
38  import org.exolab.castor.jdo.OQLQuery ;
39  import org.exolab.castor.jdo.QueryResults ;
40  import org.exolab.castor.jdo.QueryException ;
41  
42  import org.exolab.castor.mapping.MappingException ;
43  
44  import java.io.Reader ;
45  import java.io.BufferedReader ;
46  import java.io.InputStream ;
47  import java.io.InputStreamReader ;
48  
49  import java.io.IOException ;
50  import java.io.FileNotFoundException ;
51  
52  /***
53   * A class to handle configuration settings for a Castor JDO database connection.
54   * @todo Move all of the resource finding stuff into a helper class.
55   * @todo Everything should just use URLs instead.
56   *
57   *
58   */
59  public class DatabaseConfiguration
60      {
61      /***
62       * Our debug logger.
63       *
64       */
65      private static Log log = LogFactory.getLog(DatabaseConfiguration.class);
66  
67      /***
68       * The default name of our database.
69       *
70       */
71      public static final String DEFAULT_DATABASE_NAME = "database" ;
72  
73      /***
74       * The default resource name for our JDO config file.
75       *
76       */
77      public static final String DEFAULT_DATABASE_XML = "database.xml" ;
78  
79      /***
80       * The default SQL file name.
81       */
82      public static final String DEFAULT_DATABASE_SQL = "database.sql" ;
83  
84      /***
85       * Public constructor.
86       * Configures a database using the default settings.
87       * This will trigger an automatic load of the default JDO config and engine.
88       *
89       * @see DEFAULT_DATABASE_NAME
90       * @see DEFAULT_DATABASE_XML
91       */
92      public DatabaseConfiguration()
93          throws IOException, MappingException
94          {
95          this(
96          	DEFAULT_DATABASE_NAME,
97          	DEFAULT_DATABASE_XML,
98          	DEFAULT_DATABASE_SQL
99          	) ;
100         }
101 
102     /***
103      * Public constructor.
104      * This will trigger an automatic load of the JDO config and engine.
105      * Appends '.xml' to the database name to set the JDO config name.
106      * @param name - The database name.
107      *
108      */
109     public DatabaseConfiguration(String name)
110         throws IOException, MappingException
111         {
112         this(
113         	name,
114         	(name + ".xml"),
115         	(name + ".sql")
116         	) ;
117         }
118 
119     /***
120      * Public constructor.
121      * This will trigger an automatic load of the JDO config and engine.
122      * @param name - The database name.
123      * @param xml  - The Castor JDO config file.
124      * @param sql  - The SQL script to create the tables.
125      *
126      */
127     public DatabaseConfiguration(String name, String xml, String sql)
128         throws IOException, MappingException
129         {
130         log.debug("") ;
131         log.debug("----\"----") ;
132         log.debug("DatabaseConfiguration()") ;
133         log.debug("  Name : '" + name + "'") ;
134         log.debug("  XML  : '" + xml + "'") ;
135         log.debug("  SQL  : '" + sql + "'") ;
136         //
137         // Set our database name.
138         this.setDatabaseName(name) ;
139         //
140         // Set our database configuration.
141         this.setDatabaseConfigResource(xml) ;
142         //
143         // Set our SQL script.
144         this.setDatabaseScriptResource(sql) ;
145         }
146 
147     /***
148      * Get a new JDO database connection.
149      * This will open a connection, even if the database files are not there.
150      *
151      */
152     public Database getDatabase()
153         throws DatabaseNotFoundException, PersistenceException
154         {
155         //
156         // If we have a JDO engine.
157         if (null != this.databaseEngine)
158             {
159             return this.databaseEngine.getDatabase() ;
160             }
161         //
162         // If we don't have a JDO engine.
163         else {
164             return null ;
165             }
166         }
167 
168     /***
169      * Our JDO database name - this must match the name in the database config.
170      *
171      */
172     private String databaseName = null ;
173 
174     /***
175      * Get our JDO database name.
176      *
177      */
178     public String getDatabaseName()
179         {
180         return this.databaseName ;
181         }
182 
183     /***
184      * Set our JDO database name.
185      * Setting this may trigger a re-load of the JDO engine.
186      * @param name - The database name.
187      *
188      */
189     public void setDatabaseName(String name)
190         throws IOException
191         {
192         //
193         // Set our database name.
194         this.databaseName = name ;
195         //
196         // If the name is not null.
197         if (null != this.databaseName)
198             {
199             //
200             // Re-load our JDO engine.
201             this.loadDatabaseEngine() ;
202             }
203         }
204 
205     /***
206      * Our JDO configuration resource.
207      *
208      */
209     private String databaseConfigResource = null ;
210 
211     /***
212      * Get our JDO configuration resource name.
213      *
214      */
215     public String getDatabaseConfigResource()
216         {
217         return this.databaseConfigResource ;
218         }
219 
220     /***
221      * Set our JDO configuration file name.
222      * This will look for a resource in the local classpath.
223      * Setting this may trigger a re-load of the JDO config and engine.
224      * @param name - The JDO config resource name.
225      *
226      * TODO - Change to a use generic filename.
227      * TODO - Look for a local file as well as a resource.
228      */
229     public void setDatabaseConfigResource(String resource)
230         throws IOException, MappingException
231         {
232         //
233         // Set our config name.
234         this.databaseConfigResource = resource ;
235         //
236         // If the config name is null
237         if (null == this.databaseConfigResource)
238             {
239             //
240             // Reset the config url.
241             this.setDatabaseConfigUrl(null) ;
242             }
243         //
244         // If the config name is not null
245         else {
246             //
247             // Try to get a resource URL for our database config.
248             ClassLoader loader = this.getClass().getClassLoader() ;
249             URL url = loader.getResource(this.databaseConfigResource) ;
250             //
251             // If we couldn't find the resource.
252             if (null == url)
253                 {
254                 //
255                 // Clear the config URL.
256                 this.setDatabaseConfigUrl(null) ;
257                 //
258                 // Throw a FileNotFoundException.
259                 String message = "Failed to find database config : '" + this.databaseConfigResource + "'" ;
260                 throw new FileNotFoundException(message) ;
261                 }
262             //
263             // If we did find the resource.
264             else {
265                 //
266                 // Set the new config URL.
267                 this.setDatabaseConfigUrl(url) ;
268                 }
269             }
270         }
271 
272     /***
273      * Our JDO configuration URL.
274      *
275      */
276     private URL databaseConfigUrl = null ;
277 
278     /***
279      * Get our JDO configuration URL.
280      *
281      */
282     public URL getDatabaseConfigUrl()
283         {
284         return this.databaseConfigUrl ;
285         }
286 
287     /***
288      * Set our JDO configuration URL.
289      * Setting this may trigger a re-load of the JDO config and engine.
290      * @param url - The JDO configuration URL.
291      *
292      */
293     public void setDatabaseConfigUrl(URL url)
294         throws IOException, MappingException
295         {
296         //
297         // Set the current URL.
298         this.databaseConfigUrl = url ;
299         //
300         // If the URL is not null.
301         if (null != this.databaseConfigUrl)
302             {
303             //
304             // Re-load our database config.
305             this.loadDatabaseConfig() ;
306             }
307         }
308 
309     /***
310      * Load our database configuration.
311      *
312      */
313     protected void loadDatabaseConfig()
314         throws IOException, MappingException
315         {
316         //
317         // If the config URL is not null.
318         if (null != this.databaseConfigUrl)
319             {
320             //
321             // Load the JDO configuration.
322             JDO.loadConfiguration(this.databaseConfigUrl.toString()) ;
323             //
324             // If we have a database engine.
325             if (null != this.databaseEngine)
326                 {
327                 //
328                 // Set our engine configuration.
329                 this.databaseEngine.setConfiguration(this.databaseConfigUrl.toString()) ;
330                 }
331             }
332         }
333 
334     /***
335      * Our JDO database engine.
336      *
337      */
338     private JDO databaseEngine = null ;
339 
340     /***
341      * Access to our JDO engine.
342      *
343      */
344     public JDO getDatabaseEngine()
345         {
346         return this.databaseEngine ;
347         }
348 
349     /***
350      * Create our JDO database engine.
351      *
352      */
353     protected void loadDatabaseEngine()
354         throws IOException
355         {
356         log.debug("") ;
357         log.debug("----\"----") ;
358         log.debug("DatabaseConfiguration:loadDatabaseEngine()") ;
359         log.debug("  Name   : '" + this.databaseName + "'" ) ;
360         log.debug("  Config : '" + this.databaseConfigUrl + "'" ) ;
361         //
362         // If we have a database name.
363         if (null != this.databaseName)
364             {
365             //
366             // Create our JDO engine.
367             this.databaseEngine = new JDO(this.databaseName) ;
368             //
369             // If we didn't get an engine.
370             if (null == this.databaseEngine)
371                 {
372                 String message = "Failed to create JDO engine : '" + databaseName + "'" ;
373                 throw new FileNotFoundException(message) ;
374                 }
375             //
376             // If we have a config URL.
377             if (null != this.databaseConfigUrl)
378                 {
379                 //
380                 // Set the JDO configuration.
381                 this.databaseEngine.setConfiguration(this.databaseConfigUrl.toString()) ;
382                 }
383             }
384         }
385 
386     /***
387      * Our database SQL script, used to create our database tables.
388      *
389      */
390     private String databaseScriptResource = null ;
391 
392 //
393 // TODO - Change this to handle URLs as well.
394 //
395 
396     /***
397      * Get our database SQL script resource name.
398      *
399      */
400     public String getDatabaseScriptResource()
401         {
402         return this.databaseScriptResource ;
403         }
404 
405     /***
406      * Set our database SQL script resource name.
407      * @param resource - The resource name of our database SQL script.
408      * TODO - Change this to handle URLs instead.
409      *
410      */
411     public void setDatabaseScriptResource(String resource)
412         {
413         this.databaseScriptResource = resource ;
414         }
415 
416     /***
417      * Create our database tables.
418      *
419      */
420     public void resetDatabaseTables()
421         throws IOException, DatabaseNotFoundException, PersistenceException
422         {
423         log.debug("") ;
424         log.debug("----\"----") ;
425         log.debug("DatabaseConfiguration:resetDatabaseTables()") ;
426         //
427         // If we have a database script.
428         if (null != this.databaseScriptResource)
429             {
430             //
431             // Try executing the database script.
432             executeSQL(this.databaseScriptResource) ;
433             }
434 //
435 // PATCH - Force a reload of our JDO engine.
436 // Was put in to fix PasswordData problem.
437 // Didn't work.
438 //this.loadDatabaseEngine() ;
439         }
440 
441     /***
442      * Execute a SQL resource.
443      * Uses the current ClassLoader to find the specified resource.
444      * @param resource - The resource name of the SQL script.
445      *
446      * TODO - Look for a normal file as well as a resource.
447      */
448     public void executeSQL(String resource)
449         throws IOException, DatabaseNotFoundException, PersistenceException
450         {
451         log.debug("") ;
452         log.debug("----\"----") ;
453         log.debug("DatabaseConfiguration:executeSQL()") ;
454         log.debug("  SQL : '" + resource + "'") ;
455         //
456         // Check for null params.
457         if (null != resource)
458             {
459             //
460             // Try to locate our SQL resource.
461             ClassLoader loader = this.getClass().getClassLoader() ;
462             URL url = loader.getResource(resource) ;
463             //
464             // If we couldn't find the resource.
465             if (null == url)
466                 {
467                 //
468                 // Throw a FileNotFoundException.
469                 String message = "Failed to find SQL resource : '" + resource + "'" ;
470                 log.debug(message) ;
471                 throw new FileNotFoundException(message) ;
472                 }
473             //
474             // If we found the resource.
475             else {
476                 //
477                 // Execute the SQL.
478                 this.executeSQL(url) ;
479                 }
480             }
481         }
482 
483     /***
484      * Execute a SQL script.
485      * Wrapper method, openning an input stream to the URL.
486      * @param url - The URL of the SQL to execute.
487      *
488      */
489     public void executeSQL(URL url)
490         throws IOException, DatabaseNotFoundException, PersistenceException
491         {
492         //
493         // Check for null url.
494         if (null != url)
495             {
496             //
497             // Execute the SQL.
498             this.executeSQL(url.openStream()) ;
499             }
500         }
501 
502     /***
503      * Execute a SQL script.
504      * Wrapper method, creating an input stream reader for the stream.
505      * @param stream - An input stream for the SQL to execute.
506      *
507      */
508     public void executeSQL(InputStream stream)
509         throws IOException, DatabaseNotFoundException, PersistenceException
510         {
511         //
512         // Check for null stream.
513         if (null != stream)
514             {
515             //
516             // Execute the SQL.
517             this.executeSQL(new InputStreamReader(stream)) ;
518             }
519         }
520 
521     /***
522      * Execute a SQL script.
523      * Parses the SQL script, looking for end of line delimiter ';'.
524      * Sends each SQL statement to the database using the SQL pass-through 'CALL SQL'.
525      * This is experimental, only tested with Hsqldb and the Community database tables.
526      * @param reader - A reader for the SQL to execute.
527      *
528      */
529     public void executeSQL(Reader reader)
530         throws IOException, DatabaseNotFoundException, PersistenceException
531         {
532         //
533         // Check for a null reader.
534         if (null != reader)
535             {
536             //
537             // Create a buffer for our input reader.
538             BufferedReader source = new BufferedReader(reader) ;
539             //
540             // Try openning a connection to our database.
541             Database database = this.getDatabase() ;
542             //
543             // If we got a database connection.
544             if (null != database)
545                 {
546                 //
547                 // Try creating the database tables.
548                 try {
549                     //
550                     // Start our transaction.
551                     database.begin() ;
552                     //
553                     // Create our buffer.
554                     StringBuffer buffer = new StringBuffer() ;
555                     //
556                     // Read our source one line ate a time.
557                     String line = "" ;
558                     while ((line = source.readLine()) != null)
559                         {
560                         //
561                         // Skip blank lines.
562                         line = line.trim() ;
563                         if (line.length() == 0)
564                             {
565                             continue;
566                             }
567                         //
568                         // Skip lines that start with '//'
569                         if (line.startsWith("//"))
570                             {
571                             continue;
572                             }
573                         //
574                         // Skip lines that start with '--'
575                         if (line.startsWith("--"))
576                             {
577                             continue;
578                             }
579                         //
580                         // If we already have something in the buffer.
581                         if (buffer.length() > 0)
582                             {
583                             //
584                             // Add a newline character.
585                             buffer.append("\n") ;
586                             }
587                         //
588                         // Add the line to our buffer.
589                         buffer.append(line) ;
590                         //
591                         // If the line ends with a ';'
592                         if (line.endsWith(";"))
593                             {
594                             //
595                             // Remove the ';' from the end.
596                             int length = buffer.length() ;
597                             buffer.delete((length - 1), length) ;
598                             //
599                             // Add 'CALL SQL' to the start of our buffer.
600                             buffer.insert(0, "CALL SQL ") ;
601                             //
602                             // Add 'AS class' to the end of our buffer.
603                             // OQL rules require AS something, even if we don't get any results.
604                             buffer.append(" AS org.astrogrid.community.server.database.configuration.DatabaseConfigurationTestData") ;
605                             //
606                             // Try execute the SQL buffer.
607                             OQLQuery query = database.getOQLQuery(buffer.toString()) ;
608                         //
609                         // QueryResults results = query.execute();
610                             query.execute();
611                         //
612                         // Ignore the results for now ....
613                         //
614                             //
615                             // Reset our buffer
616                             buffer = new StringBuffer() ;
617                             }
618                         }
619                     //
620                     // Commit our database transaction.
621                     database.commit() ;
622                     }
623                 catch(PersistenceException ouch)
624                     {
625                     log.debug("Exception while executing SQL statement") ;
626                     log.debug("----") ;
627                     log.debug(ouch) ;
628                     log.debug("----") ;
629                     //
630                     // Rollback our database transaction.
631                     database.rollback() ;
632                     }
633                 finally
634                     {
635                     //
636                     // Close our database connection.
637                     database.close() ;
638 //
639 // Need to think about this bit ...
640 // Exception catching and throwing.
641 // org.exolab.castor.jdo.PersistenceException: Nested error: java.sql.SQLException: Unexpected token: FROG in statement [/*
642 //
643                     }
644                 }
645             }
646         }
647 
648     /**
649      * Check that our database is healthy.
650      * Opens a database connection and tries to read some test data.
651      * This relies on the database having the right tables and mapping for the test data.
652      *
653      * TODO This should use a specific class to check
654      * At the moment, it relies on having a DatabaseConfigurationTestData table in the database.
655      * TODO This should just get the first object, not all of them !!
656      *
657      */
658     public boolean checkDatabaseTables()
659         throws DatabaseNotFoundException, PersistenceException, QueryException
660         {
661         log.debug("") ;
662         log.debug("----\"----") ;
663         log.debug("DatabaseConfiguration:checkDatabaseTables()") ;
664         
665         //
666         // Start with healthy set to 'false', and set it to 'true' if we actually get some data.
667         boolean healthy = false ;
668         //
669         // Try openning a connection to our database.
670         Database database = this.getDatabase() ;
671         //
672         // Wrap everything in a try.
673         try {
674             //
675             // If we got a database connection.
676             if (null != database)
677                 {
678                 database.begin() ;
679                 //
680                 // Try to read the database test data.
681                 OQLQuery query = database.getOQLQuery(
682                     "SELECT testdata FROM org.astrogrid.community.server.database.configuration.DatabaseConfigurationTestData testdata"
683                     ) ;
684                 QueryResults results = query.execute();
685                 if (null != results)
686                     {
687                     if (results.hasMore())
688                         {
689                         while (results.hasMore())
690                             {
691                             Object result = results.next() ;
692                             if (result instanceof DatabaseConfigurationTestData)
693                                 {
694                                 log.debug("  PASS : got test data '" + result + "'") ;
695                                 healthy = true ;
696                                 }
697                             else {
698                                 log.debug("  FAIL : unknown result type '" + result.getClass() + "'") ;
699                                 healthy = false ;
700                                 }
701                             }
702                         }
703                     else {
704                         log.debug("  FAIL : empty results") ;
705                         healthy = false ;
706                         }
707                     }
708                 else {
709                     log.debug("  FAIL : null results") ;
710                     healthy = false ;
711                     }
712                 database.commit() ;
713                 database.close() ;
714                 }
715             //
716             // We don't have a database connecton.
717             else {
718                 healthy = false ;
719                 }
720             }
721         //
722         // Catch anything that goes bang.
723         catch (PersistenceException ouch)
724             {
725             log.debug("Exception caught in checkDatabaseTables()") ;
726             log.debug("  Exception : " + ouch) ;
727             healthy = false ;
728             }
729         return healthy ;
730         }
731     }
732