1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
138 this.setDatabaseName(name) ;
139
140
141 this.setDatabaseConfigResource(xml) ;
142
143
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
157 if (null != this.databaseEngine)
158 {
159 return this.databaseEngine.getDatabase() ;
160 }
161
162
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
194 this.databaseName = name ;
195
196
197 if (null != this.databaseName)
198 {
199
200
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
234 this.databaseConfigResource = resource ;
235
236
237 if (null == this.databaseConfigResource)
238 {
239
240
241 this.setDatabaseConfigUrl(null) ;
242 }
243
244
245 else {
246
247
248 ClassLoader loader = this.getClass().getClassLoader() ;
249 URL url = loader.getResource(this.databaseConfigResource) ;
250
251
252 if (null == url)
253 {
254
255
256 this.setDatabaseConfigUrl(null) ;
257
258
259 String message = "Failed to find database config : '" + this.databaseConfigResource + "'" ;
260 throw new FileNotFoundException(message) ;
261 }
262
263
264 else {
265
266
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
298 this.databaseConfigUrl = url ;
299
300
301 if (null != this.databaseConfigUrl)
302 {
303
304
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
318 if (null != this.databaseConfigUrl)
319 {
320
321
322 JDO.loadConfiguration(this.databaseConfigUrl.toString()) ;
323
324
325 if (null != this.databaseEngine)
326 {
327
328
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
363 if (null != this.databaseName)
364 {
365
366
367 this.databaseEngine = new JDO(this.databaseName) ;
368
369
370 if (null == this.databaseEngine)
371 {
372 String message = "Failed to create JDO engine : '" + databaseName + "'" ;
373 throw new FileNotFoundException(message) ;
374 }
375
376
377 if (null != this.databaseConfigUrl)
378 {
379
380
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
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
428 if (null != this.databaseScriptResource)
429 {
430
431
432 executeSQL(this.databaseScriptResource) ;
433 }
434
435
436
437
438
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
457 if (null != resource)
458 {
459
460
461 ClassLoader loader = this.getClass().getClassLoader() ;
462 URL url = loader.getResource(resource) ;
463
464
465 if (null == url)
466 {
467
468
469 String message = "Failed to find SQL resource : '" + resource + "'" ;
470 log.debug(message) ;
471 throw new FileNotFoundException(message) ;
472 }
473
474
475 else {
476
477
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
494 if (null != url)
495 {
496
497
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
513 if (null != stream)
514 {
515
516
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
534 if (null != reader)
535 {
536
537
538 BufferedReader source = new BufferedReader(reader) ;
539
540
541 Database database = this.getDatabase() ;
542
543
544 if (null != database)
545 {
546
547
548 try {
549
550
551 database.begin() ;
552
553
554 StringBuffer buffer = new StringBuffer() ;
555
556
557 String line = "" ;
558 while ((line = source.readLine()) != null)
559 {
560
561
562 line = line.trim() ;
563 if (line.length() == 0)
564 {
565 continue;
566 }
567
568
569 if (line.startsWith("//"))
570 {
571 continue;
572 }
573
574
575 if (line.startsWith("--"))
576 {
577 continue;
578 }
579
580
581 if (buffer.length() > 0)
582 {
583
584
585 buffer.append("\n") ;
586 }
587
588
589 buffer.append(line) ;
590
591
592 if (line.endsWith(";"))
593 {
594
595
596 int length = buffer.length() ;
597 buffer.delete((length - 1), length) ;
598
599
600 buffer.insert(0, "CALL SQL ") ;
601
602
603
604 buffer.append(" AS org.astrogrid.community.server.database.configuration.DatabaseConfigurationTestData") ;
605
606
607 OQLQuery query = database.getOQLQuery(buffer.toString()) ;
608
609
610 query.execute();
611
612
613
614
615
616 buffer = new StringBuffer() ;
617 }
618 }
619
620
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
631 database.rollback() ;
632 }
633 finally
634 {
635
636
637 database.close() ;
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
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
667 boolean healthy = false ;
668
669
670 Database database = this.getDatabase() ;
671
672
673 try {
674
675
676 if (null != database)
677 {
678 database.begin() ;
679
680
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
717 else {
718 healthy = false ;
719 }
720 }
721
722
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