1 package de.juplo.plugins.hibernate;
4 import com.pyx4j.log4j.MavenLogAppender;
6 import java.io.FileInputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
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;
18 import java.util.Map.Entry;
19 import java.util.Properties;
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;
65 * Baseclass with common attributes and methods.
67 * @phase process-classes
69 * @requiresDependencyResolution runtime
71 public abstract class AbstractSchemaMojo extends AbstractMojo
73 public final static String EXPORT_SKIPPED_PROPERTY = "hibernate.export.skipped";
75 private final static Pattern SPLIT = Pattern.compile("[^,\\s]+");
81 * Only needed internally.
83 * @parameter property="project"
87 private MavenProject project;
92 * Only needed internally.
94 * @parameter property="project.build.directory"
98 String buildDirectory;
101 * Classes-Directory to scan.
103 * This parameter defaults to the maven build-output-directory for classes.
104 * Additionally, all dependencies are scanned for annotated classes.
106 * @parameter property="project.build.outputDirectory"
109 private String outputDirectory;
112 * Whether to scan test-classes too, or not.
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.
117 * @parameter property="hibernate.export.scan_testclasses" default-value="false"
120 private boolean scanTestClasses;
123 * Dependency-Scopes, that should be scanned for annotated classes.
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.
129 * If you do not want any dependencies to be scanned for annotated
130 * classes, set this parameter to <code>none</code>.
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.
136 * @parameter property="hibernate.export.scan_dependencies" default-value="compile"
139 private String scanDependencies;
142 * Test-Classes-Directory to scan.
144 * This parameter defaults to the maven build-output-directory for
147 * This parameter is only used, when <code>scanTestClasses</code> is set
148 * to <code>true</code>!
150 * @parameter property="project.build.testOutputDirectory"
153 private String testOutputDirectory;
158 * If set to <code>true</code>, the execution is skipped.
160 * A skipped execution is signaled via the maven-property
161 * <code>${hibernate.export.skipped}</code>.
163 * The execution is skipped automatically, if no modified or newly added
164 * annotated classes are found and the dialect was not changed.
166 * @parameter property="hibernate.skip" default-value="${maven.test.skip}"
169 private boolean skip;
174 * Force execution, even if no modified or newly added annotated classes
175 * where found and the dialect was not changed.
177 * <code>skip</code> takes precedence over <code>force</code>.
179 * @parameter property="hibernate.export.force" default-value="false"
182 private boolean force;
187 * @parameter property="hibernate.connection.driver_class"
190 private String driver;
195 * @parameter property="hibernate.connection.url"
203 * @parameter property="hibernate.connection.username"
206 private String username;
211 * @parameter property="hibernate.connection.password"
214 private String password;
219 * @parameter property="hibernate.dialect"
222 private String dialect;
225 * Implicit naming strategy
227 * @parameter property=IMPLICIT_NAMING_STRATEGY
230 private String implicitNamingStrategy;
233 * Physical naming strategy
235 * @parameter property=PHYSICAL_NAMING_STRATEGY
238 private String physicalNamingStrategy;
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.
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.
250 * During ressource-lookup, the test-classpath takes precedence.
255 private String hibernateProperties;
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.
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.
267 * During ressource-lookup, the test-classpath takes precedence.
269 * Settings in this file will overwrite settings in the properties file.
274 private String hibernateConfig;
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.
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.
287 * Settings in this file will overwrite settings in the properties or the
288 * configuration file.
293 private String persistenceUnit;
296 * List of Hibernate-Mapping-Files (XML).
297 * Multiple files can be separated with white-spaces and/or commas.
299 * @parameter property="hibernate.mapping"
302 private String mappings;
306 public final void execute()
308 MojoFailureException,
309 MojoExecutionException
313 getLog().info("Execution of hibernate-maven-plugin was skipped!");
314 project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
318 ModificationTracker tracker;
321 tracker = new ModificationTracker(buildDirectory, getLog());
323 catch (NoSuchAlgorithmException e)
325 throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
328 SimpleConnectionProvider connectionProvider =
329 new SimpleConnectionProvider(getLog());
333 /** Start extended logging */
334 MavenLogAppender.startPluginLog(this);
336 /** Load checksums for old mapping and configuration */
339 /** Create a BootstrapServiceRegistry with special ClassLoader */
340 BootstrapServiceRegistry bootstrapServiceRegitry =
341 new BootstrapServiceRegistryBuilder()
342 .applyClassLoader(createClassLoader())
344 ClassLoaderService classLoaderService =
345 bootstrapServiceRegitry.getService(ClassLoaderService.class);
347 Properties properties = new Properties();
348 ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
350 /** Loading and merging configuration */
351 properties.putAll(loadProperties(configLoader));
352 properties.putAll(loadConfig(configLoader));
353 properties.putAll(loadPersistenceUnit(classLoaderService, properties));
355 /** Overwriting/Completing configuration */
356 configure(properties);
358 /** Check configuration for modifications */
359 if(tracker.check(properties))
360 getLog().debug("Configuration has changed.");
362 getLog().debug("Configuration unchanged.");
364 /** Configure Hibernate */
365 StandardServiceRegistry serviceRegistry =
366 new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
367 .applySettings(properties)
368 .addService(ConnectionProvider.class, connectionProvider)
372 MetadataSources sources = new MetadataSources(serviceRegistry);
373 addAnnotatedClasses(sources, classLoaderService, tracker);
374 addMappings(sources, tracker);
376 /** Skip execution, if mapping and configuration is unchanged */
377 if (!tracker.modified())
380 "Mapping and configuration unchanged."
383 getLog().info("Schema generation is forced!");
386 getLog().info("Skipping schema generation!");
387 project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
393 /** Create a connection, if sufficient configuration infromation is available */
394 connectionProvider.open(classLoaderService, properties);
396 MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
398 StrategySelector strategySelector =
399 serviceRegistry.getService(StrategySelector.class);
401 if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
403 metadataBuilder.applyImplicitNamingStrategy(
404 strategySelector.resolveStrategy(
405 ImplicitNamingStrategy.class,
406 properties.getProperty(IMPLICIT_NAMING_STRATEGY)
411 if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
413 metadataBuilder.applyPhysicalNamingStrategy(
414 strategySelector.resolveStrategy(
415 PhysicalNamingStrategy.class,
416 properties.getProperty(PHYSICAL_NAMING_STRATEGY)
421 build((MetadataImplementor)metadataBuilder.build());
425 /** Remember mappings and configuration */
428 /** Close the connection - if one was opened */
429 connectionProvider.close();
431 /** Stop Log-Capturing */
432 MavenLogAppender.endPluginLog(this);
437 abstract void build(MetadataImplementor metadata)
439 MojoFailureException,
440 MojoExecutionException;
443 private URLClassLoader createClassLoader() throws MojoExecutionException
447 getLog().debug("Creating ClassLoader for project-dependencies...");
448 List<String> classpathFiles = project.getCompileClasspathElements();
450 classpathFiles.addAll(project.getTestClasspathElements());
451 List<URL> urls = new LinkedList<URL>();
453 file = new File(testOutputDirectory);
456 getLog().info("creating test-output-directory: " + testOutputDirectory);
459 urls.add(file.toURI().toURL());
460 file = new File(outputDirectory);
463 getLog().info("creating output-directory: " + outputDirectory);
466 urls.add(file.toURI().toURL());
467 for (String pathElement : classpathFiles)
469 getLog().debug("Dependency: " + pathElement);
470 urls.add(new File(pathElement).toURI().toURL());
474 urls.toArray(new URL[urls.size()]),
475 getClass().getClassLoader()
480 getLog().error("Error while creating ClassLoader!", e);
481 throw new MojoExecutionException(e.getMessage());
485 private Map loadProperties(ConfigLoader configLoader)
487 MojoExecutionException
489 /** Try to read configuration from properties-file */
490 if (hibernateProperties == null)
494 return configLoader.loadProperties("hibernate.properties");
496 catch (ConfigurationException e)
498 getLog().debug(e.getMessage());
499 return Collections.EMPTY_MAP;
506 File file = new File(hibernateProperties);
509 getLog().info("Reading settings from file " + hibernateProperties + "...");
510 return configLoader.loadProperties(file);
513 return configLoader.loadProperties(hibernateProperties);
515 catch (ConfigurationException e)
517 getLog().error("Error while reading properties!", e);
518 throw new MojoExecutionException(e.getMessage());
523 private Map loadConfig(ConfigLoader configLoader)
524 throws MojoExecutionException
526 /** Try to read configuration from configuration-file */
527 if (hibernateConfig == null)
533 .loadConfigXmlResource("hibernate.cfg.xml")
534 .getConfigurationValues();
536 catch (ConfigurationException e)
538 getLog().debug(e.getMessage());
539 return Collections.EMPTY_MAP;
546 File file = new File(hibernateConfig);
549 getLog().info("Reading configuration from file " + hibernateConfig + "...");
550 return configLoader.loadConfigXmlFile(file).getConfigurationValues();
555 .loadConfigXmlResource(hibernateConfig)
556 .getConfigurationValues();
558 catch (ConfigurationException e)
560 getLog().error("Error while reading configuration!", e);
561 throw new MojoExecutionException(e.getMessage());
566 private void configure(Properties properties)
567 throws MojoFailureException
569 /** Overwrite values from properties-file or set, if given */
571 configure(properties, driver, DRIVER, JDBC_DRIVER);
572 configure(properties, url, URL, JDBC_URL);
573 configure(properties, username, USER, JDBC_USER);
574 configure(properties, password, PASS, JDBC_PASSWORD);
575 configure(properties, dialect, DIALECT);
576 configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY);
577 configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY);
579 if (properties.isEmpty())
581 getLog().error("No properties set!");
582 throw new MojoFailureException("Hibernate configuration is missing!");
585 getLog().info("Gathered hibernate-configuration (turn on debugging for details):");
586 for (Entry<Object,Object> entry : properties.entrySet())
587 getLog().info(" " + entry.getKey() + " = " + entry.getValue());
590 private void configure(
591 Properties properties,
594 String alternativeKey
597 configure(properties, value, key);
598 if (properties.containsKey(key) && properties.containsKey(alternativeKey))
601 "Ignoring property " + alternativeKey + "=" +
602 properties.getProperty(alternativeKey) + " in favour for property " +
603 key + "=" + properties.getProperty(key)
605 properties.remove(JDBC_DRIVER);
609 private void configure(Properties properties, String value, String key)
613 if (properties.containsKey(key))
615 "Overwriting property " + key + "=" + properties.getProperty(key) +
616 " with the value " + value
619 getLog().debug("Using the value " + value + " for property " + key);
620 properties.setProperty(key, value);
624 private void addMappings(MetadataSources sources, ModificationTracker tracker)
625 throws MojoFailureException
627 getLog().debug("Adding explicitly configured mappings...");
628 if (mappings != null)
632 for (String filename : mappings.split("[\\s,]+"))
634 // First try the filename as absolute/relative path
635 File file = new File(filename);
638 // If the file was not found, search for it in the resource-directories
639 for (Resource resource : project.getResources())
641 file = new File(resource.getDirectory() + File.separator + filename);
648 if (file.isDirectory())
649 // TODO: add support to read all mappings under a directory
650 throw new MojoFailureException(file.getAbsolutePath() + " is a directory");
651 if (tracker.check(filename, new FileInputStream(file)))
652 getLog().debug("Found new or modified mapping-file: " + filename);
654 getLog().debug("mapping-file unchanged: " + filename);
656 sources.addFile(file);
659 throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
662 catch (IOException e)
664 throw new MojoFailureException("Cannot calculate MD5 sums!", e);
669 private void addAnnotatedClasses(
670 MetadataSources sources,
671 ClassLoaderService classLoaderService,
672 ModificationTracker tracker
675 MojoFailureException,
676 MojoExecutionException
681 AnnotationDB db = new AnnotationDB();
684 dir = new File(outputDirectory);
687 getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes...");
688 URL dirUrl = dir.toURI().toURL();
689 db.scanArchives(dirUrl);
694 dir = new File(testOutputDirectory);
697 getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes...");
698 URL dirUrl = dir.toURI().toURL();
699 db.scanArchives(dirUrl);
703 if (scanDependencies != null)
705 Matcher matcher = SPLIT.matcher(scanDependencies);
706 while (matcher.find())
708 getLog().info("Scanning dependencies for scope " + matcher.group());
709 for (Artifact artifact : project.getDependencyArtifacts())
711 if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
713 if (artifact.getFile() == null)
715 getLog().warn("Cannot scan dependency " + artifact.getId() + ": no JAR-file available!");
718 getLog().info("Scanning dependency " + artifact.getId() + " for annotated classes...");
719 db.scanArchives(artifact.getFile().toURI().toURL());
724 Set<String> classes = new HashSet<String>();
725 if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
726 classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
727 if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
728 classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
729 if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
730 classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
732 Set<String> packages = new HashSet<String>();
734 for (String name : classes)
736 Class<?> annotatedClass = classLoaderService.classForName(name);
737 String packageName = annotatedClass.getPackage().getName();
738 if (!packages.contains(packageName))
741 annotatedClass.getResourceAsStream("package-info.class");
744 // No compiled package-info available: no package-level annotations!
745 getLog().debug("Package " + packageName + " is not annotated.");
749 if (tracker.check(packageName, is))
750 getLog().debug("New or modified package: " + packageName);
752 getLog().debug("Unchanged package: " + packageName);
753 getLog().info("Adding annotated package " + packageName);
754 sources.addPackage(packageName);
756 packages.add(packageName);
758 String resourceName = annotatedClass.getName();
760 resourceName.substring(
761 resourceName.lastIndexOf(".") + 1,
762 resourceName.length()
766 .getResourceAsStream(resourceName);
767 if (tracker.check(name, is))
768 getLog().debug("New or modified class: " + name);
770 getLog().debug("Unchanged class: " + name);
771 getLog().info("Adding annotated class " + annotatedClass);
772 sources.addAnnotatedClass(annotatedClass);
777 getLog().error("Error while scanning!", e);
778 throw new MojoFailureException(e.getMessage());
782 private Properties loadPersistenceUnit(
783 ClassLoaderService classLoaderService,
784 Properties properties
789 PersistenceXmlParser parser =
790 new PersistenceXmlParser(
792 PersistenceUnitTransactionType.RESOURCE_LOCAL
795 List<ParsedPersistenceXmlDescriptor> units = parser.doResolve(properties);
797 if (persistenceUnit == null)
799 switch (units.size())
802 getLog().info("Found no META-INF/persistence.xml.");
803 return new Properties();
805 getLog().info("Using persistence-unit " + units.get(0).getName());
806 return units.get(0).getProperties();
808 StringBuilder builder = new StringBuilder();
809 builder.append("No name provided and multiple persistence units found: ");
810 Iterator<ParsedPersistenceXmlDescriptor> it = units.iterator();
811 builder.append(it.next().getName());
814 builder.append(", ");
815 builder.append(it.next().getName());
818 throw new MojoFailureException(builder.toString());
822 for (ParsedPersistenceXmlDescriptor unit : units)
824 getLog().debug("Found persistence-unit " + unit.getName());
825 if (!unit.getName().equals(persistenceUnit))
828 // See if we (Hibernate) are the persistence provider
829 if (!ProviderChecker.isProvider(unit, properties))
831 getLog().debug("Wrong provider: " + unit.getProviderClassName());
835 getLog().info("Using persistence-unit " + unit.getName());
836 return unit.getProperties();
839 throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit);