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