View Javadoc

1   package org.apache.ojb.broker.util.dbhandling;
2   
3   /* Copyright 2004-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.io.*;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.StringTokenizer;
22  import java.util.zip.GZIPInputStream;
23  import java.util.zip.GZIPOutputStream;
24  
25  import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
26  import org.apache.ojb.broker.platforms.PlatformException;
27  import org.apache.ojb.broker.util.logging.LoggerFactory;
28  import org.apache.tools.ant.Project;
29  import org.apache.tools.ant.taskdefs.SQLExec;
30  import org.apache.tools.ant.types.FileSet;
31  import org.apache.torque.task.TorqueDataModelTask;
32  import org.apache.torque.task.TorqueSQLExec;
33  import org.apache.torque.task.TorqueSQLTask;
34  
35  /**
36   * Provides basic database handling (drop, create, init) via torque.
37   * 
38   * @author Thomas Dudziak
39   */
40  public class TorqueDBHandling implements DBHandling
41  {
42      /** Torque db platforms */
43      protected static final String TORQUE_PLATFORM_DB2        = "db2";
44      protected static final String TORQUE_PLATFORM_HYPERSONIC = "hypersonic";
45      protected static final String TORQUE_PLATFORM_INTERBASE  = "interbase";
46      protected static final String TORQUE_PLATFORM_MSSQL      = "mssql";
47      protected static final String TORQUE_PLATFORM_MYSQL      = "mysql";
48      protected static final String TORQUE_PLATFORM_ORACLE     = "oracle";
49      protected static final String TORQUE_PLATFORM_POSTGRESQL = "postgresql";
50      protected static final String TORQUE_PLATFORM_SAPDB      = "sapdb";
51      protected static final String TORQUE_PLATFORM_SYBASE     = "sybase";
52  
53      /** The name of the db-creation script */
54      private static final String CREATION_SCRIPT_NAME = "create-db.sql";
55      /** The name of the torque database mapping file */
56      private static final String SQL_DB_MAP_NAME      = "sqldb.map";
57  
58      /** Mapping from ojb dbms to torque database setting */
59      private static HashMap _dbmsToTorqueDb = new HashMap();
60      
61      static
62      {
63          _dbmsToTorqueDb.put("db2",         TORQUE_PLATFORM_DB2);
64          _dbmsToTorqueDb.put("hsqldb",      TORQUE_PLATFORM_HYPERSONIC);
65          _dbmsToTorqueDb.put("firebird",    TORQUE_PLATFORM_INTERBASE);
66          _dbmsToTorqueDb.put("mssqlserver", TORQUE_PLATFORM_MSSQL);
67          _dbmsToTorqueDb.put("mysql",       TORQUE_PLATFORM_MYSQL);
68          _dbmsToTorqueDb.put("oracle",      TORQUE_PLATFORM_ORACLE);
69          _dbmsToTorqueDb.put("oracle9i",    TORQUE_PLATFORM_ORACLE);
70          _dbmsToTorqueDb.put("postgresql",  TORQUE_PLATFORM_POSTGRESQL);
71          _dbmsToTorqueDb.put("sapdb",       TORQUE_PLATFORM_SAPDB);
72          _dbmsToTorqueDb.put("sybaseasa",   TORQUE_PLATFORM_SYBASE);
73          _dbmsToTorqueDb.put("sybasease",   TORQUE_PLATFORM_SYBASE);
74          _dbmsToTorqueDb.put("sybase",      TORQUE_PLATFORM_SYBASE);
75      }
76      
77      /** The jdbc connection for communicating with the db */
78      private JdbcConnectionDescriptor _jcd;
79      /** The target database */
80      private String _targetDatabase;
81      /** The directory where we work in */
82      private File _workDir;
83      /** The compressed contents of the torque schemata */
84      private HashMap _torqueSchemata = new HashMap();
85      /** The compressed content of the creation script */
86      private byte[] _creationScript;
87      /** The compressed contents of the db initialization scripts */
88      private HashMap _initScripts = new HashMap();
89  
90      /**
91       * Creates a new handling object.
92       */
93      public TorqueDBHandling()
94      {}
95  
96      /**
97       * Sets the jdbc connection to use.
98       * 
99       * @param jcd The connection to use
100      * @throws PlatformException If the target database cannot be handled with torque
101      */
102     public void setConnection(JdbcConnectionDescriptor jcd) throws PlatformException
103     {
104         _jcd = jcd;
105 
106         String targetDatabase = (String)_dbmsToTorqueDb.get(_jcd.getDbms().toLowerCase());
107 
108         if (targetDatabase == null)
109         {
110             throw new PlatformException("Database "+_jcd.getDbms()+" is not supported by torque");
111         }
112         if (!targetDatabase.equals(_targetDatabase))
113         {
114             _targetDatabase = targetDatabase;
115             _creationScript = null;
116             _initScripts.clear();
117         }
118     }
119 
120     /**
121      * Returns the connection descriptor used by this handling object.
122      * 
123      * @return The connection descriptor
124      */
125     public JdbcConnectionDescriptor getConnection()
126     {
127         return _jcd;
128     }
129 
130     /**
131      * Returns the torque database platform used.
132      * 
133      * @return The target db platform
134      */
135     public String getTargetTorquePlatform()
136     {
137         return _targetDatabase;
138     }
139 
140     /**
141      * Adds the input files (in our case torque schema files) to use.
142      * 
143      * @param srcDir          The directory containing the files
144      * @param listOfFilenames The filenames in a comma-separated list
145      */
146     public void addDBDefinitionFiles(String srcDir, String listOfFilenames) throws IOException
147     {
148         StringTokenizer tokenizer = new StringTokenizer(listOfFilenames, ",");
149         File            dir       = new File(srcDir);
150         String          filename;
151         
152         while (tokenizer.hasMoreTokens())
153         {
154             filename = tokenizer.nextToken().trim();
155             if (filename.length() > 0)
156             {    
157                  _torqueSchemata.put("schema"+_torqueSchemata.size()+".xml",
158                                      readTextCompressed(new File(dir, filename)));
159             }
160         }
161     }
162 
163     /**
164      * Adds an input stream of a db definition (in our case of a torque schema file).
165      * 
166      * @param schemaStream The input stream
167      */
168     public void addDBDefinitionFile(InputStream schemaStream) throws IOException
169     {
170         _torqueSchemata.put("schema"+_torqueSchemata.size()+".xml",
171                             readStreamCompressed(schemaStream));
172     }
173 
174     /**
175      * Writes the torque schemata to files in the given directory and returns
176      * a comma-separated list of the filenames.
177      * 
178      * @param dir The directory to write the files to
179      * @return The list of filenames
180      * @throws IOException If an error occurred
181      */
182     private String writeSchemata(File dir) throws IOException
183     {
184         writeCompressedTexts(dir, _torqueSchemata);
185 
186         StringBuffer includes = new StringBuffer();
187 
188         for (Iterator it = _torqueSchemata.keySet().iterator(); it.hasNext();)
189         {
190             includes.append((String)it.next());
191             if (it.hasNext())
192             {
193                 includes.append(",");
194             }
195         }
196         return includes.toString();
197     }
198     
199     /**
200      * Creates the db-creation sql script (but does not perform it).
201      * 
202      * @throws PlatformException If some error occurred
203      */
204     public void createCreationScript() throws PlatformException
205     {
206         Project             project    = new Project();
207         TorqueDataModelTask modelTask  = new TorqueDataModelTask();
208         File                tmpDir     = null;
209         File                scriptFile = null;
210         
211         _creationScript = null;
212         try
213         {
214             tmpDir = new File(getWorkDir(), "schemas");
215             tmpDir.mkdir();
216 
217             String includes = writeSchemata(tmpDir);
218             
219             scriptFile = new File(tmpDir, CREATION_SCRIPT_NAME);
220 
221             project.setBasedir(tmpDir.getAbsolutePath());
222 
223             // populating with defaults
224             modelTask.setProject(project);
225             modelTask.setUseClasspath(true);
226             modelTask.setControlTemplate("sql/db-init/Control.vm");
227             modelTask.setOutputDirectory(tmpDir);
228             modelTask.setOutputFile(CREATION_SCRIPT_NAME);
229             modelTask.setTargetDatabase(_targetDatabase);
230 
231             FileSet files = new FileSet();
232 
233             files.setDir(tmpDir);
234             files.setIncludes(includes);
235             modelTask.addFileset(files);
236             modelTask.execute();
237 
238             _creationScript = readTextCompressed(scriptFile);
239 
240             deleteDir(tmpDir);
241         }
242         catch (Exception ex)
243         {
244             // clean-up
245             if ((tmpDir != null) && tmpDir.exists())
246             {
247                 deleteDir(tmpDir);
248             }
249             throw new PlatformException(ex);
250         }
251     }
252     
253     /**
254      * Creates the database.
255      * 
256      * @throws PlatformException If some error occurred
257      */
258     public void createDB() throws PlatformException
259     {
260         if (_creationScript == null)
261         {
262             createCreationScript();
263         }
264 
265         Project             project    = new Project();
266         TorqueDataModelTask modelTask  = new TorqueDataModelTask();
267         File                tmpDir     = null;
268         File                scriptFile = null;
269         
270         try
271         {
272             tmpDir = new File(getWorkDir(), "schemas");
273             tmpDir.mkdir();
274 
275             scriptFile = new File(tmpDir, CREATION_SCRIPT_NAME);
276 
277             writeCompressedText(scriptFile, _creationScript);
278 
279             project.setBasedir(tmpDir.getAbsolutePath());
280 
281             // we use the ant task 'sql' to perform the creation script
282 	        SQLExec         sqlTask = new SQLExec();
283 	        SQLExec.OnError onError = new SQLExec.OnError();
284 	
285 	        onError.setValue("continue");
286 	        sqlTask.setProject(project);
287 	        sqlTask.setAutocommit(true);
288 	        sqlTask.setDriver(_jcd.getDriver());
289 	        sqlTask.setOnerror(onError);
290 	        sqlTask.setUserid(_jcd.getUserName());
291 	        sqlTask.setPassword(_jcd.getPassWord() == null ? "" : _jcd.getPassWord());
292 	        sqlTask.setUrl(getDBCreationUrl());
293 	        sqlTask.setSrc(scriptFile);
294 	        sqlTask.execute();
295 
296 	        deleteDir(tmpDir);
297         }
298         catch (Exception ex)
299         {
300             // clean-up
301             if ((tmpDir != null) && tmpDir.exists())
302             {
303                 try
304                 {
305                     scriptFile.delete();
306                 }
307                 catch (NullPointerException e) 
308                 {
309                     LoggerFactory.getLogger(this.getClass()).error("NPE While deleting scriptFile [" + scriptFile.getName() + "]", e);
310                 }
311             }
312             throw new PlatformException(ex);
313         }
314     }
315 
316     /**
317      * Creates the initialization scripts (creation of tables etc.) but does
318      * not perform them.
319      * 
320      * @throws PlatformException If some error occurred
321      */
322     public void createInitScripts() throws PlatformException
323     {
324         Project       project   = new Project();
325         TorqueSQLTask sqlTask   = new TorqueSQLTask(); 
326         File          schemaDir = null;
327         File          sqlDir    = null;
328         
329         _initScripts.clear();
330         try
331         {
332             File tmpDir = getWorkDir();
333 
334             schemaDir = new File(tmpDir, "schemas");
335             sqlDir    = new File(tmpDir, "sql");
336             schemaDir.mkdir();
337             sqlDir.mkdir();
338 
339             String includes     = writeSchemata(schemaDir);
340             File   sqlDbMapFile = new File(sqlDir, SQL_DB_MAP_NAME);
341 
342             sqlDbMapFile.createNewFile();
343             project.setBasedir(sqlDir.getAbsolutePath());
344             
345             // populating with defaults
346             sqlTask.setProject(project);
347             sqlTask.setUseClasspath(true);
348             sqlTask.setBasePathToDbProps("sql/base/");
349             sqlTask.setControlTemplate("sql/base/Control.vm");
350             sqlTask.setOutputDirectory(sqlDir);
351             // we put the report in the parent directory as we don't want
352             // to read it in later on
353             sqlTask.setOutputFile("../report.sql.generation");
354             sqlTask.setSqlDbMap(SQL_DB_MAP_NAME);
355             sqlTask.setTargetDatabase(_targetDatabase);
356 
357             FileSet files = new FileSet();
358             
359             files.setDir(schemaDir);
360             files.setIncludes(includes);
361             sqlTask.addFileset(files);
362             sqlTask.execute();
363 
364             readTextsCompressed(sqlDir, _initScripts);
365             deleteDir(schemaDir);
366             deleteDir(sqlDir);
367         }
368         catch (Exception ex)
369         {
370             // clean-up
371             if ((schemaDir != null) && schemaDir.exists())
372             {
373                 deleteDir(schemaDir);
374             }
375             if ((sqlDir != null) && sqlDir.exists())
376             {
377                 deleteDir(sqlDir);
378             }
379             throw new PlatformException(ex);
380         }
381     }
382 
383     /**
384      * Creates the tables according to the schema files.
385      * 
386      * @throws PlatformException If some error occurred
387      */
388     public void initDB() throws PlatformException
389     {
390         if (_initScripts.isEmpty())
391         {
392             createInitScripts();
393         }
394 
395         Project       project   = new Project();
396         TorqueSQLTask sqlTask   = new TorqueSQLTask(); 
397         File          outputDir = null;
398         
399         try
400         {
401             outputDir = new File(getWorkDir(), "sql");
402 
403             outputDir.mkdir();
404             writeCompressedTexts(outputDir, _initScripts);
405 
406             project.setBasedir(outputDir.getAbsolutePath());
407 
408             // executing the generated sql, but this time with a torque task 
409             TorqueSQLExec         sqlExec = new TorqueSQLExec();
410             TorqueSQLExec.OnError onError = new TorqueSQLExec.OnError();
411 
412             sqlExec.setProject(project);
413             onError.setValue("continue");
414             sqlExec.setAutocommit(true);
415             sqlExec.setDriver(_jcd.getDriver());
416             sqlExec.setOnerror(onError);
417             sqlExec.setUserid(_jcd.getUserName());
418             sqlExec.setPassword(_jcd.getPassWord() == null ? "" : _jcd.getPassWord());
419             sqlExec.setUrl(getDBManipulationUrl());
420             sqlExec.setSrcDir(outputDir.getAbsolutePath());
421             sqlExec.setSqlDbMap(SQL_DB_MAP_NAME);
422             sqlExec.execute();
423             
424             deleteDir(outputDir);
425         }
426         catch (Exception ex)
427         {
428             // clean-up
429             if (outputDir != null)
430             {
431                 deleteDir(outputDir);
432             }
433             throw new PlatformException(ex);
434         }
435     }
436 
437     /**
438      * Template-and-Hook method for generating the url required by the jdbc driver
439      * to allow for creating a database (as opposed to accessing an already-existing
440      * database).
441      *
442      */
443     protected String getDBCreationUrl()
444     {
445         JdbcConnectionDescriptor jcd = getConnection();
446 
447         // currently I only know about specifics for mysql
448         if (TORQUE_PLATFORM_MYSQL.equals(getTargetTorquePlatform()))
449         {
450             // we have to remove the db name as the jdbc driver would try to connect to
451             // a non-existing db
452             // the db-alias has this form: [host&port]/[dbname]?[options]
453             String dbAliasPrefix = jcd.getDbAlias();
454             String dbAliasSuffix = "";
455             int    questionPos   = dbAliasPrefix.indexOf('?');
456 
457             if (questionPos > 0)
458             {
459                 dbAliasSuffix = dbAliasPrefix.substring(questionPos);
460                 dbAliasPrefix = dbAliasPrefix.substring(0, questionPos);
461             }
462 
463             int slashPos = dbAliasPrefix.lastIndexOf('/');
464 
465             if (slashPos > 0)
466             {
467                 // it is important that the slash at the end is present
468                 dbAliasPrefix = dbAliasPrefix.substring(0, slashPos + 1);
469             }
470             return jcd.getProtocol()+":"+jcd.getSubProtocol()+":"+dbAliasPrefix+dbAliasSuffix;
471         }
472         else if (TORQUE_PLATFORM_POSTGRESQL.equals(getTargetTorquePlatform()))
473         {
474             // we have to replace the db name with 'template1'
475             // the db-alias has this form: [host&port]/[dbname]?[options]
476             String dbAliasPrefix = jcd.getDbAlias();
477             String dbAliasSuffix = "";
478             int    questionPos   = dbAliasPrefix.indexOf('?');
479 
480             if (questionPos > 0)
481             {
482                 dbAliasSuffix = dbAliasPrefix.substring(questionPos);
483                 dbAliasPrefix = dbAliasPrefix.substring(0, questionPos);
484             }
485 
486             int slashPos = dbAliasPrefix.lastIndexOf('/');
487 
488             if (slashPos > 0)
489             {
490                 // it is important that the slash at the end is present
491                 dbAliasPrefix = dbAliasPrefix.substring(0, slashPos + 1);
492             }
493             else
494             {
495                 dbAliasPrefix += "/";
496             }
497             dbAliasPrefix += "template1";
498             if (dbAliasSuffix.length() > 0)
499             {
500                 dbAliasPrefix += "/";
501             }
502             return jcd.getProtocol()+":"+jcd.getSubProtocol()+":"+dbAliasPrefix+dbAliasSuffix;
503             
504         }
505         else
506         {
507             return jcd.getProtocol()+":"+jcd.getSubProtocol()+":"+jcd.getDbAlias();
508         }
509     }
510 
511     /**
512      * Template-and-Hook method for generating the url required by the jdbc driver
513      * to allow for modifying an existing database.
514      *
515      */
516     protected String getDBManipulationUrl()
517     {
518         JdbcConnectionDescriptor jcd = getConnection();
519 
520         return jcd.getProtocol()+":"+jcd.getSubProtocol()+":"+jcd.getDbAlias();
521     }
522 
523     /**
524      * Reads the given text file and compressed its content.
525      * 
526      * @param file The file
527      * @return A byte array containing the GZIP-compressed content of the file
528      * @throws IOException If an error ocurred
529      */
530     private byte[] readTextCompressed(File file) throws IOException
531     {
532         return readStreamCompressed(new FileInputStream(file));
533     }
534 
535     /**
536      * Reads the given text stream and compressed its content.
537      * 
538      * @param stream The input stream
539      * @return A byte array containing the GZIP-compressed content of the stream
540      * @throws IOException If an error ocurred
541      */
542     private byte[] readStreamCompressed(InputStream stream) throws IOException
543     {
544         ByteArrayOutputStream bao    = new ByteArrayOutputStream();
545         GZIPOutputStream      gos    = new GZIPOutputStream(bao);
546         OutputStreamWriter    output = new OutputStreamWriter(gos);
547         BufferedReader        input  = new BufferedReader(new InputStreamReader(stream));
548         String                line;
549 
550         while ((line = input.readLine()) != null)
551         {
552             output.write(line);
553             output.write('\n');
554         }
555         input.close();
556         stream.close();
557         output.close();
558         gos.close();
559         bao.close();
560         return bao.toByteArray();
561     }
562 
563     /**
564      * Reads the text files in the given directory and puts their content
565      * in the given map after compressing it. Note that this method does not
566      * traverse recursivly into sub-directories.
567      * 
568      * @param dir     The directory to process
569      * @param results Map that will receive the contents (indexed by the relative filenames)
570      * @throws IOException If an error ocurred
571      */
572     private void readTextsCompressed(File dir, HashMap results) throws IOException
573     {
574         if (dir.exists() && dir.isDirectory())
575         {
576             File[] files = dir.listFiles();
577 
578             for (int idx = 0; idx < files.length; idx++)
579             {
580                 if (files[idx].isDirectory())
581                 {
582                     continue;
583                 }
584                 results.put(files[idx].getName(), readTextCompressed(files[idx]));
585             }
586         }
587     }
588 
589     /**
590      * Uncompresses the given textual content and writes it to the given file.
591      * 
592      * @param file              The file to write to
593      * @param compressedContent The content
594      * @throws IOException If an error occurred
595      */
596     private void writeCompressedText(File file, byte[] compressedContent) throws IOException
597     {
598         ByteArrayInputStream bais   = new ByteArrayInputStream(compressedContent);
599         GZIPInputStream      gis    = new GZIPInputStream(bais);
600         BufferedReader       input  = new BufferedReader(new InputStreamReader(gis));
601         BufferedWriter       output = new BufferedWriter(new FileWriter(file));
602         String               line;
603 
604         while ((line = input.readLine()) != null)
605         {
606             output.write(line);
607             output.write('\n');
608         }
609         input.close();
610         gis.close();
611         bais.close();
612         output.close();
613     }
614     
615     /**
616      * Uncompresses the textual contents in the given map and and writes them to the files
617      * denoted by the keys of the map.
618      * 
619      * @param dir      The base directory into which the files will be written 
620      * @param contents The map containing the contents indexed by the filename
621      * @throws IOException If an error occurred
622      */
623     private void writeCompressedTexts(File dir, HashMap contents) throws IOException
624     {
625         String filename;
626 
627         for (Iterator nameIt = contents.keySet().iterator(); nameIt.hasNext();)
628         {
629             filename = (String)nameIt.next();
630             writeCompressedText(new File(dir, filename), (byte[])contents.get(filename));
631         }
632     }
633     
634     /**
635      * Sets the working directory.
636      * 
637      * @param dir The directory
638      * @throws IOException If the directory does not exist or cannot be written/read
639      */
640     public void setWorkDir(String dir) throws IOException
641     {
642         File workDir = new File(dir);
643 
644         if (!workDir.exists() || !workDir.canWrite() || !workDir.canRead())
645         {
646             throw new IOException("Cannot access directory "+dir);
647         }
648         _workDir = workDir;
649     }
650 
651     /**
652      * Returns the temporary directory used by java.
653      * 
654      * @return The temporary directory
655      * @throws IOException If an io error occurred
656      */
657     private File getWorkDir() throws IOException
658     {
659         if (_workDir == null)
660         {    
661             File   dummy   = File.createTempFile("dummy", ".log");
662             String workDir = dummy.getPath().substring(0, dummy.getPath().lastIndexOf(File.separatorChar));
663     
664             if ((workDir == null) || (workDir.length() == 0))
665             {
666                 workDir = ".";
667             }
668             dummy.delete();
669             _workDir = new File(workDir);
670         }
671         return _workDir;
672     }
673 
674     /**
675      * Little helper function that recursivly deletes a directory.
676      * 
677      * @param dir The directory
678      */
679     private void deleteDir(File dir)
680     {
681         if (dir.exists() && dir.isDirectory())
682         {
683             File[] files = dir.listFiles();
684 
685             for (int idx = 0; idx < files.length; idx++)
686             {
687                 if (!files[idx].exists())
688                 {
689                     continue;
690                 }
691                 if (files[idx].isDirectory())
692                 {
693                     deleteDir(files[idx]);
694                 }
695                 else
696                 {
697                     files[idx].delete();
698                 }
699             }
700             dir.delete();
701         }
702     }
703 }