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