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