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