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