a37176cc78771eaf3fbbde6ca0f3c4eeef4fb254
[hibernate4-maven-plugin] / src / main / java / de / juplo / plugins / hibernate4 / Hbm2DdlMojo.java
1 package de.juplo.plugins.hibernate4;
2
3 /*
4  * Copyright 2001-2005 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 import com.pyx4j.log4j.MavenLogAppender;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.math.BigInteger;
29 import java.net.URL;
30 import java.net.URLClassLoader;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.sql.Driver;
34 import java.sql.DriverPropertyInfo;
35 import java.sql.SQLException;
36 import java.sql.SQLFeatureNotSupportedException;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.Properties;
44 import java.util.Set;
45 import java.util.TreeSet;
46 import java.util.logging.Logger;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49 import javax.persistence.Embeddable;
50 import javax.persistence.Entity;
51 import javax.persistence.MappedSuperclass;
52 import org.apache.maven.artifact.Artifact;
53 import org.apache.maven.model.Resource;
54 import org.apache.maven.plugin.AbstractMojo;
55 import org.apache.maven.plugin.MojoExecutionException;
56 import org.apache.maven.plugin.MojoFailureException;
57 import org.apache.maven.project.MavenProject;
58 import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
59 import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl;
60 import org.hibernate.cfg.Environment;
61 import org.hibernate.cfg.NamingStrategy;
62 import org.hibernate.envers.configuration.spi.AuditConfiguration;
63 import org.hibernate.internal.util.config.ConfigurationHelper;
64 import org.hibernate.tool.hbm2ddl.SchemaExport;
65 import org.hibernate.tool.hbm2ddl.SchemaExport.Type;
66 import org.hibernate.tool.hbm2ddl.Target;
67 import org.scannotation.AnnotationDB;
68
69
70 /**
71  * Goal which extracts the hibernate-mapping-configuration and
72  * exports an according SQL-database-schema.
73  *
74  * @goal export
75  * @phase process-classes
76  * @threadSafe
77  * @requiresDependencyResolution runtime
78  */
79 public class Hbm2DdlMojo extends AbstractMojo
80 {
81   public final static String EXPORT_SKIPPED_PROPERTY = "hibernate.export.skipped";
82
83   public final static String DRIVER_CLASS = "hibernate.connection.driver_class";
84   public final static String URL = "hibernate.connection.url";
85   public final static String USERNAME = "hibernate.connection.username";
86   public final static String PASSWORD = "hibernate.connection.password";
87   public final static String DIALECT = "hibernate.dialect";
88   public final static String NAMING_STRATEGY="hibernate.ejb.naming_strategy";
89   public final static String ENVERS = "hibernate.export.envers";
90
91   public final static String MD5S = "hibernate4-generatedschema.md5s";
92
93   private final static Pattern split = Pattern.compile("[^,\\s]+");
94
95
96   /**
97    * The maven project.
98    * <p>
99    * Only needed internally.
100    *
101    * @parameter property="project"
102    * @required
103    * @readonly
104    */
105   private MavenProject project;
106
107   /**
108    * Build-directory.
109    * <p>
110    * Only needed internally.
111    *
112    * @parameter property="project.build.directory"
113    * @required
114    * @readonly
115    */
116   private String buildDirectory;
117
118   /**
119    * Classes-Directory to scan.
120    * <p>
121    * This parameter defaults to the maven build-output-directory for classes.
122    * Additionally, all dependencies are scanned for annotated classes.
123    *
124    * @parameter property="project.build.outputDirectory"
125    * @since 1.0
126    */
127   private String outputDirectory;
128
129   /**
130    * Whether to scan test-classes too, or not.
131    * <p>
132    * If this parameter is set to <code>true</code> the test-classes of the
133    * artifact will be scanned for hibernate-annotated classes additionally.
134    *
135    * @parameter property="hibernate.export.scan_testclasses" default-value="false"
136    * @since 1.0.1
137    */
138   private boolean scanTestClasses;
139
140   /**
141    * Dependency-Scopes, that should be scanned for annotated classes.
142    * <p>
143    * By default, only dependencies in the scope <code>compile</code> are
144    * scanned for annotated classes. Multiple scopes can be seperated by
145    * white space or commas.
146    * <p>
147    * If you do not want any dependencies to be scanned for annotated
148    * classes, set this parameter to <code>none</code>.
149    * <p>
150    * The plugin does not scan for annotated classes in transitive
151    * dependencies. If some of your annotated classes are hidden in a
152    * transitive dependency, you can simply add that dependency explicitly.
153    *
154    * @parameter property="hibernate.export.scan_dependencies" default-value="compile"
155    * @since 1.0.3
156    */
157   private String scanDependencies;
158
159   /**
160    * Test-Classes-Directory to scan.
161    * <p>
162    * This parameter defaults to the maven build-output-directory for
163    * test-classes.
164    * <p>
165    * This parameter is only used, when <code>scanTestClasses</code> is set
166    * to <code>true</code>!
167    *
168    * @parameter property="project.build.testOutputDirectory"
169    * @since 1.0.2
170    */
171   private String testOutputDirectory;
172
173   /**
174    * Skip execution
175    * <p>
176    * If set to <code>true</code>, the execution is skipped.
177    * <p>
178    * A skipped execution is signaled via the maven-property
179    * <code>${hibernate.export.skipped}</code>.
180    * <p>
181    * The execution is skipped automatically, if no modified or newly added
182    * annotated classes are found and the dialect was not changed.
183    *
184    * @parameter property="hibernate.skip" default-value="${maven.test.skip}"
185    * @since 1.0
186    */
187   private boolean skip;
188
189   /**
190    * Force execution
191    * <p>
192    * Force execution, even if no modified or newly added annotated classes
193    * where found and the dialect was not changed.
194    * <p>
195    * <code>skip</code> takes precedence over <code>force</code>.
196    *
197    * @parameter property="hibernate.export.force" default-value="false"
198    * @since 1.0
199    */
200   private boolean force;
201
202   /**
203    * SQL-Driver name.
204    *
205    * @parameter property="hibernate.connection.driver_class"
206    * @since 1.0
207    */
208   private String driverClassName;
209
210   /**
211    * Database URL.
212    *
213    * @parameter property="hibernate.connection.url"
214    * @since 1.0
215    */
216   private String url;
217
218   /**
219    * Database username
220    *
221    * @parameter property="hibernate.connection.username"
222    * @since 1.0
223    */
224   private String username;
225
226   /**
227    * Database password
228    *
229    * @parameter property="hibernate.connection.password"
230    * @since 1.0
231    */
232   private String password;
233
234   /**
235    * Hibernate dialect.
236    *
237    * @parameter property="hibernate.dialect"
238    * @since 1.0
239    */
240   private String hibernateDialect;
241
242   /**
243    * Hibernate Naming Strategy
244    *
245    * @parameter property="hibernate.ejb.naming_strategy"
246    * @since 1.0.2
247    */
248   private String hibernateNamingStrategy;
249
250   /**
251    * Path to Hibernate configuration file.
252    *
253    * @parameter default-value="${project.build.outputDirectory}/hibernate.properties"
254    * @since 1.0
255    */
256   private String hibernateProperties;
257
258   /**
259    * List of Hibernate-Mapping-Files (XML).
260    * Multiple files can be separated with white-spaces and/or commas.
261    *
262    * @parameter property="hibernate.mapping"
263    * @since 1.0.2
264    */
265   private String hibernateMapping;
266
267   /**
268    * Target of execution:
269    * <ul>
270    *   <li><strong>NONE</strong> only export schema to SQL-script (forces execution, signals skip)</li>
271    *   <li><strong>EXPORT</strong> create database (<strong>DEFAULT!</strong>). forces execution, signals skip)</li>
272    *   <li><strong>SCRIPT</strong> export schema to SQL-script and print it to STDOUT</li>
273    *   <li><strong>BOTH</strong></li>
274    * </ul>
275    *
276    * A database connection is only needed for EXPORT and BOTH, but a
277    * Hibernate-Dialect must always be chosen.
278    *
279    * @parameter property="hibernate.export.target" default-value="EXPORT"
280    * @since 1.0
281    */
282   private String target;
283
284   /**
285    * Type of execution.
286    * <ul>
287    *   <li><strong>NONE</strong> do nothing - just validate the configuration</li>
288    *   <li><strong>CREATE</strong> create database-schema</li>
289    *   <li><strong>DROP</strong> drop database-schema</li>
290    *   <li><strong>BOTH</strong> (<strong>DEFAULT!</strong>)</li>
291    * </ul>
292    *
293    * If NONE is choosen, no databaseconnection is needed.
294    *
295    * @parameter property="hibernate.export.type" default-value="BOTH"
296    * @since 1.0
297    */
298   private String type;
299
300   /**
301    * Output file.
302    *
303    * @parameter property="hibernate.export.schema.filename" default-value="${project.build.directory}/schema.sql"
304    * @since 1.0
305    */
306   private String outputFile;
307
308   /**
309    * Delimiter in output-file.
310    *
311    * @parameter property="hibernate.export.schema.delimiter" default-value=";"
312    * @since 1.0
313    */
314   private String delimiter;
315
316   /**
317    * Format output-file.
318    *
319    * @parameter property="hibernate.export.schema.format" default-value="true"
320    * @since 1.0
321    */
322   private boolean format;
323
324   /**
325    * Generate envers schema for auditing tables.
326    *
327    * @parameter property="hibernate.export.envers" default-value="false"
328    * @since 1.0.3
329    */
330   private boolean envers;
331
332
333   @Override
334   public void execute()
335     throws
336       MojoFailureException,
337       MojoExecutionException
338   {
339     if (skip)
340     {
341       getLog().info("Execution of hibernate4-maven-plugin:export was skipped!");
342       project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
343       return;
344     }
345
346     Map<String,String> md5s;
347     boolean modified = false;
348     File saved = new File(buildDirectory + File.separator + MD5S);
349
350     if (saved.isFile() && saved.length() > 0)
351     {
352       try
353       {
354         FileInputStream fis = new FileInputStream(saved);
355         ObjectInputStream ois = new ObjectInputStream(fis);
356         md5s = (HashMap<String,String>)ois.readObject();
357         ois.close();
358       }
359       catch (Exception e)
360       {
361         md5s = new HashMap<String,String>();
362         getLog().warn("Cannot read timestamps from saved: " + e);
363       }
364     }
365     else
366     {
367       md5s = new HashMap<String,String>();
368       try
369       {
370         saved.createNewFile();
371       }
372       catch (IOException e)
373       {
374         getLog().debug("Cannot create file \"" + saved.getPath() + "\" for timestamps: " + e);
375       }
376     }
377
378     ClassLoader classLoader = null;
379     try
380     {
381       getLog().debug("Creating ClassLoader for project-dependencies...");
382       List<String> classpathFiles = project.getCompileClasspathElements();
383       if (scanTestClasses)
384         classpathFiles.addAll(project.getTestClasspathElements());
385       URL[] urls = new URL[classpathFiles.size()];
386       for (int i = 0; i < classpathFiles.size(); ++i)
387       {
388         getLog().debug("Dependency: " + classpathFiles.get(i));
389         urls[i] = new File(classpathFiles.get(i)).toURI().toURL();
390       }
391       classLoader = new URLClassLoader(urls, getClass().getClassLoader());
392     }
393     catch (Exception e)
394     {
395       getLog().error("Error while creating ClassLoader!", e);
396       throw new MojoExecutionException(e.getMessage());
397     }
398
399     Set<Class<?>> classes =
400         new TreeSet<Class<?>>(
401             new Comparator<Class<?>>() {
402               @Override
403               public int compare(Class<?> a, Class<?> b)
404               {
405                 return a.getName().compareTo(b.getName());
406               }
407             }
408           );
409
410     try
411     {
412       AnnotationDB db = new AnnotationDB();
413       File dir = new File(outputDirectory);
414       if (dir.exists())
415       {
416         getLog().info("Scanning directory " + outputDirectory + " for annotated classes...");
417         URL dirUrl = dir.toURI().toURL();
418         db.scanArchives(dirUrl);
419       }
420       if (scanTestClasses)
421       {
422         dir = new File(testOutputDirectory);
423         if (dir.exists())
424         {
425           getLog().info("Scanning directory " + testOutputDirectory + " for annotated classes...");
426           URL dirUrl = dir.toURI().toURL();
427           db.scanArchives(dirUrl);
428         }
429       }
430       if (scanDependencies != null)
431       {
432         Matcher matcher = split.matcher(scanDependencies);
433         while (matcher.find())
434         {
435           getLog().info("Scanning dependencies for scope " + matcher.group());
436           for (Artifact artifact : project.getDependencyArtifacts())
437           {
438             if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
439               continue;
440             if (artifact.getFile() == null)
441             {
442               getLog().warn(
443                   "Cannot scan dependency " +
444                   artifact.getId() +
445                   ": no JAR-file available!"
446                   );
447               continue;
448             }
449             getLog().info(
450                 "Scanning dependency " +
451                 artifact.getId() +
452                 " for annotated classes..."
453                 );
454             db.scanArchives(artifact.getFile().toURI().toURL());
455           }
456         }
457       }
458
459       Set<String> classNames = new HashSet<String>();
460       if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
461         classNames.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
462       if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
463         classNames.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
464       if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
465         classNames.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
466
467       MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
468       for (String name : classNames)
469       {
470         Class<?> annotatedClass = classLoader.loadClass(name);
471         classes.add(annotatedClass);
472         String resourceName = annotatedClass.getName();
473         resourceName = resourceName.substring(resourceName.lastIndexOf(".") + 1, resourceName.length()) + ".class";
474         InputStream is =
475             annotatedClass
476                 .getResourceAsStream(resourceName);
477         byte[] buffer = new byte[1024*4]; // copy data in 4MB-chunks
478         int i;
479         while((i = is.read(buffer)) > -1)
480           digest.update(buffer, 0, i);
481         is.close();
482         byte[] bytes = digest.digest();
483         BigInteger bi = new BigInteger(1, bytes);
484         String newMd5 = String.format("%0" + (bytes.length << 1) + "x", bi);
485         String oldMd5 = !md5s.containsKey(name) ? "" : md5s.get(name);
486         if (!newMd5.equals(oldMd5))
487         {
488           getLog().debug("Found new or modified annotated class: " + name);
489           modified = true;
490           md5s.put(name, newMd5);
491         }
492         else
493         {
494           getLog().debug(oldMd5 + " -> class unchanged: " + name);
495         }
496       }
497     }
498     catch (ClassNotFoundException e)
499     {
500       getLog().error("Error while adding annotated classes!", e);
501       throw new MojoExecutionException(e.getMessage());
502     }
503     catch (Exception e)
504     {
505       getLog().error("Error while scanning!", e);
506       throw new MojoFailureException(e.getMessage());
507     }
508
509     if (classes.isEmpty())
510     {
511       if (hibernateMapping == null || hibernateMapping.isEmpty())
512         throw new MojoFailureException("No annotated classes found in directory " + outputDirectory);
513     }
514     else
515     {
516       getLog().debug("Detected classes with mapping-annotations:");
517       for (Class<?> annotatedClass : classes)
518         getLog().debug("  " + annotatedClass.getName());
519     }
520
521
522     Properties properties = new Properties();
523
524     /** Try to read configuration from properties-file */
525     try
526     {
527       File file = new File(hibernateProperties);
528       if (file.exists())
529       {
530         getLog().info("Reading properties from file " + hibernateProperties + "...");
531         properties.load(new FileInputStream(file));
532       }
533       else
534         getLog().info("No hibernate-properties-file found! (Checked path: " + hibernateProperties + ")");
535     }
536     catch (IOException e)
537     {
538       getLog().error("Error while reading properties!", e);
539       throw new MojoExecutionException(e.getMessage());
540     }
541
542     /** Overwrite values from properties-file or set, if given */
543     if (driverClassName != null)
544     {
545       if (properties.containsKey(DRIVER_CLASS))
546         getLog().debug(
547             "Overwriting property " +
548             DRIVER_CLASS + "=" + properties.getProperty(DRIVER_CLASS) +
549             " with the value " + driverClassName
550           );
551       else
552         getLog().debug("Using the value " + driverClassName);
553       properties.setProperty(DRIVER_CLASS, driverClassName);
554     }
555     if (url != null)
556     {
557       if (properties.containsKey(URL))
558         getLog().debug(
559             "Overwriting property " +
560             URL + "=" + properties.getProperty(URL) +
561             " with the value " + url
562           );
563       else
564         getLog().debug("Using the value " + url);
565       properties.setProperty(URL, url);
566     }
567     if (username != null)
568     {
569       if (properties.containsKey(USERNAME))
570         getLog().debug(
571             "Overwriting property " +
572             USERNAME + "=" + properties.getProperty(USERNAME) +
573             " with the value " + username
574           );
575       else
576         getLog().debug("Using the value " + username);
577       properties.setProperty(USERNAME, username);
578     }
579     if (password != null)
580     {
581       if (properties.containsKey(PASSWORD))
582         getLog().debug(
583             "Overwriting property " +
584             PASSWORD + "=" + properties.getProperty(PASSWORD) +
585             " with value " + password
586           );
587       else
588         getLog().debug("Using value " + password + " for property " + PASSWORD);
589       properties.setProperty(PASSWORD, password);
590     }
591     if (hibernateDialect != null)
592     {
593       if (properties.containsKey(DIALECT))
594         getLog().debug(
595             "Overwriting property " +
596             DIALECT + "=" + properties.getProperty(DIALECT) +
597             " with value " + hibernateDialect
598           );
599       else
600         getLog().debug(
601             "Using value " + hibernateDialect + " for property " + DIALECT
602             );
603       properties.setProperty(DIALECT, hibernateDialect);
604     }
605     else
606     {
607       hibernateDialect = properties.getProperty(DIALECT);
608     }
609     if ( hibernateNamingStrategy != null )
610     {
611       if ( properties.contains(NAMING_STRATEGY))
612         getLog().debug(
613             "Overwriting property " +
614             NAMING_STRATEGY + "=" + properties.getProperty(NAMING_STRATEGY) +
615             " with value " + hibernateNamingStrategy
616            );
617       else
618         getLog().debug(
619             "Using value " + hibernateNamingStrategy + " for property " +
620             NAMING_STRATEGY
621             );
622       properties.setProperty(NAMING_STRATEGY, hibernateNamingStrategy);
623     }
624
625     /** The generated SQL varies with the dialect! */
626     if (md5s.containsKey(DIALECT))
627     {
628       String dialect = properties.getProperty(DIALECT);
629       if (md5s.get(DIALECT).equals(dialect))
630         getLog().debug("SQL-dialect unchanged.");
631       else
632       {
633         modified = true;
634         if (dialect == null)
635         {
636           getLog().debug("SQL-dialect was unset.");
637           md5s.remove(DIALECT);
638         }
639         else
640         {
641           getLog().debug("SQL-dialect changed: " + dialect);
642           md5s.put(DIALECT, dialect);
643         }
644       }
645     }
646     else
647     {
648       String dialect = properties.getProperty(DIALECT);
649       if (dialect != null)
650       {
651         modified = true;
652         md5s.put(DIALECT, properties.getProperty(DIALECT));
653       }
654     }
655
656     /** The generated SQL varies with the envers-configuration */
657     if (md5s.get(ENVERS) != null)
658     {
659       if (md5s.get(ENVERS).equals(Boolean.toString(envers)))
660         getLog().debug("Envers-Configuration unchanged. Enabled: " + envers);
661       else
662       {
663         getLog().debug("Envers-Configuration changed. Enabled: " + envers);
664         modified = true;
665         md5s.put(ENVERS, Boolean.toString(envers));
666       }
667     }
668     else
669     {
670       modified = true;
671       md5s.put(ENVERS, Boolean.toString(envers));
672     }
673
674     if (properties.isEmpty())
675     {
676       getLog().error("No properties set!");
677       throw new MojoFailureException("Hibernate configuration is missing!");
678     }
679
680     final ValidationConfiguration config = new ValidationConfiguration(hibernateDialect);
681
682     config.setProperties(properties);
683
684     if ( properties.containsKey(NAMING_STRATEGY))
685     {
686       String namingStrategy = properties.getProperty(NAMING_STRATEGY);
687       getLog().debug("Explicitly set NamingStrategy: " + namingStrategy);
688       try
689       {
690         @SuppressWarnings("unchecked")
691         Class<NamingStrategy> namingStrategyClass = (Class<NamingStrategy>) Class.forName(namingStrategy);
692         config.setNamingStrategy(namingStrategyClass.newInstance());
693       }
694       catch (Exception e)
695       {
696         getLog().error("Error setting NamingStrategy", e);
697         throw new MojoExecutionException(e.getMessage());
698       }
699     }
700
701     ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
702     MavenLogAppender.startPluginLog(this);
703     try
704     {
705       /**
706        * Change class-loader of current thread, so that hibernate can
707        * see all dependencies!
708        */
709       Thread.currentThread().setContextClassLoader(classLoader);
710
711       getLog().debug("Adding annotated classes to hibernate-mapping-configuration...");
712       // build annotated packages
713       Set<String> packages = new HashSet<String>();
714       for (Class<?> annotatedClass : classes)
715       {
716         String packageName = annotatedClass.getPackage().getName();
717         if (!packages.contains(packageName))
718         {
719           getLog().debug("Add package " + packageName);
720           packages.add(packageName);
721           config.addPackage(packageName);
722           getLog().debug("type definintions" + config.getTypeDefs());
723         }
724         getLog().debug("Class " + annotatedClass);
725         config.addAnnotatedClass(annotatedClass);
726       }
727
728       if (hibernateMapping != null)
729       {
730         try
731         {
732           MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
733           for (String filename : hibernateMapping.split("[\\s,]+"))
734           {
735             // First try the filename as absolute/relative path
736             File file = new File(filename);
737             if (!file.exists())
738             {
739               // If the file was not found, search for it in the resource-directories
740               for (Resource resource : project.getResources())
741               {
742                 file = new File(resource.getDirectory() + File.separator + filename);
743                 if (file.exists())
744                   break;
745               }
746             }
747             if (file != null && file.exists())
748             {
749               InputStream is = new FileInputStream(file);
750               byte[] buffer = new byte[1024*4]; // copy data in 4MB-chunks
751               int i;
752               while((i = is.read(buffer)) > -1)
753                 digest.update(buffer, 0, i);
754               is.close();
755               byte[] bytes = digest.digest();
756               BigInteger bi = new BigInteger(1, bytes);
757               String newMd5 = String.format("%0" + (bytes.length << 1) + "x", bi);
758               String oldMd5 = !md5s.containsKey(filename) ? "" : md5s.get(filename);
759               if (!newMd5.equals(oldMd5))
760               {
761                 getLog().debug("Found new or modified mapping-file: " + filename);
762                 modified = true;
763                 md5s.put(filename, newMd5);
764               }
765               else
766               {
767                 getLog().debug(oldMd5 + " -> mapping-file unchanged: " + filename);
768               }
769               getLog().debug("Adding mappings from XML-configurationfile: " + file);
770               config.addFile(file);
771             }
772             else
773               throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
774           }
775         }
776         catch (NoSuchAlgorithmException e)
777         {
778           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
779         }
780         catch (FileNotFoundException e)
781         {
782           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
783         }
784         catch (IOException e)
785         {
786           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
787         }
788       }
789
790       Target target = null;
791       try
792       {
793         target = Target.valueOf(this.target.toUpperCase());
794       }
795       catch (IllegalArgumentException e)
796       {
797         getLog().error("Invalid value for configuration-option \"target\": " + this.target);
798         getLog().error("Valid values are: NONE, SCRIPT, EXPORT, BOTH");
799         throw new MojoExecutionException("Invalid value for configuration-option \"target\"");
800       }
801       Type type = null;
802       try
803       {
804         type = Type.valueOf(this.type.toUpperCase());
805       }
806       catch (IllegalArgumentException e)
807       {
808         getLog().error("Invalid value for configuration-option \"type\": " + this.type);
809         getLog().error("Valid values are: NONE, CREATE, DROP, BOTH");
810         throw new MojoExecutionException("Invalid value for configuration-option \"type\"");
811       }
812
813       if (target.equals(Target.SCRIPT) || target.equals(Target.NONE))
814       {
815         project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
816       }
817       if (
818           !modified
819           && !target.equals(Target.SCRIPT)
820           && !target.equals(Target.NONE)
821           && !force
822         )
823       {
824         getLog().info("No modified annotated classes or mapping-files found and dialect unchanged.");
825         getLog().info("Skipping schema generation!");
826         project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
827         return;
828       }
829
830       getLog().info("Gathered hibernate-configuration (turn on debugging for details):");
831       for (Entry<Object,Object> entry : properties.entrySet())
832         getLog().info("  " + entry.getKey() + " = " + entry.getValue());
833
834       Environment.verifyProperties( properties );
835       ConfigurationHelper.resolvePlaceHolders( properties );
836       StandardServiceRegistryImpl registry =
837           (StandardServiceRegistryImpl)
838           new StandardServiceRegistryBuilder().applySettings(properties).build();
839
840       config.buildMappings();
841
842       if (envers)
843       {
844         getLog().info("Automatic auditing via hibernate-envers enabled!");
845         AuditConfiguration.getFor(config);
846       }
847
848       SchemaExport export = new SchemaExport(registry, config);
849       export.setDelimiter(delimiter);
850       export.setFormat(format);
851
852       File outF = new File(outputFile);
853
854       if (!outF.isAbsolute())
855       {
856         // Interpret relative file path relative to build directory
857         outF = new File(buildDirectory, outputFile);
858         getLog().info("Adjusted relative path, resulting path is " + outF.getPath());
859       }
860
861       // Ensure that directory path for specified file exists
862       File outFileParentDir = outF.getParentFile();
863       if (null != outFileParentDir && !outFileParentDir.exists())
864       {
865         try
866         {
867           getLog().info("Creating directory path for output file:" + outFileParentDir.getPath());
868           outFileParentDir.mkdirs();
869         }
870         catch (Exception e)
871         {
872           getLog().error("Error creating directory path for output file: " + e.getLocalizedMessage());
873         }
874       }
875
876       export.setOutputFile(outF.getPath());
877       export.execute(target, type);
878
879       for (Object exception : export.getExceptions())
880         getLog().debug(exception.toString());
881     }
882     finally
883     {
884       /** Stop Log-Capturing */
885       MavenLogAppender.endPluginLog(this);
886
887       /** Restore the old class-loader (TODO: is this really necessary?) */
888       Thread.currentThread().setContextClassLoader(contextClassLoader);
889     }
890
891     /** Write md5-sums for annotated classes to file */
892     try
893     {
894       FileOutputStream fos = new FileOutputStream(saved);
895       ObjectOutputStream oos = new ObjectOutputStream(fos);
896       oos.writeObject(md5s);
897       oos.close();
898       fos.close();
899     }
900     catch (Exception e)
901     {
902       getLog().error("Cannot write md5-sums to file: " + e);
903     }
904   }
905
906   /**
907    * Needed, because DriverManager won't pick up drivers, that were not
908    * loaded by the system-classloader!
909    * See:
910    * http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-fromodifiedm-an-arbitrary-location
911    */
912   static final class DriverProxy implements Driver
913   {
914     private final Driver target;
915
916     DriverProxy(Driver target)
917     {
918       if (target == null)
919         throw new NullPointerException();
920       this.target = target;
921     }
922
923     public java.sql.Driver getTarget()
924     {
925       return target;
926     }
927
928     @Override
929     public boolean acceptsURL(String url) throws SQLException
930     {
931       return target.acceptsURL(url);
932     }
933
934     @Override
935     public java.sql.Connection connect(
936         String url,
937         java.util.Properties info
938       )
939       throws
940         SQLException
941     {
942       return target.connect(url, info);
943     }
944
945     @Override
946     public int getMajorVersion()
947     {
948       return target.getMajorVersion();
949     }
950
951     @Override
952     public int getMinorVersion()
953     {
954       return target.getMinorVersion();
955     }
956
957     @Override
958     public DriverPropertyInfo[] getPropertyInfo(
959         String url,
960         Properties info
961       )
962       throws
963         SQLException
964     {
965       return target.getPropertyInfo(url, info);
966     }
967
968     @Override
969     public boolean jdbcCompliant()
970     {
971       return target.jdbcCompliant();
972     }
973
974     /**
975      * This Method cannot be annotated with @Override, becaus the plugin
976      * will not compile then under Java 1.6!
977      */
978     public Logger getParentLogger() throws SQLFeatureNotSupportedException
979     {
980       throw new SQLFeatureNotSupportedException("Not supported, for backward-compatibility with Java 1.6");
981     }
982
983     @Override
984     public String toString()
985     {
986       return "Proxy: " + target;
987     }
988
989     @Override
990     public int hashCode()
991     {
992       return target.hashCode();
993     }
994
995     @Override
996     public boolean equals(Object obj)
997     {
998       if (!(obj instanceof DriverProxy))
999         return false;
1000       DriverProxy other = (DriverProxy) obj;
1001       return this.target.equals(other.target);
1002     }
1003   }
1004 }