d50a1e7f2148b92728e1a8e5354bf9f0b08ef0e2
[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.IOException;
8 import java.io.InputStream;
9 import java.net.URL;
10 import java.net.URLClassLoader;
11 import java.security.NoSuchAlgorithmException;
12 import java.util.Collections;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Properties;
20 import java.util.Set;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 import javax.persistence.Embeddable;
24 import javax.persistence.Entity;
25 import javax.persistence.MappedSuperclass;
26 import javax.persistence.spi.PersistenceUnitTransactionType;
27 import org.apache.maven.artifact.Artifact;
28 import org.apache.maven.model.Resource;
29 import org.apache.maven.plugin.AbstractMojo;
30 import org.apache.maven.plugin.MojoExecutionException;
31 import org.apache.maven.plugin.MojoFailureException;
32 import org.apache.maven.project.MavenProject;
33 import org.hibernate.boot.MetadataBuilder;
34 import org.hibernate.boot.MetadataSources;
35 import org.hibernate.boot.cfgxml.internal.ConfigLoader;
36 import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
37 import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
38 import org.hibernate.boot.registry.BootstrapServiceRegistry;
39 import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
40 import org.hibernate.boot.registry.StandardServiceRegistry;
41 import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
42 import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
43 import org.hibernate.boot.registry.selector.spi.StrategySelector;
44 import org.hibernate.boot.spi.MetadataImplementor;
45 import static org.hibernate.cfg.AvailableSettings.DIALECT;
46 import static org.hibernate.cfg.AvailableSettings.DRIVER;
47 import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL;
48 import static org.hibernate.cfg.AvailableSettings.HBM2DLL_CREATE_NAMESPACES;
49 import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY;
50 import static org.hibernate.cfg.AvailableSettings.PASS;
51 import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY;
52 import static org.hibernate.cfg.AvailableSettings.SHOW_SQL;
53 import static org.hibernate.cfg.AvailableSettings.USER;
54 import static org.hibernate.cfg.AvailableSettings.URL;
55 import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
56 import org.hibernate.internal.util.config.ConfigurationException;
57 import static org.hibernate.jpa.AvailableSettings.JDBC_DRIVER;
58 import static org.hibernate.jpa.AvailableSettings.JDBC_PASSWORD;
59 import static org.hibernate.jpa.AvailableSettings.JDBC_URL;
60 import static org.hibernate.jpa.AvailableSettings.JDBC_USER;
61 import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
62 import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
63 import org.hibernate.jpa.boot.spi.ProviderChecker;
64 import org.scannotation.AnnotationDB;
65
66
67 /**
68  * Baseclass with common attributes and methods.
69  *
70  * @phase process-classes
71  * @threadSafe
72  * @requiresDependencyResolution runtime
73  */
74 public abstract class AbstractSchemaMojo extends AbstractMojo
75 {
76   public final static String EXPORT = "hibernate.schema.export";
77   public final static String DELIMITER = "hibernate.schema.delimiter";
78   public final static String OUTPUTDIRECTORY = "project.build.outputDirectory";
79   public final static String SCAN_DEPENDENCIES = "hibernate.schema.scan.dependencies";
80   public final static String SCAN_TESTCLASSES = "hibernate.schema.scan.test_classes";
81   public final static String TEST_OUTPUTDIRECTORY = "project.build.testOutputDirectory";
82   public final static String SKIPPED = "hibernate.schema.skipped";
83
84   private final static Pattern SPLIT = Pattern.compile("[^,\\s]+");
85
86
87   /**
88    * The maven project.
89    * <p>
90    * Only needed internally.
91    *
92    * @parameter property="project"
93    * @required
94    * @readonly
95    */
96   private MavenProject project;
97
98   /**
99    * Build-directory.
100    * <p>
101    * Only needed internally.
102    *
103    * @parameter property="project.build.directory"
104    * @required
105    * @readonly
106    */
107   String buildDirectory;
108
109
110   /** Parameters to configure the genaration of the SQL *********************/
111
112   /**
113    * Export the database-schma to the database.
114    * If set to <code>false</code>, only the SQL-script is created and the
115    * database is not touched.
116    * <p>
117    * <strong>Important:</strong>
118    * This configuration value can only be configured through the
119    * <code>pom.xml</code>, or by the definition of a system-property, because
120    * it is not known by Hibernate nor JPA and, hence, not picked up from
121    * their configuration!
122    *
123    * @parameter property="hibernate.schema.export" default-value="true"
124    * @since 2.0
125    */
126   Boolean export;
127
128   /**
129    * Skip execution
130    * <p>
131    * If set to <code>true</code>, the execution is skipped.
132    * <p>
133    * A skipped execution is signaled via the maven-property
134    * <code>${hibernate.export.skipped}</code>.
135    * <p>
136    * The execution is skipped automatically, if no modified or newly added
137    * annotated classes are found and the dialect was not changed.
138    * <p>
139    * <strong>Important:</strong>
140    * This configuration value can only be configured through the
141    * <code>pom.xml</code>, or by the definition of a system-property, because
142    * it is not known by Hibernate nor JPA and, hence, not picked up from
143    * their configuration!
144    *
145    * @parameter property="hibernate.schema.skip" default-value="${maven.test.skip}"
146    * @since 1.0
147    */
148   private boolean skip;
149
150   /**
151    * Force execution
152    * <p>
153    * Force execution, even if no modified or newly added annotated classes
154    * where found and the dialect was not changed.
155    * <p>
156    * <code>skip</code> takes precedence over <code>force</code>.
157    * <p>
158    * <strong>Important:</strong>
159    * This configuration value can only be configured through the
160    * <code>pom.xml</code>, or by the definition of a system-property, because
161    * it is not known by Hibernate nor JPA and, hence, not picked up from
162    * their configuration!
163    *
164    * @parameter property="hibernate.schema.force" default-value="false"
165    * @since 1.0
166    */
167   private boolean force;
168
169   /**
170    * Hibernate dialect.
171    *
172    * @parameter property="hibernate.dialect"
173    * @since 1.0
174    */
175   private String dialect;
176
177   /**
178    * Delimiter in output-file.
179    * <p>
180    * <strong>Important:</strong>
181    * This configuration value can only be configured through the
182    * <code>pom.xml</code>, or by the definition of a system-property, because
183    * it is not known by Hibernate nor JPA and, hence, not picked up from
184    * their configuration!
185    *
186    * @parameter property="hibernate.schema.delimiter" default-value=";"
187    * @since 1.0
188    */
189   String delimiter;
190
191   /**
192    * Show the generated SQL in the command-line output.
193    *
194    * @parameter property="hibernate.show_sql"
195    * @since 1.0
196    */
197   Boolean show;
198
199   /**
200    * Format output-file.
201    *
202    * @parameter property="hibernate.format_sql"
203    * @since 1.0
204    */
205   Boolean format;
206
207   /**
208    * Specifies whether to automatically create also the database schema/catalog.
209    *
210    * @parameter property="hibernate.hbm2dll.create_namespaces" default-value="false"
211    * @since 2.0
212    */
213   Boolean createNamespaces;
214
215   /**
216    * Implicit naming strategy
217    *
218    * @parameter property="hibernate.implicit_naming_strategy"
219    * @since 2.0
220    */
221   private String implicitNamingStrategy;
222
223   /**
224    * Physical naming strategy
225    *
226    * @parameter property="hibernate.physical_naming_strategy"
227    * @since 2.0
228    */
229   private String physicalNamingStrategy;
230
231   /**
232    * Classes-Directory to scan.
233    * <p>
234    * This parameter defaults to the maven build-output-directory for classes.
235    * Additionally, all dependencies are scanned for annotated classes.
236    * <p>
237    * <strong>Important:</strong>
238    * This configuration value can only be configured through the
239    * <code>pom.xml</code>, or by the definition of a system-property, because
240    * it is not known by Hibernate nor JPA and, hence, not picked up from
241    * their configuration!
242    *
243    * @parameter property="project.build.outputDirectory"
244    * @since 1.0
245    */
246   private String outputDirectory;
247
248   /**
249    * Dependency-Scopes, that should be scanned for annotated classes.
250    * <p>
251    * By default, only dependencies in the scope <code>compile</code> are
252    * scanned for annotated classes. Multiple scopes can be seperated by
253    * white space or commas.
254    * <p>
255    * If you do not want any dependencies to be scanned for annotated
256    * classes, set this parameter to <code>none</code>.
257    * <p>
258    * The plugin does not scan for annotated classes in transitive
259    * dependencies. If some of your annotated classes are hidden in a
260    * transitive dependency, you can simply add that dependency explicitly.
261    *
262    * @parameter property="hibernate.schema.scan.dependencies" default-value="compile"
263    * @since 1.0.3
264    */
265   private String scanDependencies;
266
267   /**
268    * Whether to scan test-classes too, or not.
269    * <p>
270    * If this parameter is set to <code>true</code> the test-classes of the
271    * artifact will be scanned for hibernate-annotated classes additionally.
272    * <p>
273    * <strong>Important:</strong>
274    * This configuration value can only be configured through the
275    * <code>pom.xml</code>, or by the definition of a system-property, because
276    * it is not known by Hibernate nor JPA and, hence, not picked up from
277    * their configuration!
278    *
279    * @parameter property="hibernate.schema.scan.test_classes" default-value="false"
280    * @since 1.0.1
281    */
282   private Boolean scanTestClasses;
283
284   /**
285    * Test-Classes-Directory to scan.
286    * <p>
287    * This parameter defaults to the maven build-output-directory for
288    * test-classes.
289    * <p>
290    * This parameter is only used, when <code>scanTestClasses</code> is set
291    * to <code>true</code>!
292    * <p>
293    * <strong>Important:</strong>
294    * This configuration value can only be configured through the
295    * <code>pom.xml</code>, or by the definition of a system-property, because
296    * it is not known by Hibernate nor JPA and, hence, not picked up from
297    * their configuration!
298    *
299    * @parameter property="project.build.testOutputDirectory"
300    * @since 1.0.2
301    */
302   private String testOutputDirectory;
303
304
305   /** Conection parameters *************************************************/
306
307   /**
308    * SQL-Driver name.
309    *
310    * @parameter property="hibernate.connection.driver_class"
311    * @since 1.0
312    */
313   private String driver;
314
315   /**
316    * Database URL.
317    *
318    * @parameter property="hibernate.connection.url"
319    * @since 1.0
320    */
321   private String url;
322
323   /**
324    * Database username
325    *
326    * @parameter property="hibernate.connection.username"
327    * @since 1.0
328    */
329   private String username;
330
331   /**
332    * Database password
333    *
334    * @parameter property="hibernate.connection.password"
335    * @since 1.0
336    */
337   private String password;
338
339
340   /** Parameters to locate configuration sources ****************************/
341
342   /**
343    * Path to a file or name of a ressource with hibernate properties.
344    * If this parameter is specified, the plugin will try to load configuration
345    * values from a file with the given path or a ressource on the classpath with
346    * the given name. If both fails, the execution of the plugin will fail.
347    * <p>
348    * If this parameter is not set the plugin will load configuration values
349    * from a ressource named <code>hibernate.properties</code> on the classpath,
350    * if it is present, but will not fail if there is no such ressource.
351    * <p>
352    * During ressource-lookup, the test-classpath takes precedence.
353    *
354    * @parameter
355    * @since 1.0
356    */
357   private String hibernateProperties;
358
359   /**
360    * Path to Hibernate configuration file (.cfg.xml).
361    * If this parameter is specified, the plugin will try to load configuration
362    * values from a file with the given path or a ressource on the classpath with
363    * the given name. If both fails, the execution of the plugin will fail.
364    * <p>
365    * If this parameter is not set the plugin will load configuration values
366    * from a ressource named <code>hibernate.cfg.xml</code> on the classpath,
367    * if it is present, but will not fail if there is no such ressource.
368    * <p>
369    * During ressource-lookup, the test-classpath takes precedence.
370    * <p>
371    * Settings in this file will overwrite settings in the properties file.
372    *
373    * @parameter
374    * @since 1.1.0
375    */
376   private String hibernateConfig;
377
378   /**
379    * Name of the persistence-unit.
380    * If this parameter is specified, the plugin will try to load configuration
381    * values from a persistence-unit with the specified name. If no such
382    * persistence-unit can be found, the plugin will throw an exception.
383    * <p>
384    * If this parameter is not set and there is only one persistence-unit
385    * available, that unit will be used automatically. But if this parameter is
386    * not set and there are multiple persistence-units available on,
387    * the class-path, the execution of the plugin will fail.
388    * <p>
389    * Settings in this file will overwrite settings in the properties or the
390    * configuration file.
391    *
392    * @parameter
393    * @since 1.1.0
394    */
395   private String persistenceUnit;
396
397   /**
398    * List of Hibernate-Mapping-Files (XML).
399    * Multiple files can be separated with white-spaces and/or commas.
400    *
401    * @parameter property="hibernate.mapping"
402    * @since 1.0.2
403    */
404   private String mappings;
405
406
407
408   public final void execute(String filename)
409     throws
410       MojoFailureException,
411       MojoExecutionException
412   {
413     if (skip)
414     {
415       getLog().info("Execution of hibernate-maven-plugin was skipped!");
416       project.getProperties().setProperty(SKIPPED, "true");
417       return;
418     }
419
420     ModificationTracker tracker;
421     try
422     {
423       tracker = new ModificationTracker(buildDirectory, filename, getLog());
424     }
425     catch (NoSuchAlgorithmException e)
426     {
427       throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
428     }
429
430     SimpleConnectionProvider connectionProvider =
431         new SimpleConnectionProvider(getLog());
432
433     try
434     {
435       /** Start extended logging */
436       MavenLogAppender.startPluginLog(this);
437
438       /** Load checksums for old mapping and configuration */
439       tracker.load();
440
441       /** Create the ClassLoader */
442       URLClassLoader classLoader = createClassLoader();
443
444       /** Create a BootstrapServiceRegistry with the created ClassLoader */
445       BootstrapServiceRegistry bootstrapServiceRegitry =
446           new BootstrapServiceRegistryBuilder()
447               .applyClassLoader(classLoader)
448               .build();
449       ClassLoaderService classLoaderService =
450           bootstrapServiceRegitry.getService(ClassLoaderService.class);
451
452       Properties properties = new Properties();
453       ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
454
455       /** Loading and merging configuration */
456       properties.putAll(loadProperties(configLoader));
457       properties.putAll(loadConfig(configLoader));
458       properties.putAll(loadPersistenceUnit(classLoaderService, properties));
459
460       /** Overwriting/Completing configuration */
461       configure(properties, tracker);
462
463       /** Check configuration for modifications */
464       if(tracker.track(properties))
465         getLog().debug("Configuration has changed.");
466       else
467         getLog().debug("Configuration unchanged.");
468
469       /** Configure Hibernate */
470       StandardServiceRegistry serviceRegistry =
471           new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
472               .applySettings(properties)
473               .addService(ConnectionProvider.class, connectionProvider)
474               .build();
475
476       /** Load Mappings */
477       MetadataSources sources = new MetadataSources(serviceRegistry);
478       addAnnotatedClasses(sources, classLoaderService, tracker);
479       addMappings(sources, tracker);
480
481       /** Skip execution, if mapping and configuration is unchanged */
482       if (!tracker.modified())
483       {
484         getLog().info(
485             "Mapping and configuration unchanged."
486             );
487         if (force)
488           getLog().info("Schema generation is forced!");
489         else
490         {
491           getLog().info("Skipping schema generation!");
492           project.getProperties().setProperty(SKIPPED, "true");
493           return;
494         }
495       }
496
497
498       /** Create a connection, if sufficient configuration infromation is available */
499       connectionProvider.open(classLoaderService, properties);
500
501       MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
502
503       StrategySelector strategySelector =
504           serviceRegistry.getService(StrategySelector.class);
505
506       if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
507       {
508         metadataBuilder.applyImplicitNamingStrategy(
509             strategySelector.resolveStrategy(
510                 ImplicitNamingStrategy.class,
511                 properties.getProperty(IMPLICIT_NAMING_STRATEGY)
512                 )
513             );
514       }
515
516       if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
517       {
518         metadataBuilder.applyPhysicalNamingStrategy(
519             strategySelector.resolveStrategy(
520                 PhysicalNamingStrategy.class,
521                 properties.getProperty(PHYSICAL_NAMING_STRATEGY)
522                 )
523             );
524       }
525
526       /**
527        * Change class-loader of current thread.
528        * This is necessary, because still not all parts of Hibernate 5 use
529        * the newly introduced ClassLoaderService and will fail otherwise!
530        */
531       Thread thread = Thread.currentThread();
532       ClassLoader contextClassLoader = thread.getContextClassLoader();
533       try
534       {
535         thread.setContextClassLoader(classLoader);
536         build((MetadataImplementor)metadataBuilder.build());
537       }
538       finally
539       {
540         thread.setContextClassLoader(contextClassLoader);
541       }
542     }
543     finally
544     {
545       /** Remember mappings and configuration */
546       tracker.save();
547
548       /** Close the connection - if one was opened */
549       connectionProvider.close();
550
551       /** Stop Log-Capturing */
552       MavenLogAppender.endPluginLog(this);
553     }
554   }
555
556
557   abstract void build(MetadataImplementor metadata)
558     throws
559       MojoFailureException,
560       MojoExecutionException;
561
562
563   private URLClassLoader createClassLoader() throws MojoExecutionException
564   {
565     try
566     {
567       getLog().debug("Creating ClassLoader for project-dependencies...");
568       List<String> classpathFiles = project.getCompileClasspathElements();
569       if (scanTestClasses)
570         classpathFiles.addAll(project.getTestClasspathElements());
571       List<URL> urls = new LinkedList<URL>();
572       File file;
573       file = new File(testOutputDirectory);
574       if (!file.exists())
575       {
576         getLog().info("creating test-output-directory: " + testOutputDirectory);
577         file.mkdirs();
578       }
579       urls.add(file.toURI().toURL());
580       file = new File(outputDirectory);
581       if (!file.exists())
582       {
583         getLog().info("creating output-directory: " + outputDirectory);
584         file.mkdirs();
585       }
586       urls.add(file.toURI().toURL());
587       for (String pathElement : classpathFiles)
588       {
589         getLog().debug("Dependency: " + pathElement);
590         urls.add(new File(pathElement).toURI().toURL());
591       }
592       return
593           new URLClassLoader(
594               urls.toArray(new URL[urls.size()]),
595               getClass().getClassLoader()
596               );
597     }
598     catch (Exception e)
599     {
600       getLog().error("Error while creating ClassLoader!", e);
601       throw new MojoExecutionException(e.getMessage());
602     }
603   }
604
605   private Map loadProperties(ConfigLoader configLoader)
606       throws
607         MojoExecutionException
608   {
609     /** Try to read configuration from properties-file */
610     if (hibernateProperties == null)
611     {
612       try
613       {
614         return configLoader.loadProperties("hibernate.properties");
615       }
616       catch (ConfigurationException e)
617       {
618         getLog().debug(e.getMessage());
619         return Collections.EMPTY_MAP;
620       }
621     }
622     else
623     {
624       try
625       {
626         File file = new File(hibernateProperties);
627         if (file.exists())
628         {
629           getLog().info("Reading settings from file " + hibernateProperties + "...");
630           return configLoader.loadProperties(file);
631         }
632         else
633           return configLoader.loadProperties(hibernateProperties);
634       }
635       catch (ConfigurationException e)
636       {
637         getLog().error("Error while reading properties!", e);
638         throw new MojoExecutionException(e.getMessage());
639       }
640     }
641   }
642
643   private Map loadConfig(ConfigLoader configLoader)
644       throws MojoExecutionException
645   {
646     /** Try to read configuration from configuration-file */
647     if (hibernateConfig == null)
648     {
649       try
650       {
651         return
652             configLoader
653                 .loadConfigXmlResource("hibernate.cfg.xml")
654                 .getConfigurationValues();
655       }
656       catch (ConfigurationException e)
657       {
658         getLog().debug(e.getMessage());
659         return Collections.EMPTY_MAP;
660       }
661     }
662     else
663     {
664       try
665       {
666         File file = new File(hibernateConfig);
667         if (file.exists())
668         {
669           getLog().info("Reading configuration from file " + hibernateConfig + "...");
670           return configLoader.loadConfigXmlFile(file).getConfigurationValues();
671         }
672         else
673           return
674               configLoader
675                   .loadConfigXmlResource(hibernateConfig)
676                   .getConfigurationValues();
677       }
678       catch (ConfigurationException e)
679       {
680         getLog().error("Error while reading configuration!", e);
681         throw new MojoExecutionException(e.getMessage());
682       }
683     }
684   }
685
686   private void configure(Properties properties, ModificationTracker tracker)
687       throws MojoFailureException
688   {
689     /**
690      * Special treatment for the configuration-value "export": if it is
691      * switched to "true", the genearation fo the schema should be forced!
692      */
693     if (tracker.check(EXPORT, export.toString()) && export)
694       tracker.touch();
695
696     /**
697      * Configure the generation of the SQL.
698      * Overwrite values from properties-file if the configuration parameter is
699      * known to Hibernate.
700      */
701     dialect = configure(properties, dialect, DIALECT);
702     tracker.track(DELIMITER, delimiter); // << not reflected in hibernate configuration!
703     format = configure(properties, format, FORMAT_SQL);
704     createNamespaces = configure(properties, createNamespaces, HBM2DLL_CREATE_NAMESPACES);
705     implicitNamingStrategy = configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY);
706     physicalNamingStrategy = configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY);
707     tracker.track(OUTPUTDIRECTORY, outputDirectory); // << not reflected in hibernate configuration!
708     tracker.track(SCAN_DEPENDENCIES, scanDependencies); // << not reflected in hibernate configuration!
709     tracker.track(SCAN_TESTCLASSES, scanTestClasses.toString()); // << not reflected in hibernate configuration!
710     tracker.track(TEST_OUTPUTDIRECTORY, testOutputDirectory); // << not reflected in hibernate configuration!
711
712     /**
713      * Special treatment for the configuration-value "show": a change of its
714      * configured value should not lead to a regeneration of the database
715      * schama!
716      */
717     if (show == null)
718       show = Boolean.valueOf(properties.getProperty(SHOW_SQL));
719     else
720       properties.setProperty(SHOW_SQL, show.toString());
721
722     /**
723      * Configure the connection parameters.
724      * Overwrite values from properties-file.
725      */
726     driver = configure(properties, driver, DRIVER, JDBC_DRIVER);
727     url = configure(properties, url, URL, JDBC_URL);
728     username = configure(properties, username, USER, JDBC_USER);
729     password = configure(properties, password, PASS, JDBC_PASSWORD);
730
731     if (properties.isEmpty())
732     {
733       getLog().error("No properties set!");
734       throw new MojoFailureException("Hibernate configuration is missing!");
735     }
736
737     getLog().info("Gathered hibernate-configuration (turn on debugging for details):");
738     for (Entry<Object,Object> entry : properties.entrySet())
739       getLog().info("  " + entry.getKey() + " = " + entry.getValue());
740   }
741
742   private String configure(
743       Properties properties,
744       String value,
745       String key,
746       String alternativeKey
747       )
748   {
749     value = configure(properties, value, key);
750     if (value == null)
751       return properties.getProperty(alternativeKey);
752
753     if (properties.containsKey(alternativeKey))
754     {
755       getLog().warn(
756           "Ignoring property " + alternativeKey + "=" +
757           properties.getProperty(alternativeKey) + " in favour for property " +
758           key + "=" + properties.getProperty(key)
759           );
760       properties.remove(alternativeKey);
761     }
762     return properties.getProperty(alternativeKey);
763   }
764
765   private String configure(Properties properties, String value, String key)
766   {
767     if (value != null)
768     {
769       if (properties.containsKey(key))
770         getLog().debug(
771             "Overwriting property " + key + "=" + properties.getProperty(key) +
772             " with the value " + value
773             );
774       else
775         getLog().debug("Using the value " + value + " for property " + key);
776       properties.setProperty(key, value);
777     }
778     return properties.getProperty(key);
779   }
780
781   private boolean configure(Properties properties, Boolean value, String key)
782   {
783     if (value != null)
784     {
785       if (properties.containsKey(key))
786         getLog().debug(
787             "Overwriting property " + key + "=" + properties.getProperty(key) +
788             " with the value " + value
789             );
790       else
791         getLog().debug("Using the value " + value + " for property " + key);
792       properties.setProperty(key, value.toString());
793     }
794     return Boolean.valueOf(properties.getProperty(key));
795   }
796
797   private void addMappings(MetadataSources sources, ModificationTracker tracker)
798       throws MojoFailureException
799   {
800     getLog().debug("Adding explicitly configured mappings...");
801     if (mappings != null)
802     {
803       try
804       {
805         for (String filename : mappings.split("[\\s,]+"))
806         {
807           // First try the filename as absolute/relative path
808           File file = new File(filename);
809           if (!file.exists())
810           {
811             // If the file was not found, search for it in the resource-directories
812             for (Resource resource : project.getResources())
813             {
814               file = new File(resource.getDirectory() + File.separator + filename);
815               if (file.exists())
816                 break;
817             }
818           }
819           if (file.exists())
820           {
821             if (file.isDirectory())
822               // TODO: add support to read all mappings under a directory
823               throw new MojoFailureException(file.getAbsolutePath() + " is a directory");
824             if (tracker.track(filename, new FileInputStream(file)))
825               getLog().debug("Found new or modified mapping-file: " + filename);
826             else
827               getLog().debug("mapping-file unchanged: " + filename);
828
829             sources.addFile(file);
830           }
831           else
832             throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
833         }
834       }
835       catch (IOException e)
836       {
837         throw new MojoFailureException("Cannot calculate MD5 sums!", e);
838       }
839     }
840   }
841
842   private void addAnnotatedClasses(
843       MetadataSources sources,
844       ClassLoaderService classLoaderService,
845       ModificationTracker tracker
846       )
847       throws
848         MojoFailureException,
849         MojoExecutionException
850
851   {
852     try
853     {
854       AnnotationDB db = new AnnotationDB();
855       File dir;
856
857       dir = new File(outputDirectory);
858       if (dir.exists())
859       {
860         getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes...");
861         URL dirUrl = dir.toURI().toURL();
862         db.scanArchives(dirUrl);
863       }
864
865       if (scanTestClasses)
866       {
867         dir = new File(testOutputDirectory);
868         if (dir.exists())
869         {
870           getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes...");
871           URL dirUrl = dir.toURI().toURL();
872           db.scanArchives(dirUrl);
873         }
874       }
875
876       if (scanDependencies != null)
877       {
878         Matcher matcher = SPLIT.matcher(scanDependencies);
879         while (matcher.find())
880         {
881           getLog().info("Scanning dependencies for scope " + matcher.group());
882           for (Artifact artifact : project.getDependencyArtifacts())
883           {
884             if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
885               continue;
886             if (artifact.getFile() == null)
887             {
888               getLog().warn("Cannot scan dependency " + artifact.getId() + ": no JAR-file available!");
889               continue;
890             }
891             getLog().info("Scanning dependency " + artifact.getId() + " for annotated classes...");
892             db.scanArchives(artifact.getFile().toURI().toURL());
893           }
894         }
895       }
896
897       Set<String> classes = new HashSet<String>();
898       if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
899         classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
900       if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
901         classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
902       if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
903         classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
904
905       Set<String> packages = new HashSet<String>();
906
907       for (String name : classes)
908       {
909         Class<?> annotatedClass = classLoaderService.classForName(name);
910         String packageName = annotatedClass.getPackage().getName();
911         if (!packages.contains(packageName))
912         {
913           InputStream is =
914               annotatedClass.getResourceAsStream("package-info.class");
915           if (is == null)
916           {
917             // No compiled package-info available: no package-level annotations!
918             getLog().debug("Package " + packageName + " is not annotated.");
919           }
920           else
921           {
922             if (tracker.track(packageName, is))
923               getLog().debug("New or modified package: " + packageName);
924             else
925               getLog().debug("Unchanged package: " + packageName);
926             getLog().info("Adding annotated package " + packageName);
927             sources.addPackage(packageName);
928           }
929           packages.add(packageName);
930         }
931         String resourceName = annotatedClass.getName();
932         resourceName =
933             resourceName.substring(
934                 resourceName.lastIndexOf(".") + 1,
935                 resourceName.length()
936                 ) + ".class";
937         InputStream is =
938             annotatedClass
939                 .getResourceAsStream(resourceName);
940         if (tracker.track(name, is))
941           getLog().debug("New or modified class: " + name);
942         else
943           getLog().debug("Unchanged class: " + name);
944         getLog().info("Adding annotated class " + annotatedClass);
945         sources.addAnnotatedClass(annotatedClass);
946       }
947     }
948     catch (Exception e)
949     {
950       getLog().error("Error while scanning!", e);
951       throw new MojoFailureException(e.getMessage());
952     }
953   }
954
955   private Properties loadPersistenceUnit(
956       ClassLoaderService classLoaderService,
957       Properties properties
958       )
959       throws
960         MojoFailureException
961   {
962     PersistenceXmlParser parser =
963         new PersistenceXmlParser(
964             classLoaderService,
965             PersistenceUnitTransactionType.RESOURCE_LOCAL
966              );
967
968     List<ParsedPersistenceXmlDescriptor> units = parser.doResolve(properties);
969
970     if (persistenceUnit == null)
971     {
972       switch (units.size())
973       {
974         case 0:
975           getLog().info("Found no META-INF/persistence.xml.");
976           return new Properties();
977         case 1:
978           getLog().info("Using persistence-unit " + units.get(0).getName());
979           return units.get(0).getProperties();
980         default:
981           StringBuilder builder = new StringBuilder();
982           builder.append("No name provided and multiple persistence units found: ");
983           Iterator<ParsedPersistenceXmlDescriptor> it = units.iterator();
984           builder.append(it.next().getName());
985           while (it.hasNext())
986           {
987             builder.append(", ");
988             builder.append(it.next().getName());
989           }
990           builder.append('.');
991           throw new MojoFailureException(builder.toString());
992       }
993     }
994
995     for (ParsedPersistenceXmlDescriptor unit : units)
996     {
997       getLog().debug("Found persistence-unit " + unit.getName());
998       if (!unit.getName().equals(persistenceUnit))
999         continue;
1000
1001       // See if we (Hibernate) are the persistence provider
1002       if (!ProviderChecker.isProvider(unit, properties))
1003       {
1004         getLog().debug("Wrong provider: " + unit.getProviderClassName());
1005         continue;
1006       }
1007
1008       getLog().info("Using persistence-unit " + unit.getName());
1009       return unit.getProperties();
1010     }
1011
1012     throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit);
1013   }
1014 }