View Javadoc
1   package de.juplo.plugins.hibernate;
2   
3   
4   import com.pyx4j.log4j.MavenLogAppender;
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.net.MalformedURLException;
11  import java.net.URL;
12  import java.security.NoSuchAlgorithmException;
13  import java.time.ZonedDateTime;
14  import java.util.Collections;
15  import java.util.EnumSet;
16  import java.util.HashMap;
17  import java.util.HashSet;
18  import java.util.Iterator;
19  import java.util.LinkedHashSet;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  import java.util.Properties;
24  import java.util.Set;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  import javax.persistence.Embeddable;
28  import javax.persistence.Entity;
29  import javax.persistence.MappedSuperclass;
30  import javax.persistence.spi.PersistenceUnitTransactionType;
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.model.Resource;
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.MojoFailureException;
36  import org.apache.maven.project.MavenProject;
37  import org.hibernate.boot.MetadataBuilder;
38  import org.hibernate.boot.MetadataSources;
39  import org.hibernate.boot.cfgxml.internal.ConfigLoader;
40  import org.hibernate.boot.cfgxml.spi.LoadedConfig;
41  import org.hibernate.boot.cfgxml.spi.MappingReference;
42  import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
43  import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
44  import org.hibernate.boot.registry.BootstrapServiceRegistry;
45  import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
46  import org.hibernate.boot.registry.StandardServiceRegistry;
47  import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
48  import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
49  import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
50  import org.hibernate.boot.registry.selector.spi.StrategySelector;
51  import org.hibernate.boot.spi.MetadataImplementor;
52  import org.hibernate.cfg.AvailableSettings;
53  import static org.hibernate.cfg.AvailableSettings.DIALECT;
54  import static org.hibernate.cfg.AvailableSettings.DRIVER;
55  import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL;
56  import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DELIMITER;
57  import static org.hibernate.cfg.AvailableSettings.HBM2DLL_CREATE_NAMESPACES;
58  import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY;
59  import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER;
60  import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD;
61  import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL;
62  import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER;
63  import static org.hibernate.cfg.AvailableSettings.PASS;
64  import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY;
65  import static org.hibernate.cfg.AvailableSettings.SHOW_SQL;
66  import static org.hibernate.cfg.AvailableSettings.USER;
67  import static org.hibernate.cfg.AvailableSettings.URL;
68  import org.hibernate.engine.config.spi.ConfigurationService;
69  import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
70  import org.hibernate.internal.util.config.ConfigurationException;
71  import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
72  import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
73  import org.hibernate.tool.schema.TargetType;
74  import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl;
75  import org.hibernate.tool.schema.internal.exec.ScriptTargetOutputToFile;
76  import org.hibernate.tool.schema.spi.ExecutionOptions;
77  import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
78  import org.hibernate.tool.schema.spi.ScriptTargetOutput;
79  import org.hibernate.tool.schema.spi.TargetDescriptor;
80  import org.scannotation.AnnotationDB;
81  
82  
83  /**
84   * Baseclass with common attributes and methods.
85   *
86   * @phase process-classes
87   * @threadSafe
88   * @requiresDependencyResolution runtime
89   */
90  public abstract class AbstractSchemaMojo extends AbstractMojo
91  {
92    public final static String EXECUTE = "hibernate.schema.execute";
93    public final static String OUTPUTDIRECTORY = "project.build.outputDirectory";
94    public final static String SCAN_CLASSES = "hibernate.schema.scan.classes";
95    public final static String SCAN_DEPENDENCIES = "hibernate.schema.scan.dependencies";
96    public final static String SCAN_TESTCLASSES = "hibernate.schema.scan.test_classes";
97    public final static String TEST_OUTPUTDIRECTORY = "project.build.testOutputDirectory";
98    public final static String SKIPPED = "hibernate.schema.skipped";
99    public final static String SCRIPT = "hibernate.schema.script";
100 
101   private final static Pattern SPLIT = Pattern.compile("[^,\\s]+");
102 
103   private final Set<String> packages = new HashSet<String>();
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   /** Parameters to configure the genaration of the SQL *********************/
130 
131   /**
132    * Excecute the generated SQL.
133    * If set to <code>false</code>, only the SQL-script is created and the
134    * database is not touched.
135    * <p>
136    * <strong>Important:</strong>
137    * This configuration value can only be configured through the
138    * <code>pom.xml</code>, or by the definition of a system-property, because
139    * it is not known by Hibernate nor JPA and, hence, not picked up from
140    * their configuration!
141    *
142    * @parameter property="hibernate.schema.execute" default-value="true"
143    * @since 2.0
144    */
145   private Boolean execute;
146 
147   /**
148    * Skip execution
149    * <p>
150    * If set to <code>true</code>, the execution is skipped.
151    * <p>
152    * A skipped execution is signaled via the maven-property
153    * <code>${hibernate.schema.skipped}</code>.
154    * <p>
155    * The execution is skipped automatically, if no modified or newly added
156    * annotated classes are found and the dialect was not changed.
157    * <p>
158    * <strong>Important:</strong>
159    * This configuration value can only be configured through the
160    * <code>pom.xml</code>, or by the definition of a system-property, because
161    * it is not known by Hibernate nor JPA and, hence, not picked up from
162    * their configuration!
163    *
164    * @parameter property="hibernate.schema.skip" default-value="${maven.test.skip}"
165    * @since 1.0
166    */
167   private boolean skip;
168 
169   /**
170    * Force generation/execution
171    * <p>
172    * Force the generation and (if configured) the execution of the SQL, even if
173    * no modified or newly added annotated classes where found and the
174    * configuration was not changed.
175    * <p>
176    * <code>skip</code> takes precedence over <code>force</code>.
177    * <p>
178    * <strong>Important:</strong>
179    * This configuration value can only be configured through the
180    * <code>pom.xml</code>, or by the definition of a system-property, because
181    * it is not known by Hibernate nor JPA and, hence, not picked up from
182    * their configuration!
183    *
184    * @parameter property="hibernate.schema.force" default-value="false"
185    * @since 1.0
186    */
187   private boolean force;
188 
189   /**
190    * Hibernate dialect.
191    *
192    * @parameter property="hibernate.dialect"
193    * @since 1.0
194    */
195   private String dialect;
196 
197   /**
198    * Delimiter in output-file.
199    * <p>
200    * <strong>Important:</strong>
201    * This configuration value can only be configured through the
202    * <code>pom.xml</code>, or by the definition of a system-property, because
203    * it is not known by Hibernate nor JPA and, hence, not picked up from
204    * their configuration!
205    *
206    * @parameter property="hibernate.hbm2ddl.delimiter" default-value=";"
207    * @since 1.0
208    */
209   private String delimiter;
210 
211   /**
212    * Show the generated SQL in the command-line output.
213    *
214    * @parameter property="hibernate.show_sql"
215    * @since 1.0
216    */
217   private Boolean show;
218 
219   /**
220    * Format output-file.
221    *
222    * @parameter property="hibernate.format_sql"
223    * @since 1.0
224    */
225   private Boolean format;
226 
227   /**
228    * Specifies whether to automatically create also the database schema/catalog.
229    *
230    * @parameter property="hibernate.hbm2dll.create_namespaces" default-value="false"
231    * @since 2.0
232    */
233   private Boolean createNamespaces;
234 
235   /**
236    * Implicit naming strategy
237    *
238    * @parameter property="hibernate.implicit_naming_strategy"
239    * @since 2.0
240    */
241   private String implicitNamingStrategy;
242 
243   /**
244    * Physical naming strategy
245    *
246    * @parameter property="hibernate.physical_naming_strategy"
247    * @since 2.0
248    */
249   private String physicalNamingStrategy;
250 
251   /**
252    * Wether the project should be scanned for annotated-classes, or not
253    * <p>
254    * This parameter is intended to allow overwriting of the parameter
255    * <code>exclude-unlisted-classes</code> of a <code>persistence-unit</code>.
256    * If not specified, it defaults to <code>true</code>
257    *
258    * @parameter property="hibernate.schema.scan.classes"
259    * @since 2.0
260    */
261   private Boolean scanClasses;
262 
263   /**
264    * Classes-Directory to scan.
265    * <p>
266    * This parameter defaults to the maven build-output-directory for classes.
267    * Additionally, all dependencies are scanned for annotated classes.
268    * <p>
269    * <strong>Important:</strong>
270    * This configuration value can only be configured through the
271    * <code>pom.xml</code>, or by the definition of a system-property, because
272    * it is not known by Hibernate nor JPA and, hence, not picked up from
273    * their configuration!
274    *
275    * @parameter property="project.build.outputDirectory"
276    * @since 1.0
277    */
278   private String outputDirectory;
279 
280   /**
281    * Dependency-Scopes, that should be scanned for annotated classes.
282    * <p>
283    * By default, only dependencies in the scope <code>compile</code> are
284    * scanned for annotated classes. Multiple scopes can be seperated by
285    * white space or commas.
286    * <p>
287    * If you do not want any dependencies to be scanned for annotated
288    * classes, set this parameter to <code>none</code>.
289    * <p>
290    * The plugin does not scan for annotated classes in transitive
291    * dependencies. If some of your annotated classes are hidden in a
292    * transitive dependency, you can simply add that dependency explicitly.
293    *
294    * @parameter property="hibernate.schema.scan.dependencies" default-value="compile"
295    * @since 1.0.3
296    */
297   private String scanDependencies;
298 
299   /**
300    * Whether to scan the test-branch of the project for annotated classes, or
301    * not.
302    * <p>
303    * If this parameter is set to <code>true</code> the test-classes of the
304    * artifact will be scanned for hibernate-annotated classes additionally.
305    * <p>
306    * <strong>Important:</strong>
307    * This configuration value can only be configured through the
308    * <code>pom.xml</code>, or by the definition of a system-property, because
309    * it is not known by Hibernate nor JPA and, hence, not picked up from
310    * their configuration!
311    *
312    * @parameter property="hibernate.schema.scan.test_classes" default-value="false"
313    * @since 1.0.1
314    */
315   private Boolean scanTestClasses;
316 
317   /**
318    * Test-Classes-Directory to scan.
319    * <p>
320    * This parameter defaults to the maven build-output-directory for
321    * test-classes.
322    * <p>
323    * This parameter is only used, when <code>scanTestClasses</code> is set
324    * to <code>true</code>!
325    * <p>
326    * <strong>Important:</strong>
327    * This configuration value can only be configured through the
328    * <code>pom.xml</code>, or by the definition of a system-property, because
329    * it is not known by Hibernate nor JPA and, hence, not picked up from
330    * their configuration!
331    *
332    * @parameter property="project.build.testOutputDirectory"
333    * @since 1.0.2
334    */
335   private String testOutputDirectory;
336 
337 
338   /** Conection parameters *************************************************/
339 
340   /**
341    * SQL-Driver name.
342    *
343    * @parameter property="hibernate.connection.driver_class"
344    * @since 1.0
345    */
346   private String driver;
347 
348   /**
349    * Database URL.
350    *
351    * @parameter property="hibernate.connection.url"
352    * @since 1.0
353    */
354   private String url;
355 
356   /**
357    * Database username
358    *
359    * @parameter property="hibernate.connection.username"
360    * @since 1.0
361    */
362   private String username;
363 
364   /**
365    * Database password
366    *
367    * @parameter property="hibernate.connection.password"
368    * @since 1.0
369    */
370   private String password;
371 
372 
373   /** Parameters to locate configuration sources ****************************/
374 
375   /**
376    * Path to a file or name of a ressource with hibernate properties.
377    * If this parameter is specified, the plugin will try to load configuration
378    * values from a file with the given path or a ressource on the classpath with
379    * the given name. If both fails, the execution of the plugin will fail.
380    * <p>
381    * If this parameter is not set the plugin will load configuration values
382    * from a ressource named <code>hibernate.properties</code> on the classpath,
383    * if it is present, but will not fail if there is no such ressource.
384    * <p>
385    * During ressource-lookup, the test-classpath takes precedence.
386    *
387    * @parameter
388    * @since 1.0
389    */
390   private String hibernateProperties;
391 
392   /**
393    * Path to Hibernate configuration file (.cfg.xml).
394    * If this parameter is specified, the plugin will try to load configuration
395    * values from a file with the given path or a ressource on the classpath with
396    * the given name. If both fails, the execution of the plugin will fail.
397    * <p>
398    * If this parameter is not set the plugin will load configuration values
399    * from a ressource named <code>hibernate.cfg.xml</code> on the classpath,
400    * if it is present, but will not fail if there is no such ressource.
401    * <p>
402    * During ressource-lookup, the test-classpath takes precedence.
403    * <p>
404    * Settings in this file will overwrite settings in the properties file.
405    *
406    * @parameter
407    * @since 1.1.0
408    */
409   private String hibernateConfig;
410 
411   /**
412    * Name of the persistence-unit.
413    * If this parameter is specified, the plugin will try to load configuration
414    * values from a persistence-unit with the specified name. If no such
415    * persistence-unit can be found, the plugin will throw an exception.
416    * <p>
417    * If this parameter is not set and there is only one persistence-unit
418    * available, that unit will be used automatically. But if this parameter is
419    * not set and there are multiple persistence-units available on,
420    * the class-path, the execution of the plugin will fail.
421    * <p>
422    * Settings in this file will overwrite settings in the properties or the
423    * configuration file.
424    *
425    * @parameter
426    * @since 1.1.0
427    */
428   private String persistenceUnit;
429 
430   /**
431    * List of Hibernate-Mapping-Files (XML).
432    * Multiple files can be separated with white-spaces and/or commas.
433    *
434    * @parameter property="hibernate.mapping"
435    * @since 1.0.2
436    */
437   private String mappings;
438 
439 
440 
441   public final void execute(String filename)
442     throws
443       MojoFailureException,
444       MojoExecutionException
445   {
446     if (skip)
447     {
448       getLog().info("Execution of hibernate-maven-plugin was skipped!");
449       project.getProperties().setProperty(SKIPPED, "true");
450       return;
451     }
452 
453     ModificationTracker tracker;
454     try
455     {
456       tracker = new ModificationTracker(buildDirectory, filename, getLog());
457     }
458     catch (NoSuchAlgorithmException e)
459     {
460       throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
461     }
462 
463     final SimpleConnectionProvider connectionProvider =
464         new SimpleConnectionProvider(getLog());
465 
466     try
467     {
468       /** Start extended logging */
469       MavenLogAppender.startPluginLog(this);
470 
471       /** Load checksums for old mapping and configuration */
472       tracker.load();
473 
474       /** Create the ClassLoader */
475       MutableClassLoader classLoader = createClassLoader();
476 
477       /** Create a BootstrapServiceRegistry with the created ClassLoader */
478       BootstrapServiceRegistry bootstrapServiceRegitry =
479           new BootstrapServiceRegistryBuilder()
480               .applyClassLoader(classLoader)
481               .build();
482       ClassLoaderService classLoaderService =
483           bootstrapServiceRegitry.getService(ClassLoaderService.class);
484 
485       Properties properties = new Properties();
486       ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
487 
488       /** Loading and merging configuration */
489       properties.putAll(loadProperties(configLoader));
490       LoadedConfig config = loadConfig(configLoader);
491       if (config != null)
492         properties.putAll(config.getConfigurationValues());
493       ParsedPersistenceXmlDescriptor unit =
494           loadPersistenceUnit(classLoaderService, properties);
495       if (unit != null)
496         properties.putAll(unit.getProperties());
497 
498       /** Overwriting/Completing configuration */
499       configure(properties, tracker);
500 
501       /** Check configuration for modifications */
502       if(tracker.track(properties))
503         getLog().debug("Configuration has changed.");
504       else
505         getLog().debug("Configuration unchanged.");
506 
507       /** Check, that the outputfile is writable */
508       final File output = getOutputFile(filename);
509       /** Check, if the outputfile is missing or was changed */
510       checkOutputFile(output, tracker);
511 
512       /** Configure Hibernate */
513       final StandardServiceRegistry serviceRegistry =
514           new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
515               .applySettings(properties)
516               .addService(ConnectionProvider.class, connectionProvider)
517               .build();
518       final MetadataSources sources = new MetadataSources(serviceRegistry);
519 
520       /** Add the remaining class-path-elements */
521       completeClassPath(classLoader);
522 
523       /** Apply mappings from hibernate-configuration, if present */
524       if (config != null)
525       {
526         for (MappingReference mapping : config.getMappingReferences())
527           mapping.apply(sources);
528       }
529 
530       Set<String> classes;
531       if (unit == null)
532       {
533         /** No persistent unit: default behaviour */
534         if (scanClasses == null)
535           scanClasses = true;
536         Set<URL> urls = new HashSet<URL>();
537         if (scanClasses)
538           addRoot(urls, outputDirectory);
539         if (scanTestClasses)
540           addRoot(urls, testOutputDirectory);
541         addDependencies(urls);
542         classes = scanUrls(urls);
543       }
544       else
545       {
546         /** Follow configuration in persisten unit */
547         if (scanClasses == null)
548           scanClasses = !unit.isExcludeUnlistedClasses();
549         Set<URL> urls = new HashSet<URL>();
550         if (scanClasses)
551         {
552           /**
553            * Scan the root of the persiten unit and configured jars for
554            * annotated classes
555            */
556           urls.add(unit.getPersistenceUnitRootUrl());
557           for (URL url : unit.getJarFileUrls())
558             urls.add(url);
559         }
560         if (scanTestClasses)
561           addRoot(urls, testOutputDirectory);
562         classes = scanUrls(urls);
563         for (String className : unit.getManagedClassNames())
564           classes.add(className);
565         /**
566          * Add mappings from the default mapping-file
567          * <code>META-INF/orm.xml</code>, if present
568          */
569         boolean error = false;
570         InputStream is;
571         is = classLoader.getResourceAsStream("META-INF/orm.xml");
572         if (is != null)
573         {
574           getLog().info("Adding default JPA-XML-mapping from META-INF/orm.xml");
575           try
576           {
577             tracker.track("META-INF/orm.xml", is);
578             sources.addResource("META-INF/orm.xml");
579           }
580           catch (IOException e)
581           {
582             getLog().error("cannot read META-INF/orm.xml: " + e);
583             error = true;
584           }
585         }
586         else
587         {
588           getLog().debug("no META-INF/orm.xml found");
589         }
590         /**
591          * Add mappings from files, that are explicitly configured in the
592          * persistence unit
593          */
594         for (String mapping : unit.getMappingFileNames())
595         {
596           getLog().info("Adding explicitly configured mapping from " + mapping);
597           is = classLoader.getResourceAsStream(mapping);
598           if (is != null)
599           {
600             try
601             {
602               tracker.track(mapping, is);
603               sources.addResource(mapping);
604             }
605             catch (IOException e)
606             {
607               getLog().info("cannot read mapping-file " + mapping + ": " + e);
608               error = true;
609             }
610           }
611           else
612           {
613             getLog().error("cannot find mapping-file " + mapping);
614             error = true;
615           }
616         }
617         if (error)
618           throw new MojoFailureException(
619               "error, while reading mappings configured in persistence-unit \"" +
620               unit.getName() +
621               "\""
622               );
623       }
624 
625       /** Add the configured/collected annotated classes */
626       for (String className : classes)
627         addAnnotated(className, sources, classLoaderService, tracker);
628 
629       /** Add explicitly configured classes */
630       addMappings(sources, tracker);
631 
632       /** Skip execution, if mapping and configuration is unchanged */
633       if (!tracker.modified())
634       {
635         getLog().info("Mapping and configuration unchanged.");
636         if (force)
637           getLog().info("Generation/execution is forced!");
638         else
639         {
640           getLog().info("Skipping schema generation!");
641           project.getProperties().setProperty(SKIPPED, "true");
642           return;
643         }
644       }
645 
646 
647       /** Truncate output file */
648       try
649       {
650         new FileOutputStream(output).getChannel().truncate(0).close();
651       }
652       catch (IOException e)
653       {
654         String error =
655             "Error while truncating " + output.getAbsolutePath() + ": "
656             + e.getMessage();
657         getLog().warn(error);
658         throw new MojoExecutionException(error);
659       }
660 
661       /** Create a connection, if sufficient configuration infromation is available */
662       connectionProvider.open(classLoaderService, properties);
663 
664       MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
665 
666       StrategySelector strategySelector =
667           serviceRegistry.getService(StrategySelector.class);
668 
669       if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
670       {
671         metadataBuilder.applyImplicitNamingStrategy(
672             strategySelector.resolveStrategy(
673                 ImplicitNamingStrategy.class,
674                 properties.getProperty(IMPLICIT_NAMING_STRATEGY)
675                 )
676             );
677       }
678 
679       if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
680       {
681         metadataBuilder.applyPhysicalNamingStrategy(
682             strategySelector.resolveStrategy(
683                 PhysicalNamingStrategy.class,
684                 properties.getProperty(PHYSICAL_NAMING_STRATEGY)
685                 )
686             );
687       }
688 
689       /** Prepare the generation of the SQL */
690       Map settings = new HashMap();
691       settings.putAll(
692           serviceRegistry
693               .getService(ConfigurationService.class)
694               .getSettings()
695               );
696       ExceptionHandlerCollectingImpl handler =
697           new ExceptionHandlerCollectingImpl();
698       ExecutionOptions options =
699           SchemaManagementToolCoordinator
700               .buildExecutionOptions(settings, handler);
701       final EnumSet<TargetType> targetTypes = EnumSet.of(TargetType.SCRIPT);
702       if (execute)
703         targetTypes.add(TargetType.DATABASE);
704       TargetDescriptor target = new TargetDescriptor()
705       {
706         @Override
707         public EnumSet<TargetType> getTargetTypes()
708         {
709           return targetTypes;
710         }
711 
712         @Override
713         public ScriptTargetOutput getScriptTargetOutput()
714         {
715           String charset =
716               (String)
717               serviceRegistry
718                   .getService(ConfigurationService.class)
719                   .getSettings()
720                   .get(AvailableSettings.HBM2DDL_CHARSET_NAME);
721           return new ScriptTargetOutputToFile(output, charset);
722         }
723       };
724 
725       /**
726        * Change class-loader of current thread.
727        * This is necessary, because still not all parts of Hibernate 5 use
728        * the newly introduced ClassLoaderService and will fail otherwise!
729        */
730       Thread thread = Thread.currentThread();
731       ClassLoader contextClassLoader = thread.getContextClassLoader();
732       try
733       {
734         thread.setContextClassLoader(classLoader);
735         build((MetadataImplementor)metadataBuilder.build(), options, target);
736         if (handler.getExceptions().size() > 0)
737         {
738           StringBuilder builder = new StringBuilder();
739           builder.append("Hibernate failed:");
740           for (Exception e : handler.getExceptions())
741           {
742             builder.append("\n * ");
743             builder.append(e.getMessage());
744             AbstractSchemaMojo.printStrackTrace(builder, e);
745             builder.append("\n");
746           }
747           String error = builder.toString();
748           getLog().error(error);
749           throw new MojoFailureException(error);
750         }
751       }
752       finally
753       {
754         thread.setContextClassLoader(contextClassLoader);
755         /** Track, the content of the generated script */
756         checkOutputFile(output, tracker);
757       }
758     }
759     catch (MojoExecutionException e)
760     {
761       tracker.failed();
762       throw e;
763     }
764     catch (MojoFailureException e)
765     {
766       tracker.failed();
767       throw e;
768     }
769     catch (RuntimeException e)
770     {
771       tracker.failed();
772       throw e;
773     }
774     finally
775     {
776       /** Remember mappings and configuration */
777       tracker.save();
778 
779       /** Close the connection - if one was opened */
780       connectionProvider.close();
781 
782       /** Stop Log-Capturing */
783       MavenLogAppender.endPluginLog(this);
784     }
785   }
786 
787 
788   abstract void build(
789       MetadataImplementor metadata,
790       ExecutionOptions options,
791       TargetDescriptor target
792       )
793     throws
794       MojoFailureException,
795       MojoExecutionException;
796 
797 
798   private MutableClassLoader createClassLoader() throws MojoExecutionException
799   {
800     try
801     {
802       getLog().debug("Creating ClassLoader for project-dependencies...");
803       LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
804       File file;
805 
806       file = new File(testOutputDirectory);
807       if (!file.exists())
808       {
809         getLog().info("Creating test-output-directory: " + testOutputDirectory);
810         file.mkdirs();
811       }
812       urls.add(file.toURI().toURL());
813 
814       file = new File(outputDirectory);
815       if (!file.exists())
816       {
817         getLog().info("Creating output-directory: " + outputDirectory);
818         file.mkdirs();
819       }
820       urls.add(file.toURI().toURL());
821 
822       return new MutableClassLoader(urls, getLog());
823     }
824     catch (Exception e)
825     {
826       getLog().error("Error while creating ClassLoader!", e);
827       throw new MojoExecutionException(e.getMessage());
828     }
829   }
830 
831   private void completeClassPath(MutableClassLoader classLoader)
832       throws
833         MojoExecutionException
834   {
835     try
836     {
837       getLog().debug("Completing class-paths of the ClassLoader for project-dependencies...");
838       List<String> classpathFiles = project.getCompileClasspathElements();
839       if (scanTestClasses)
840         classpathFiles.addAll(project.getTestClasspathElements());
841       LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
842       for (String pathElement : classpathFiles)
843       {
844         getLog().debug("Dependency: " + pathElement);
845         urls.add(new File(pathElement).toURI().toURL());
846       }
847       classLoader.add(urls);
848     }
849     catch (Exception e)
850     {
851       getLog().error("Error while creating ClassLoader!", e);
852       throw new MojoExecutionException(e.getMessage());
853     }
854   }
855 
856   private Map loadProperties(ConfigLoader configLoader)
857       throws
858         MojoExecutionException
859   {
860     /** Try to read configuration from properties-file */
861     if (hibernateProperties == null)
862     {
863       try
864       {
865         return configLoader.loadProperties("hibernate.properties");
866       }
867       catch (ConfigurationException e)
868       {
869         getLog().debug(e.getMessage());
870         return Collections.EMPTY_MAP;
871       }
872     }
873     else
874     {
875       try
876       {
877         File file = new File(hibernateProperties);
878         if (file.exists())
879         {
880           getLog().info("Reading settings from file " + hibernateProperties + "...");
881           return configLoader.loadProperties(file);
882         }
883         else
884           return configLoader.loadProperties(hibernateProperties);
885       }
886       catch (ConfigurationException e)
887       {
888         getLog().error("Error while reading properties!", e);
889         throw new MojoExecutionException(e.getMessage());
890       }
891     }
892   }
893 
894   private LoadedConfig loadConfig(ConfigLoader configLoader)
895       throws MojoExecutionException
896   {
897     /** Try to read configuration from configuration-file */
898     if (hibernateConfig == null)
899     {
900       try
901       {
902         return configLoader.loadConfigXmlResource("hibernate.cfg.xml");
903       }
904       catch (ConfigurationException e)
905       {
906         getLog().debug(e.getMessage());
907         return null;
908       }
909     }
910     else
911     {
912       try
913       {
914         File file = new File(hibernateConfig);
915         if (file.exists())
916         {
917           getLog().info("Reading configuration from file " + hibernateConfig + "...");
918           return configLoader.loadConfigXmlFile(file);
919         }
920         else
921         {
922           return configLoader.loadConfigXmlResource(hibernateConfig);
923         }
924       }
925       catch (ConfigurationException e)
926       {
927         getLog().error("Error while reading configuration!", e);
928         throw new MojoExecutionException(e.getMessage());
929       }
930     }
931   }
932 
933   private void configure(Properties properties, ModificationTracker tracker)
934       throws MojoFailureException
935   {
936     /**
937      * Special treatment for the configuration-value "execute": if it is
938      * switched to "true", the genearation fo the schema should be forced!
939      */
940     if (tracker.check(EXECUTE, execute.toString()) && execute)
941     {
942       getLog().info(
943           "hibernate.schema.execute was switched on: " +
944           "forcing generation/execution of SQL"
945           );
946       tracker.touch();
947     }
948     configure(properties, execute, EXECUTE);
949 
950     /**
951      * Configure the generation of the SQL.
952      * Overwrite values from properties-file if the configuration parameter is
953      * known to Hibernate.
954      */
955     configure(properties, dialect, DIALECT);
956     configure(properties, delimiter, HBM2DDL_DELIMITER);
957     configure(properties, format, FORMAT_SQL);
958     configure(properties, createNamespaces, HBM2DLL_CREATE_NAMESPACES);
959     configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY);
960     configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY);
961     configure(properties, outputDirectory, OUTPUTDIRECTORY);
962     configure(properties, scanDependencies, SCAN_DEPENDENCIES);
963     configure(properties, scanTestClasses, SCAN_TESTCLASSES);
964     configure(properties, testOutputDirectory, TEST_OUTPUTDIRECTORY);
965 
966     /**
967      * Special treatment for the configuration-value "show": a change of its
968      * configured value should not lead to a regeneration of the database
969      * schama!
970      */
971     if (show == null)
972       show = Boolean.valueOf(properties.getProperty(SHOW_SQL));
973     else
974       properties.setProperty(SHOW_SQL, show.toString());
975 
976     /**
977      * Configure the connection parameters.
978      * Overwrite values from properties-file.
979      */
980     configure(properties, driver, DRIVER, JPA_JDBC_DRIVER);
981     configure(properties, url, URL, JPA_JDBC_URL);
982     configure(properties, username, USER, JPA_JDBC_USER);
983     configure(properties, password, PASS, JPA_JDBC_PASSWORD);
984 
985     if (properties.isEmpty())
986     {
987       getLog().error("No properties set!");
988       throw new MojoFailureException("Hibernate configuration is missing!");
989     }
990 
991     getLog().info("Gathered configuration:");
992     for (Entry<Object,Object> entry : properties.entrySet())
993       getLog().info("  " + entry.getKey() + " = " + entry.getValue());
994   }
995 
996   private void configure(
997       Properties properties,
998       String value,
999       String key,
1000       String alternativeKey
1001       )
1002   {
1003     configure(properties, value, key);
1004 
1005     if (properties.containsKey(alternativeKey))
1006     {
1007       if (properties.containsKey(key))
1008       {
1009         getLog().warn(
1010             "Ignoring property " + alternativeKey + "=\"" +
1011             properties.getProperty(alternativeKey) +
1012             "\" in favour for property " + key + "=\"" +
1013             properties.getProperty(key) + "\""
1014             );
1015         properties.remove(alternativeKey);
1016       }
1017       else
1018       {
1019         value = properties.getProperty(alternativeKey);
1020         properties.remove(alternativeKey);
1021         getLog().info(
1022             "Using value \"" + value + "\" from property " + alternativeKey +
1023             " for property " + key
1024             );
1025         properties.setProperty(key, value);
1026       }
1027     }
1028   }
1029 
1030   private void configure(Properties properties, String value, String key)
1031   {
1032     if (value != null)
1033     {
1034       if (properties.containsKey(key))
1035       {
1036         if (!properties.getProperty(key).equals(value))
1037         {
1038           getLog().info(
1039               "Overwriting property " + key + "=\"" +
1040               properties.getProperty(key) +
1041               "\" with value \"" + value + "\""
1042               );
1043           properties.setProperty(key, value);
1044         }
1045       }
1046       else
1047       {
1048         getLog().debug("Using value \"" + value + "\" for property " + key);
1049         properties.setProperty(key, value);
1050       }
1051     }
1052   }
1053 
1054   private void configure(Properties properties, Boolean value, String key)
1055   {
1056     configure(properties, value == null ? null : value.toString(), key);
1057   }
1058 
1059   private File getOutputFile(String filename)
1060       throws
1061         MojoExecutionException
1062   {
1063     File output = new File(filename);
1064 
1065     if (!output.isAbsolute())
1066     {
1067       // Interpret relative file path relative to build directory
1068       output = new File(buildDirectory, filename);
1069     }
1070     getLog().debug("Output file: " + output.getPath());
1071 
1072     // Ensure that directory path for specified file exists
1073     File outFileParentDir = output.getParentFile();
1074     if (null != outFileParentDir && !outFileParentDir.exists())
1075     {
1076       try
1077       {
1078         getLog().info(
1079             "Creating directory path for output file:" +
1080             outFileParentDir.getPath()
1081             );
1082         outFileParentDir.mkdirs();
1083       }
1084       catch (Exception e)
1085       {
1086         String error =
1087             "Error creating directory path for output file: " + e.getMessage();
1088         getLog().error(error);
1089         throw new MojoExecutionException(error);
1090       }
1091     }
1092 
1093     try
1094     {
1095       output.createNewFile();
1096     }
1097     catch (IOException e)
1098     {
1099       String error = "Error creating output file: " + e.getMessage();
1100       getLog().error(error);
1101       throw new MojoExecutionException(error);
1102     }
1103 
1104     if (!output.canWrite())
1105     {
1106       String error =
1107           "Output file " + output.getAbsolutePath() + " is not writable!";
1108       getLog().error(error);
1109       throw new MojoExecutionException(error);
1110     }
1111 
1112     return output;
1113   }
1114 
1115   private void checkOutputFile(File output, ModificationTracker tracker)
1116       throws
1117         MojoExecutionException
1118   {
1119     try
1120     {
1121       if (output.exists())
1122         tracker.track(SCRIPT, new FileInputStream(output));
1123       else
1124         tracker.track(SCRIPT, ZonedDateTime.now().toString());
1125     }
1126     catch (IOException e)
1127     {
1128       String error =
1129           "Error while checking the generated script: " + e.getMessage();
1130       getLog().error(error);
1131       throw new MojoExecutionException(error);
1132     }
1133   }
1134 
1135   private void addMappings(MetadataSources sources, ModificationTracker tracker)
1136       throws MojoFailureException
1137   {
1138     getLog().debug("Adding explicitly configured mappings...");
1139     if (mappings != null)
1140     {
1141       try
1142       {
1143         for (String filename : mappings.split("[\\s,]+"))
1144         {
1145           // First try the filename as absolute/relative path
1146           File file = new File(filename);
1147           if (!file.exists())
1148           {
1149             // If the file was not found, search for it in the resource-directories
1150             for (Resource resource : project.getResources())
1151             {
1152               file = new File(resource.getDirectory() + File.separator + filename);
1153               if (file.exists())
1154                 break;
1155             }
1156           }
1157           if (file.exists())
1158           {
1159             if (file.isDirectory())
1160               // TODO: add support to read all mappings under a directory
1161               throw new MojoFailureException(file.getAbsolutePath() + " is a directory");
1162             if (tracker.track(filename, new FileInputStream(file)))
1163               getLog().debug("Found new or modified mapping-file: " + filename);
1164             else
1165               getLog().debug("Mapping-file unchanged: " + filename);
1166 
1167             sources.addFile(file);
1168           }
1169           else
1170             throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
1171         }
1172       }
1173       catch (IOException e)
1174       {
1175         throw new MojoFailureException("Cannot calculate MD5 sums!", e);
1176       }
1177     }
1178   }
1179 
1180   private void addRoot(Set<URL> urls, String path) throws MojoFailureException
1181   {
1182     try
1183     {
1184       File dir = new File(path);
1185       if (dir.exists())
1186       {
1187         getLog().info("Adding " + dir.getAbsolutePath() + " to the list of roots to scan...");
1188         urls.add(dir.toURI().toURL());
1189       }
1190       else
1191         getLog().warn(
1192             "the directory cannot be scanned for annotated classes, " +
1193             "because it does not exist: " +
1194             dir.getAbsolutePath()
1195             );
1196     }
1197     catch (MalformedURLException e)
1198     {
1199       getLog().error("error while adding the project-root to the list of roots to scan!", e);
1200       throw new MojoFailureException(e.getMessage());
1201     }
1202   }
1203 
1204   private void addDependencies(Set<URL> urls) throws MojoFailureException
1205   {
1206     try
1207     {
1208       if (scanDependencies != null)
1209       {
1210         Matcher matcher = SPLIT.matcher(scanDependencies);
1211         while (matcher.find())
1212         {
1213           getLog().info("Adding dependencies from scope " + matcher.group() + " to the list of roots to scan");
1214           for (Artifact artifact : project.getDependencyArtifacts())
1215           {
1216             if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
1217               continue;
1218             if (artifact.getFile() == null)
1219             {
1220               getLog().warn("Cannot add dependency " + artifact.getId() + ": no JAR-file available!");
1221               continue;
1222             }
1223             getLog().info("Adding dependencies from scope " + artifact.getId() + " to the list of roots to scan");
1224             urls.add(artifact.getFile().toURI().toURL());
1225           }
1226         }
1227       }
1228     }
1229     catch (MalformedURLException e)
1230     {
1231       getLog().error("Error while adding dependencies to the list of roots to scan!", e);
1232       throw new MojoFailureException(e.getMessage());
1233     }
1234   }
1235 
1236   private Set<String> scanUrls(Set<URL> scanRoots)
1237       throws
1238         MojoFailureException
1239   {
1240     try
1241     {
1242       AnnotationDB db = new AnnotationDB();
1243       for (URL root : scanRoots)
1244         db.scanArchives(root);
1245 
1246       Set<String> classes = new HashSet<String>();
1247       if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
1248         classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
1249       if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
1250         classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
1251       if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
1252         classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
1253 
1254       return classes;
1255     }
1256     catch (Exception e)
1257     {
1258       getLog().error("Error while scanning!", e);
1259       throw new MojoFailureException(e.getMessage());
1260     }
1261   }
1262 
1263   private void addAnnotated(
1264       String name,
1265       MetadataSources sources,
1266       ClassLoaderService classLoaderService,
1267       ModificationTracker tracker
1268       )
1269       throws
1270         MojoFailureException,
1271         MojoExecutionException
1272   {
1273     try
1274     {
1275       getLog().info("Adding annotated resource: " + name);
1276       String packageName = null;
1277 
1278       boolean error = false;
1279       try
1280       {
1281         Class<?> annotatedClass = classLoaderService.classForName(name);
1282         String resourceName = annotatedClass.getName();
1283         resourceName =
1284             resourceName.substring(
1285                 resourceName.lastIndexOf(".") + 1,
1286                 resourceName.length()
1287                 ) + ".class";
1288         InputStream is = annotatedClass.getResourceAsStream(resourceName);
1289         if (is != null)
1290         {
1291           if (tracker.track(name, is))
1292             getLog().debug("New or modified class: " + name);
1293           else
1294             getLog().debug("Unchanged class: " + name);
1295           sources.addAnnotatedClass(annotatedClass);
1296           packageName = annotatedClass.getPackage().getName();
1297         }
1298         else
1299         {
1300           getLog().error("cannot find ressource " + resourceName + " for class " + name);
1301           error = true;
1302         }
1303       }
1304       catch(ClassLoadingException e)
1305       {
1306         packageName = name;
1307       }
1308       if (error)
1309       {
1310         throw new MojoExecutionException("error while inspecting annotated class " + name);
1311       }
1312 
1313       while (packageName != null)
1314       {
1315         if (packages.contains(packageName))
1316           return;
1317         String resource = packageName.replace('.', '/') + "/package-info.class";
1318         InputStream is = classLoaderService.locateResourceStream(resource);
1319         if (is == null)
1320         {
1321           // No compiled package-info available: no package-level annotations!
1322           getLog().debug("Package " + packageName + " is not annotated.");
1323         }
1324         else
1325         {
1326           if (tracker.track(packageName, is))
1327             getLog().debug("New or modified package: " + packageName);
1328           else
1329            getLog().debug("Unchanged package: " + packageName);
1330           getLog().info("Adding annotations from package " + packageName);
1331           sources.addPackage(packageName);
1332         }
1333         packages.add(packageName);
1334         int i = packageName.lastIndexOf('.');
1335         if (i < 0)
1336           packageName = null;
1337         else
1338           packageName = packageName.substring(0,i);
1339       }
1340     }
1341     catch (Exception e)
1342     {
1343       getLog().error("Error while adding the annotated class " + name, e);
1344       throw new MojoFailureException(e.getMessage());
1345     }
1346   }
1347 
1348   private ParsedPersistenceXmlDescriptor loadPersistenceUnit(
1349       ClassLoaderService classLoaderService,
1350       Properties properties
1351       )
1352       throws
1353         MojoFailureException
1354   {
1355     PersistenceXmlParser parser =
1356         new PersistenceXmlParser(
1357             classLoaderService,
1358             PersistenceUnitTransactionType.RESOURCE_LOCAL
1359              );
1360 
1361     Map<String, ParsedPersistenceXmlDescriptor> units =
1362         parser.doResolve(properties);
1363 
1364     if (persistenceUnit == null)
1365     {
1366       Iterator<String> names = units.keySet().iterator();
1367       if (!names.hasNext())
1368       {
1369         getLog().info("Found no META-INF/persistence.xml.");
1370         return null;
1371       }
1372 
1373       String name = names.next();
1374       if (!names.hasNext())
1375       {
1376           getLog().info("Using persistence-unit " + name);
1377           return units.get(name);
1378       }
1379 
1380       StringBuilder builder = new StringBuilder();
1381       builder.append("No name provided and multiple persistence units found: ");
1382       builder.append(name);
1383       while(names.hasNext())
1384       {
1385         builder.append(", ");
1386         builder.append(names.next());
1387       }
1388       builder.append('.');
1389       throw new MojoFailureException(builder.toString());
1390     }
1391 
1392     if (units.containsKey(persistenceUnit))
1393     {
1394       getLog().info("Using configured persistence-unit " + persistenceUnit);
1395       return units.get(persistenceUnit);
1396     }
1397 
1398     throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit);
1399   }
1400 
1401 
1402   public static void printStrackTrace(StringBuilder builder, Throwable t)
1403   {
1404     while (t != null)
1405     {
1406       builder.append("\n\tCause: ");
1407       builder.append(t.getMessage() == null ? "" : t.getMessage().replaceAll("\\s+", " "));
1408       for (StackTraceElement trace : t.getStackTrace())
1409       {
1410         builder.append("\n\t");
1411         builder.append(trace.getClassName());
1412         builder.append(".");
1413         builder.append(trace.getMethodName());
1414         builder.append("():");
1415         builder.append(trace.getLineNumber());
1416       }
1417       t = t.getCause();
1418     }
1419   }
1420 }