Settings in a hibernate.cfg.xml are read
[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     ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
545     MavenLogAppender.startPluginLog(this);
546     try
547     {
548       /** Try to read configuration from properties-file */
549       try
550       {
551         File file = new File(hibernateProperties);
552         if (file.exists())
553         {
554           getLog().info("Reading properties from file " + hibernateProperties + "...");
555           Properties properties = new Properties();
556           properties.load(new FileInputStream(file));
557           config.setProperties(properties);
558         }
559         else
560           getLog().info("No hibernate-properties-file found! (Checked path: " + hibernateProperties + ")");
561       }
562       catch (IOException e)
563       {
564         getLog().error("Error while reading properties!", e);
565         throw new MojoExecutionException(e.getMessage());
566       }
567
568       /**
569        * Change class-loader of current thread, so that hibernate can
570        * see all dependencies!
571        */
572       Thread.currentThread().setContextClassLoader(classLoader);
573
574       /** Try to read configuration from configuration-file */
575       try
576       {
577         File file = new File(hibernateConfig);
578         if (file.exists())
579         {
580           getLog().info("Reading configuration from file " + hibernateConfig + "...");
581           config.configure(file);
582         }
583         else
584           getLog().info("No hibernate-configuration-file found! (Checked path: " + hibernateConfig + ")");
585       }
586       catch (Exception e)
587       {
588         getLog().error("Error while reading configuration!", e);
589         throw new MojoExecutionException(e.getMessage());
590       }
591
592       ParsedPersistenceXmlDescriptor persistenceUnitDescriptor =
593           getPersistenceUnitDescriptor(
594               persistenceUnit,
595               config.getProperties(),
596               new MavenProjectClassLoaderService(classLoader)
597               );
598       if (persistenceUnitDescriptor != null)
599         config.setProperties(persistenceUnitDescriptor.getProperties());
600
601       /** Overwrite values from properties-file or set, if given */
602       if (driverClassName != null)
603       {
604         if (config.getProperties().containsKey(DRIVER_CLASS))
605           getLog().debug(
606               "Overwriting property " +
607               DRIVER_CLASS + "=" + config.getProperty(DRIVER_CLASS) +
608               " with the value " + driverClassName
609             );
610         else
611           getLog().debug("Using the value " + driverClassName);
612         config.setProperty(DRIVER_CLASS, driverClassName);
613       }
614       if (url != null)
615       {
616         if (config.getProperties().containsKey(URL))
617           getLog().debug(
618               "Overwriting property " +
619               URL + "=" + config.getProperty(URL) +
620               " with the value " + url
621             );
622         else
623           getLog().debug("Using the value " + url);
624         config.setProperty(URL, url);
625       }
626       if (username != null)
627       {
628         if (config.getProperties().containsKey(USERNAME))
629           getLog().debug(
630               "Overwriting property " +
631               USERNAME + "=" + config.getProperty(USERNAME) +
632               " with the value " + username
633             );
634         else
635           getLog().debug("Using the value " + username);
636         config.setProperty(USERNAME, username);
637       }
638       if (password != null)
639       {
640         if (config.getProperties().containsKey(PASSWORD))
641           getLog().debug(
642               "Overwriting property " +
643               PASSWORD + "=" + config.getProperty(PASSWORD) +
644               " with value " + password
645             );
646         else
647           getLog().debug("Using value " + password + " for property " + PASSWORD);
648         config.setProperty(PASSWORD, password);
649       }
650       if (hibernateDialect != null)
651       {
652         if (config.getProperties().containsKey(DIALECT))
653           getLog().debug(
654               "Overwriting property " +
655               DIALECT + "=" + config.getProperty(DIALECT) +
656               " with value " + hibernateDialect
657             );
658         else
659           getLog().debug(
660               "Using value " + hibernateDialect + " for property " + DIALECT
661               );
662         config.setProperty(DIALECT, hibernateDialect);
663       }
664       if ( hibernateNamingStrategy != null )
665       {
666         if ( config.getProperties().contains(NAMING_STRATEGY))
667           getLog().debug(
668               "Overwriting property " +
669               NAMING_STRATEGY + "=" + config.getProperty(NAMING_STRATEGY) +
670               " with value " + hibernateNamingStrategy
671              );
672         else
673           getLog().debug(
674               "Using value " + hibernateNamingStrategy + " for property " +
675               NAMING_STRATEGY
676               );
677         config.setProperty(NAMING_STRATEGY, hibernateNamingStrategy);
678       }
679
680       /** The generated SQL varies with the dialect! */
681       if (md5s.containsKey(DIALECT))
682       {
683         String dialect = config.getProperty(DIALECT);
684         if (md5s.get(DIALECT).equals(dialect))
685           getLog().debug("SQL-dialect unchanged.");
686         else
687         {
688           modified = true;
689           if (dialect == null)
690           {
691             getLog().debug("SQL-dialect was unset.");
692             md5s.remove(DIALECT);
693           }
694           else
695           {
696             getLog().debug("SQL-dialect changed: " + dialect);
697             md5s.put(DIALECT, dialect);
698           }
699         }
700       }
701       else
702       {
703         String dialect = config.getProperty(DIALECT);
704         if (dialect != null)
705         {
706           modified = true;
707           md5s.put(DIALECT, config.getProperty(DIALECT));
708         }
709       }
710
711       /** The generated SQL varies with the envers-configuration */
712       if (md5s.get(ENVERS) != null)
713       {
714         if (md5s.get(ENVERS).equals(Boolean.toString(envers)))
715           getLog().debug("Envers-Configuration unchanged. Enabled: " + envers);
716         else
717         {
718           getLog().debug("Envers-Configuration changed. Enabled: " + envers);
719           modified = true;
720           md5s.put(ENVERS, Boolean.toString(envers));
721         }
722       }
723       else
724       {
725         modified = true;
726         md5s.put(ENVERS, Boolean.toString(envers));
727       }
728
729       if (config.getProperties().isEmpty())
730       {
731         getLog().error("No properties set!");
732         throw new MojoFailureException("Hibernate configuration is missing!");
733       }
734
735       getLog().info("Gathered hibernate-configuration (turn on debugging for details):");
736       for (Entry<Object,Object> entry : config.getProperties().entrySet())
737         getLog().info("  " + entry.getKey() + " = " + entry.getValue());
738
739
740       getLog().debug("Adding explicitly configured mappings...");
741       if (hibernateMapping != null)
742       {
743         try
744         {
745           MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
746           for (String filename : hibernateMapping.split("[\\s,]+"))
747           {
748             // First try the filename as absolute/relative path
749             File file = new File(filename);
750             if (!file.exists())
751             {
752               // If the file was not found, search for it in the resource-directories
753               for (Resource resource : project.getResources())
754               {
755                 file = new File(resource.getDirectory() + File.separator + filename);
756                 if (file.exists())
757                   break;
758               }
759             }
760             if (file != null && file.exists())
761             {
762               InputStream is = new FileInputStream(file);
763               byte[] buffer = new byte[1024*4]; // copy data in 4MB-chunks
764               int i;
765               while((i = is.read(buffer)) > -1)
766                 digest.update(buffer, 0, i);
767               is.close();
768               byte[] bytes = digest.digest();
769               BigInteger bi = new BigInteger(1, bytes);
770               String newMd5 = String.format("%0" + (bytes.length << 1) + "x", bi);
771               String oldMd5 = !md5s.containsKey(filename) ? "" : md5s.get(filename);
772               if (!newMd5.equals(oldMd5))
773               {
774                 getLog().debug("Found new or modified mapping-file: " + filename);
775                 modified = true;
776                 md5s.put(filename, newMd5);
777               }
778               else
779               {
780                 getLog().debug(oldMd5 + " -> mapping-file unchanged: " + filename);
781               }
782               getLog().debug("Adding mappings from XML-configurationfile: " + file);
783               config.addFile(file);
784             }
785             else
786               throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
787           }
788         }
789         catch (NoSuchAlgorithmException e)
790         {
791           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
792         }
793         catch (FileNotFoundException e)
794         {
795           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
796         }
797         catch (IOException e)
798         {
799           throw new MojoFailureException("Cannot calculate MD5 sums!", e);
800         }
801       }
802
803       getLog().debug("Adding annotated classes to hibernate-mapping-configuration...");
804       // build annotated packages
805       Set<String> packages = new HashSet<String>();
806       for (Class<?> annotatedClass : classes)
807       {
808         String packageName = annotatedClass.getPackage().getName();
809         if (!packages.contains(packageName))
810         {
811           getLog().debug("Add package " + packageName);
812           packages.add(packageName);
813           config.addPackage(packageName);
814           getLog().debug("type definintions" + config.getTypeDefs());
815         }
816         getLog().debug("Class " + annotatedClass);
817         config.addAnnotatedClass(annotatedClass);
818       }
819
820       Target target = null;
821       try
822       {
823         target = Target.valueOf(this.target.toUpperCase());
824       }
825       catch (IllegalArgumentException e)
826       {
827         getLog().error("Invalid value for configuration-option \"target\": " + this.target);
828         getLog().error("Valid values are: NONE, SCRIPT, EXPORT, BOTH");
829         throw new MojoExecutionException("Invalid value for configuration-option \"target\"");
830       }
831       Type type = null;
832       try
833       {
834         type = Type.valueOf(this.type.toUpperCase());
835       }
836       catch (IllegalArgumentException e)
837       {
838         getLog().error("Invalid value for configuration-option \"type\": " + this.type);
839         getLog().error("Valid values are: NONE, CREATE, DROP, BOTH");
840         throw new MojoExecutionException("Invalid value for configuration-option \"type\"");
841       }
842
843
844       Iterator<PersistentClass> it = config.getClassMappings();
845       if (!it.hasNext())
846       {
847         throw new MojoFailureException("No mapped classes found!");
848       }
849       else
850       {
851         getLog().info("Mapped classes:");
852         while (it.hasNext())
853         {
854           getLog().debug("  " + it.next().getClassName());
855         }
856       }
857
858       if (config.getProperty(DIALECT) == null)
859         throw new MojoFailureException("hibernate-dialect must be set!");
860
861
862       if (target.equals(Target.SCRIPT) || target.equals(Target.NONE))
863       {
864         project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
865       }
866       if (
867           !modified
868           && !target.equals(Target.SCRIPT)
869           && !target.equals(Target.NONE)
870           && !force
871         )
872       {
873         getLog().info("No modified annotated classes or mapping-files found and dialect unchanged.");
874         getLog().info("Skipping schema generation!");
875         project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
876         return;
877       }
878
879
880       if ( config.getProperties().containsKey(NAMING_STRATEGY))
881       {
882         String namingStrategy = config.getProperty(NAMING_STRATEGY);
883         getLog().debug("Explicitly set NamingStrategy: " + namingStrategy);
884         try
885         {
886           @SuppressWarnings("unchecked")
887           Class<NamingStrategy> namingStrategyClass = (Class<NamingStrategy>) Class.forName(namingStrategy);
888           config.setNamingStrategy(namingStrategyClass.newInstance());
889         }
890         catch (Exception e)
891         {
892           getLog().error("Error setting NamingStrategy", e);
893           throw new MojoExecutionException(e.getMessage());
894         }
895       }
896
897
898       Environment.verifyProperties(config.getProperties());
899       ConfigurationHelper.resolvePlaceHolders(config.getProperties());
900       StandardServiceRegistryImpl registry =
901           (StandardServiceRegistryImpl)
902           new StandardServiceRegistryBuilder()
903               .applySettings(config.getProperties())
904               .build();
905
906       config.buildMappings();
907
908       if (envers)
909       {
910         getLog().info("Automatic auditing via hibernate-envers enabled!");
911         AuditConfiguration.getFor(config);
912       }
913
914       SchemaExport export = new SchemaExport(registry, config);
915       export.setDelimiter(delimiter);
916       export.setFormat(format);
917
918       File outF = new File(outputFile);
919
920       if (!outF.isAbsolute())
921       {
922         // Interpret relative file path relative to build directory
923         outF = new File(buildDirectory, outputFile);
924         getLog().info("Adjusted relative path, resulting path is " + outF.getPath());
925       }
926
927       // Ensure that directory path for specified file exists
928       File outFileParentDir = outF.getParentFile();
929       if (null != outFileParentDir && !outFileParentDir.exists())
930       {
931         try
932         {
933           getLog().info("Creating directory path for output file:" + outFileParentDir.getPath());
934           outFileParentDir.mkdirs();
935         }
936         catch (Exception e)
937         {
938           getLog().error("Error creating directory path for output file: " + e.getLocalizedMessage());
939         }
940       }
941
942       export.setOutputFile(outF.getPath());
943       export.execute(target, type);
944
945       for (Object exception : export.getExceptions())
946         getLog().debug(exception.toString());
947     }
948     finally
949     {
950       /** Stop Log-Capturing */
951       MavenLogAppender.endPluginLog(this);
952
953       /** Restore the old class-loader (TODO: is this really necessary?) */
954       Thread.currentThread().setContextClassLoader(contextClassLoader);
955     }
956
957     /** Write md5-sums for annotated classes to file */
958     try
959     {
960       FileOutputStream fos = new FileOutputStream(saved);
961       ObjectOutputStream oos = new ObjectOutputStream(fos);
962       oos.writeObject(md5s);
963       oos.close();
964       fos.close();
965     }
966     catch (Exception e)
967     {
968       getLog().error("Cannot write md5-sums to file: " + e);
969     }
970   }
971
972   private ParsedPersistenceXmlDescriptor getPersistenceUnitDescriptor(
973       String name,
974       Properties properties,
975       ClassLoaderService loader
976       )
977       throws
978         MojoFailureException
979   {
980     PersistenceXmlParser parser =
981         new PersistenceXmlParser(
982             loader,
983             PersistenceUnitTransactionType.RESOURCE_LOCAL
984              );
985
986     List<ParsedPersistenceXmlDescriptor> units = parser.doResolve(properties);
987
988     if (name == null)
989     {
990       switch (units.size())
991       {
992         case 0:
993           getLog().info("Found no META-INF/persistence.xml.");
994           return null;
995         case 1:
996           getLog().info("Using persistence-unit " + units.get(0).getName());
997           return units.get(0);
998         default:
999           getLog().warn("No name provided and multiple persistence units found:");
1000           for (ParsedPersistenceXmlDescriptor unit : units)
1001             getLog().warn(" - " + unit.getName());
1002           return null;
1003       }
1004
1005     }
1006
1007     for (ParsedPersistenceXmlDescriptor unit : units)
1008     {
1009       getLog().debug("Found persistence-unit " + unit.getName());
1010       if (!unit.getName().equals(name))
1011         continue;
1012
1013       // See if we (Hibernate) are the persistence provider
1014       if (!ProviderChecker.isProvider(unit, properties))
1015       {
1016         getLog().debug("Wrong provider: " + unit.getProviderClassName());
1017         continue;
1018       }
1019
1020       getLog().info("Using persistence-unit " + unit.getName());
1021       return unit;
1022     }
1023
1024     throw new MojoFailureException("Could not find persistence-unit " + name);
1025   }
1026
1027
1028   static final class MavenProjectClassLoaderService implements ClassLoaderService
1029   {
1030     final private ClassLoader loader;
1031
1032
1033     public MavenProjectClassLoaderService(ClassLoader loader)
1034     {
1035       this.loader = loader;
1036     }
1037
1038
1039     @Override
1040     public <T> Class<T> classForName(String name)
1041     {
1042       try
1043       {
1044         return (Class<T>)loader.loadClass(name);
1045       }
1046       catch (ClassNotFoundException e)
1047       {
1048         throw new ClassLoadingException( "Unable to load class [" + name + "]", e );
1049       }
1050     }
1051
1052     @Override
1053     public URL locateResource(String name)
1054     {
1055       return loader.getResource(name);
1056     }
1057
1058     @Override
1059     public InputStream locateResourceStream(String name)
1060     {
1061       return loader.getResourceAsStream(name);
1062     }
1063
1064     @Override
1065     public List<URL> locateResources(String name)
1066     {
1067       try
1068       {
1069         return Collections.list(loader.getResources(name));
1070       }
1071       catch (IOException e)
1072       {
1073         return Collections.EMPTY_LIST;
1074       }
1075     }
1076
1077     @Override
1078     public <S> LinkedHashSet<S> loadJavaServices(Class<S> serviceContract)
1079     {
1080       throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1081     }
1082
1083     @Override
1084     public void stop() { }
1085
1086   }
1087
1088
1089   /**
1090    * Needed, because DriverManager won't pick up drivers, that were not
1091    * loaded by the system-classloader!
1092    * See:
1093    * http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-fromodifiedm-an-arbitrary-location
1094    */
1095   static final class DriverProxy implements Driver
1096   {
1097     private final Driver target;
1098
1099     DriverProxy(Driver target)
1100     {
1101       if (target == null)
1102         throw new NullPointerException();
1103       this.target = target;
1104     }
1105
1106     public java.sql.Driver getTarget()
1107     {
1108       return target;
1109     }
1110
1111     @Override
1112     public boolean acceptsURL(String url) throws SQLException
1113     {
1114       return target.acceptsURL(url);
1115     }
1116
1117     @Override
1118     public java.sql.Connection connect(
1119         String url,
1120         java.util.Properties info
1121       )
1122       throws
1123         SQLException
1124     {
1125       return target.connect(url, info);
1126     }
1127
1128     @Override
1129     public int getMajorVersion()
1130     {
1131       return target.getMajorVersion();
1132     }
1133
1134     @Override
1135     public int getMinorVersion()
1136     {
1137       return target.getMinorVersion();
1138     }
1139
1140     @Override
1141     public DriverPropertyInfo[] getPropertyInfo(
1142         String url,
1143         Properties info
1144       )
1145       throws
1146         SQLException
1147     {
1148       return target.getPropertyInfo(url, info);
1149     }
1150
1151     @Override
1152     public boolean jdbcCompliant()
1153     {
1154       return target.jdbcCompliant();
1155     }
1156
1157     /**
1158      * This Method cannot be annotated with @Override, becaus the plugin
1159      * will not compile then under Java 1.6!
1160      */
1161     public Logger getParentLogger() throws SQLFeatureNotSupportedException
1162     {
1163       throw new SQLFeatureNotSupportedException("Not supported, for backward-compatibility with Java 1.6");
1164     }
1165
1166     @Override
1167     public String toString()
1168     {
1169       return "Proxy: " + target;
1170     }
1171
1172     @Override
1173     public int hashCode()
1174     {
1175       return target.hashCode();
1176     }
1177
1178     @Override
1179     public boolean equals(Object obj)
1180     {
1181       if (!(obj instanceof DriverProxy))
1182         return false;
1183       DriverProxy other = (DriverProxy) obj;
1184       return this.target.equals(other.target);
1185     }
1186   }
1187 }