1 package de.juplo.plugins.hibernate;
4 import com.pyx4j.log4j.MavenLogAppender;
6 import java.io.FileInputStream;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.net.MalformedURLException;
12 import java.security.NoSuchAlgorithmException;
13 import java.time.ZonedDateTime;
14 import java.util.Collections;
15 import java.util.EnumSet;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.LinkedHashSet;
20 import java.util.List;
22 import java.util.Map.Entry;
23 import java.util.Properties;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 import javax.persistence.Embeddable;
28 import javax.persistence.Entity;
29 import javax.persistence.MappedSuperclass;
30 import org.apache.maven.artifact.Artifact;
31 import org.apache.maven.model.Resource;
32 import org.apache.maven.plugin.AbstractMojo;
33 import org.apache.maven.plugin.MojoExecutionException;
34 import org.apache.maven.plugin.MojoFailureException;
35 import org.apache.maven.project.MavenProject;
36 import org.hibernate.boot.MetadataBuilder;
37 import org.hibernate.boot.MetadataSources;
38 import org.hibernate.boot.cfgxml.internal.ConfigLoader;
39 import org.hibernate.boot.cfgxml.spi.LoadedConfig;
40 import org.hibernate.boot.cfgxml.spi.MappingReference;
41 import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
42 import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
43 import org.hibernate.boot.registry.BootstrapServiceRegistry;
44 import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
45 import org.hibernate.boot.registry.StandardServiceRegistry;
46 import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
47 import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
48 import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
49 import org.hibernate.boot.registry.selector.spi.StrategySelector;
50 import org.hibernate.boot.spi.MetadataImplementor;
51 import org.hibernate.cfg.AvailableSettings;
52 import static org.hibernate.cfg.AvailableSettings.DIALECT;
53 import static org.hibernate.cfg.AvailableSettings.DRIVER;
54 import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL;
55 import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DELIMITER;
56 import static org.hibernate.cfg.AvailableSettings.HBM2DLL_CREATE_NAMESPACES;
57 import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY;
58 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER;
59 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD;
60 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL;
61 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER;
62 import static org.hibernate.cfg.AvailableSettings.PASS;
63 import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY;
64 import static org.hibernate.cfg.AvailableSettings.SHOW_SQL;
65 import static org.hibernate.cfg.AvailableSettings.USER;
66 import static org.hibernate.cfg.AvailableSettings.URL;
67 import org.hibernate.engine.config.spi.ConfigurationService;
68 import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
69 import org.hibernate.internal.util.config.ConfigurationException;
70 import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
71 import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
72 import org.hibernate.tool.schema.TargetType;
73 import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl;
74 import org.hibernate.tool.schema.internal.exec.ScriptTargetOutputToFile;
75 import org.hibernate.tool.schema.spi.ExecutionOptions;
76 import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
77 import org.hibernate.tool.schema.spi.ScriptTargetOutput;
78 import org.hibernate.tool.schema.spi.TargetDescriptor;
79 import org.scannotation.AnnotationDB;
83 * Baseclass with common attributes and methods.
85 * @phase process-classes
87 * @requiresDependencyResolution runtime
89 public abstract class AbstractSchemaMojo extends AbstractMojo
91 public final static String EXECUTE = "hibernate.schema.execute";
92 public final static String OUTPUTDIRECTORY = "project.build.outputDirectory";
93 public final static String SCAN_CLASSES = "hibernate.schema.scan.classes";
94 public final static String SCAN_DEPENDENCIES = "hibernate.schema.scan.dependencies";
95 public final static String SCAN_TESTCLASSES = "hibernate.schema.scan.test_classes";
96 public final static String TEST_OUTPUTDIRECTORY = "project.build.testOutputDirectory";
97 public final static String SKIPPED = "hibernate.schema.skipped";
98 public final static String SCRIPT = "hibernate.schema.script";
100 private final static Pattern SPLIT = Pattern.compile("[^,\\s]+");
102 private final Set<String> packages = new HashSet<String>();
108 * Only needed internally.
110 * @parameter property="project"
114 private MavenProject project;
119 * Only needed internally.
121 * @parameter property="project.build.directory"
125 private String buildDirectory;
128 /** Parameters to configure the genaration of the SQL *********************/
131 * Excecute the generated SQL.
132 * If set to <code>false</code>, only the SQL-script is created and the
133 * database is not touched.
135 * <strong>Important:</strong>
136 * This configuration value can only be configured through the
137 * <code>pom.xml</code>, or by the definition of a system-property, because
138 * it is not known by Hibernate nor JPA and, hence, not picked up from
139 * their configuration!
141 * @parameter property="hibernate.schema.execute" default-value="true"
144 private Boolean execute;
149 * If set to <code>true</code>, the execution is skipped.
151 * A skipped execution is signaled via the maven-property
152 * <code>${hibernate.schema.skipped}</code>.
154 * The execution is skipped automatically, if no modified or newly added
155 * annotated classes are found and the dialect was not changed.
157 * <strong>Important:</strong>
158 * This configuration value can only be configured through the
159 * <code>pom.xml</code>, or by the definition of a system-property, because
160 * it is not known by Hibernate nor JPA and, hence, not picked up from
161 * their configuration!
163 * @parameter property="hibernate.schema.skip" default-value="${maven.test.skip}"
166 private boolean skip;
169 * Force generation/execution
171 * Force the generation and (if configured) the execution of the SQL, even if
172 * no modified or newly added annotated classes where found and the
173 * configuration was not changed.
175 * <code>skip</code> takes precedence over <code>force</code>.
177 * <strong>Important:</strong>
178 * This configuration value can only be configured through the
179 * <code>pom.xml</code>, or by the definition of a system-property, because
180 * it is not known by Hibernate nor JPA and, hence, not picked up from
181 * their configuration!
183 * @parameter property="hibernate.schema.force" default-value="false"
186 private boolean force;
191 * @parameter property="hibernate.dialect"
194 private String dialect;
197 * Delimiter in output-file.
199 * <strong>Important:</strong>
200 * This configuration value can only be configured through the
201 * <code>pom.xml</code>, or by the definition of a system-property, because
202 * it is not known by Hibernate nor JPA and, hence, not picked up from
203 * their configuration!
205 * @parameter property="hibernate.hbm2ddl.delimiter" default-value=";"
208 private String delimiter;
211 * Show the generated SQL in the command-line output.
213 * @parameter property="hibernate.show_sql"
216 private Boolean show;
219 * Format output-file.
221 * @parameter property="hibernate.format_sql"
224 private Boolean format;
227 * Specifies whether to automatically create also the database schema/catalog.
229 * @parameter property="hibernate.hbm2dll.create_namespaces" default-value="false"
232 private Boolean createNamespaces;
235 * Implicit naming strategy
237 * @parameter property="hibernate.implicit_naming_strategy"
240 private String implicitNamingStrategy;
243 * Physical naming strategy
245 * @parameter property="hibernate.physical_naming_strategy"
248 private String physicalNamingStrategy;
251 * Wether the project should be scanned for annotated-classes, or not
253 * This parameter is intended to allow overwriting of the parameter
254 * <code>exclude-unlisted-classes</code> of a <code>persistence-unit</code>.
255 * If not specified, it defaults to <code>true</code>
257 * @parameter property="hibernate.schema.scan.classes"
260 private Boolean scanClasses;
263 * Classes-Directory to scan.
265 * This parameter defaults to the maven build-output-directory for classes.
266 * Additionally, all dependencies are scanned for annotated classes.
268 * <strong>Important:</strong>
269 * This configuration value can only be configured through the
270 * <code>pom.xml</code>, or by the definition of a system-property, because
271 * it is not known by Hibernate nor JPA and, hence, not picked up from
272 * their configuration!
274 * @parameter property="project.build.outputDirectory"
277 private String outputDirectory;
280 * Dependency-Scopes, that should be scanned for annotated classes.
282 * By default, only dependencies in the scope <code>compile</code> are
283 * scanned for annotated classes. Multiple scopes can be seperated by
284 * white space or commas.
286 * If you do not want any dependencies to be scanned for annotated
287 * classes, set this parameter to <code>none</code>.
289 * The plugin does not scan for annotated classes in transitive
290 * dependencies. If some of your annotated classes are hidden in a
291 * transitive dependency, you can simply add that dependency explicitly.
293 * @parameter property="hibernate.schema.scan.dependencies" default-value="compile"
296 private String scanDependencies;
299 * Whether to scan the test-branch of the project for annotated classes, or
302 * If this parameter is set to <code>true</code> the test-classes of the
303 * artifact will be scanned for hibernate-annotated classes additionally.
305 * <strong>Important:</strong>
306 * This configuration value can only be configured through the
307 * <code>pom.xml</code>, or by the definition of a system-property, because
308 * it is not known by Hibernate nor JPA and, hence, not picked up from
309 * their configuration!
311 * @parameter property="hibernate.schema.scan.test_classes" default-value="false"
314 private Boolean scanTestClasses;
317 * Test-Classes-Directory to scan.
319 * This parameter defaults to the maven build-output-directory for
322 * This parameter is only used, when <code>scanTestClasses</code> is set
323 * to <code>true</code>!
325 * <strong>Important:</strong>
326 * This configuration value can only be configured through the
327 * <code>pom.xml</code>, or by the definition of a system-property, because
328 * it is not known by Hibernate nor JPA and, hence, not picked up from
329 * their configuration!
331 * @parameter property="project.build.testOutputDirectory"
334 private String testOutputDirectory;
337 /** Conection parameters *************************************************/
342 * @parameter property="hibernate.connection.driver_class"
345 private String driver;
350 * @parameter property="hibernate.connection.url"
358 * @parameter property="hibernate.connection.username"
361 private String username;
366 * @parameter property="hibernate.connection.password"
369 private String password;
372 /** Parameters to locate configuration sources ****************************/
375 * Path to a file or name of a ressource with hibernate properties.
376 * If this parameter is specified, the plugin will try to load configuration
377 * values from a file with the given path or a ressource on the classpath with
378 * the given name. If both fails, the execution of the plugin will fail.
380 * If this parameter is not set the plugin will load configuration values
381 * from a ressource named <code>hibernate.properties</code> on the classpath,
382 * if it is present, but will not fail if there is no such ressource.
384 * During ressource-lookup, the test-classpath takes precedence.
389 private String hibernateProperties;
392 * Path to Hibernate configuration file (.cfg.xml).
393 * If this parameter is specified, the plugin will try to load configuration
394 * values from a file with the given path or a ressource on the classpath with
395 * the given name. If both fails, the execution of the plugin will fail.
397 * If this parameter is not set the plugin will load configuration values
398 * from a ressource named <code>hibernate.cfg.xml</code> on the classpath,
399 * if it is present, but will not fail if there is no such ressource.
401 * During ressource-lookup, the test-classpath takes precedence.
403 * Settings in this file will overwrite settings in the properties file.
408 private String hibernateConfig;
411 * Name of the persistence-unit.
412 * If this parameter is specified, the plugin will try to load configuration
413 * values from a persistence-unit with the specified name. If no such
414 * persistence-unit can be found, the plugin will throw an exception.
416 * If this parameter is not set and there is only one persistence-unit
417 * available, that unit will be used automatically. But if this parameter is
418 * not set and there are multiple persistence-units available on,
419 * the class-path, the execution of the plugin will fail.
421 * Settings in this file will overwrite settings in the properties or the
422 * configuration file.
427 private String persistenceUnit;
430 * List of Hibernate-Mapping-Files (XML).
431 * Multiple files can be separated with white-spaces and/or commas.
433 * @parameter property="hibernate.mapping"
436 private String mappings;
440 public final void execute(String filename)
442 MojoFailureException,
443 MojoExecutionException
447 getLog().info("Execution of hibernate-maven-plugin was skipped!");
448 project.getProperties().setProperty(SKIPPED, "true");
452 ModificationTracker tracker;
455 tracker = new ModificationTracker(buildDirectory, filename, getLog());
457 catch (NoSuchAlgorithmException e)
459 throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
462 final SimpleConnectionProvider connectionProvider =
463 new SimpleConnectionProvider(getLog());
467 /** Start extended logging */
468 MavenLogAppender.startPluginLog(this);
470 /** Load checksums for old mapping and configuration */
473 /** Create the ClassLoader */
474 MutableClassLoader classLoader = createClassLoader();
476 /** Create a BootstrapServiceRegistry with the created ClassLoader */
477 BootstrapServiceRegistry bootstrapServiceRegitry =
478 new BootstrapServiceRegistryBuilder()
479 .applyClassLoader(classLoader)
481 ClassLoaderService classLoaderService =
482 bootstrapServiceRegitry.getService(ClassLoaderService.class);
484 Properties properties = new Properties();
485 ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
487 /** Loading configuration */
488 properties.putAll(loadProperties(configLoader));
489 LoadedConfig config = loadConfig(configLoader);
491 properties.putAll(config.getConfigurationValues());
493 /** Add the remaining class-path-elements */
494 addDirectDependenciesClassPath(classLoader);
496 /** Loading and merging configuration from persistence-unit(s) */
497 ParsedPersistenceXmlDescriptor unit =
498 loadPersistenceUnit(classLoader, properties);
500 properties.putAll(unit.getProperties());
502 /** Overwriting/Completing configuration */
503 configure(properties, tracker);
505 /** Check configuration for modifications */
506 if(tracker.track(properties))
507 getLog().debug("Configuration has changed.");
509 getLog().debug("Configuration unchanged.");
511 /** Check, that the outputfile is writable */
512 final File output = getOutputFile(filename);
513 /** Check, if the outputfile is missing or was changed */
514 checkOutputFile(output, tracker);
516 /** Configure Hibernate */
517 final StandardServiceRegistry serviceRegistry =
518 new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
519 .applySettings(properties)
520 .addService(ConnectionProvider.class, connectionProvider)
522 final MetadataSources sources = new MetadataSources(serviceRegistry);
524 /** Add the remaining class-path-elements */
525 addAllDependenciesToClassPath(classLoader);
527 /** Apply mappings from hibernate-configuration, if present */
530 for (MappingReference mapping : config.getMappingReferences())
531 mapping.apply(sources);
537 /** No persistent unit: default behaviour */
538 if (scanClasses == null)
540 Set<URL> urls = new HashSet<URL>();
541 getLog().debug("Compiling the dependencies, that are scanned for annotated classes");
543 addRoot(urls, outputDirectory);
545 addRoot(urls, testOutputDirectory);
546 addDependencies(urls);
547 classes = scanUrls(urls);
551 /** Follow configuration in persisten unit */
552 if (scanClasses == null)
553 scanClasses = !unit.isExcludeUnlistedClasses();
554 getLog().debug("Compiling the dependencies, that are scanned for annotated classes");
556 Set<URL> urls = new HashSet<URL>();
559 getLog().debug("Only dependencies relative to persistent-unit " + unit.getName() + " are scanned!");
561 * Scan the root of the persiten unit and configured jars for
564 getLog().debug(" - adding " + unit.getPersistenceUnitRootUrl());
565 urls.add(unit.getPersistenceUnitRootUrl());
566 for (URL url : unit.getJarFileUrls())
568 getLog().debug(" - adding " + url);
572 addRoot(urls, testOutputDirectory);
575 getLog().debug("Scanning of unlisted classes is prohibited in persistent-unit " + unit.getName());
576 classes = scanUrls(urls);
577 for (String className : unit.getManagedClassNames())
578 classes.add(className);
580 * Add mappings from the default mapping-file
581 * <code>META-INF/orm.xml</code>, if present
583 boolean error = false;
585 is = classLoader.getResourceAsStream("META-INF/orm.xml");
588 getLog().info("Adding default JPA-XML-mapping from META-INF/orm.xml");
591 tracker.track("META-INF/orm.xml", is);
592 sources.addResource("META-INF/orm.xml");
594 catch (IOException e)
596 getLog().error("cannot read META-INF/orm.xml: " + e);
602 getLog().debug("no META-INF/orm.xml found");
605 * Add mappings from files, that are explicitly configured in the
608 getLog().info("Adding mappings from persistence-unit " + unit.getName());
609 for (String mapping : unit.getMappingFileNames())
611 getLog().info(" - adding " + mapping);
612 is = classLoader.getResourceAsStream(mapping);
617 tracker.track(mapping, is);
618 sources.addResource(mapping);
620 catch (IOException e)
622 getLog().info("cannot read mapping-file " + mapping + ": " + e);
628 getLog().error("cannot find mapping-file " + mapping);
633 throw new MojoFailureException(
634 "error, while reading mappings configured in persistence-unit \"" +
640 /** Add the configured/collected annotated classes */
641 for (String className : classes)
642 addAnnotated(className, sources, classLoaderService, tracker);
644 /** Add explicitly configured classes */
645 addMappings(sources, tracker);
647 /** Skip execution, if mapping and configuration is unchanged */
648 if (!tracker.modified())
650 getLog().info("Mapping and configuration unchanged.");
652 getLog().info("Generation/execution is forced!");
655 getLog().info("Skipping schema generation!");
656 project.getProperties().setProperty(SKIPPED, "true");
662 /** Truncate output file */
665 new FileOutputStream(output).getChannel().truncate(0).close();
667 catch (IOException e)
670 "Error while truncating " + output.getAbsolutePath() + ": "
672 getLog().warn(error);
673 throw new MojoExecutionException(error);
676 /** Create a connection, if sufficient configuration infromation is available */
677 connectionProvider.open(classLoaderService, properties);
679 MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
681 StrategySelector strategySelector =
682 serviceRegistry.getService(StrategySelector.class);
684 if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
686 metadataBuilder.applyImplicitNamingStrategy(
687 strategySelector.resolveStrategy(
688 ImplicitNamingStrategy.class,
689 properties.getProperty(IMPLICIT_NAMING_STRATEGY)
694 if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
696 metadataBuilder.applyPhysicalNamingStrategy(
697 strategySelector.resolveStrategy(
698 PhysicalNamingStrategy.class,
699 properties.getProperty(PHYSICAL_NAMING_STRATEGY)
704 /** Prepare the generation of the SQL */
705 Map settings = new HashMap();
708 .getService(ConfigurationService.class)
711 ExceptionHandlerCollectingImpl handler =
712 new ExceptionHandlerCollectingImpl();
713 ExecutionOptions options =
714 SchemaManagementToolCoordinator
715 .buildExecutionOptions(settings, handler);
716 final EnumSet<TargetType> targetTypes = EnumSet.of(TargetType.SCRIPT);
718 targetTypes.add(TargetType.DATABASE);
719 TargetDescriptor target = new TargetDescriptor()
722 public EnumSet<TargetType> getTargetTypes()
728 public ScriptTargetOutput getScriptTargetOutput()
733 .getService(ConfigurationService.class)
735 .get(AvailableSettings.HBM2DDL_CHARSET_NAME);
736 return new ScriptTargetOutputToFile(output, charset);
741 * Change class-loader of current thread.
742 * This is necessary, because still not all parts of Hibernate 5 use
743 * the newly introduced ClassLoaderService and will fail otherwise!
745 Thread thread = Thread.currentThread();
746 ClassLoader contextClassLoader = thread.getContextClassLoader();
749 thread.setContextClassLoader(classLoader);
750 build((MetadataImplementor)metadataBuilder.build(), options, target);
751 if (handler.getExceptions().size() > 0)
753 StringBuilder builder = new StringBuilder();
754 builder.append("Hibernate failed:");
755 for (Exception e : handler.getExceptions())
757 builder.append("\n * ");
758 builder.append(e.getMessage());
759 AbstractSchemaMojo.printStrackTrace(builder, e);
760 builder.append("\n");
762 String error = builder.toString();
763 getLog().error(error);
764 throw new MojoFailureException(error);
769 thread.setContextClassLoader(contextClassLoader);
770 /** Track, the content of the generated script */
771 checkOutputFile(output, tracker);
774 catch (MojoExecutionException e)
779 catch (MojoFailureException e)
784 catch (RuntimeException e)
791 /** Remember mappings and configuration */
794 /** Close the connection - if one was opened */
795 connectionProvider.close();
797 /** Stop Log-Capturing */
798 MavenLogAppender.endPluginLog(this);
804 MetadataImplementor metadata,
805 ExecutionOptions options,
806 TargetDescriptor target
809 MojoFailureException,
810 MojoExecutionException;
813 private MutableClassLoader createClassLoader() throws MojoExecutionException
817 getLog().debug("Creating ClassLoader for project-dependencies...");
818 LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
821 file = new File(testOutputDirectory);
824 getLog().info("Creating test-output-directory: " + testOutputDirectory);
827 urls.add(file.toURI().toURL());
829 file = new File(outputDirectory);
832 getLog().info("Creating output-directory: " + outputDirectory);
835 urls.add(file.toURI().toURL());
837 return new MutableClassLoader(urls, getLog());
841 getLog().error("Error while creating ClassLoader!", e);
842 throw new MojoExecutionException(e.getMessage());
846 private void addDirectDependenciesClassPath(MutableClassLoader classLoader)
848 MojoExecutionException
852 getLog().debug("Adding all direct project-dependencies to the ClassLoader...");
853 LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
854 addDependencies(urls);
856 addRoot(urls, testOutputDirectory);
857 classLoader.add(urls);
861 getLog().error("Error while creating ClassLoader!", e);
862 throw new MojoExecutionException(e.getMessage());
866 private void addAllDependenciesToClassPath(MutableClassLoader classLoader)
868 MojoExecutionException
872 getLog().debug("Adding all project-dependencies to the ClassLoader...");
873 List<String> classpathFiles = project.getCompileClasspathElements();
874 classpathFiles.addAll(project.getTestClasspathElements());
875 LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
876 for (String pathElement : classpathFiles)
878 getLog().debug(" - adding " + pathElement);
879 urls.add(new File(pathElement).toURI().toURL());
881 classLoader.add(urls);
885 getLog().error("Error while creating ClassLoader!", e);
886 throw new MojoExecutionException(e.getMessage());
890 private Map loadProperties(ConfigLoader configLoader)
892 MojoExecutionException
894 /** Try to read configuration from properties-file */
895 if (hibernateProperties == null)
899 return configLoader.loadProperties("hibernate.properties");
901 catch (ConfigurationException e)
903 getLog().debug(e.getMessage());
904 return Collections.EMPTY_MAP;
911 File file = new File(hibernateProperties);
914 getLog().info("Reading settings from file " + hibernateProperties + "...");
915 return configLoader.loadProperties(file);
918 return configLoader.loadProperties(hibernateProperties);
920 catch (ConfigurationException e)
922 getLog().error("Error while reading properties!", e);
923 throw new MojoExecutionException(e.getMessage());
928 private LoadedConfig loadConfig(ConfigLoader configLoader)
929 throws MojoExecutionException
931 /** Try to read configuration from configuration-file */
932 if (hibernateConfig == null)
936 return configLoader.loadConfigXmlResource("hibernate.cfg.xml");
938 catch (ConfigurationException e)
940 getLog().debug(e.getMessage());
948 File file = new File(hibernateConfig);
951 getLog().info("Reading configuration from file " + hibernateConfig + "...");
952 return configLoader.loadConfigXmlFile(file);
956 return configLoader.loadConfigXmlResource(hibernateConfig);
959 catch (ConfigurationException e)
961 getLog().error("Error while reading configuration!", e);
962 throw new MojoExecutionException(e.getMessage());
967 private void configure(Properties properties, ModificationTracker tracker)
968 throws MojoFailureException
971 * Special treatment for the configuration-value "execute": if it is
972 * switched to "true", the genearation fo the schema should be forced!
974 if (tracker.check(EXECUTE, execute.toString()) && execute)
977 "hibernate.schema.execute was switched on: " +
978 "forcing generation/execution of SQL"
982 configure(properties, execute, EXECUTE);
985 * Configure the generation of the SQL.
986 * Overwrite values from properties-file if the configuration parameter is
987 * known to Hibernate.
989 configure(properties, dialect, DIALECT);
990 configure(properties, delimiter, HBM2DDL_DELIMITER);
991 configure(properties, format, FORMAT_SQL);
992 configure(properties, createNamespaces, HBM2DLL_CREATE_NAMESPACES);
993 configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY);
994 configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY);
995 configure(properties, outputDirectory, OUTPUTDIRECTORY);
996 configure(properties, scanDependencies, SCAN_DEPENDENCIES);
997 configure(properties, scanTestClasses, SCAN_TESTCLASSES);
998 configure(properties, testOutputDirectory, TEST_OUTPUTDIRECTORY);
1001 * Special treatment for the configuration-value "show": a change of its
1002 * configured value should not lead to a regeneration of the database
1006 show = Boolean.valueOf(properties.getProperty(SHOW_SQL));
1008 properties.setProperty(SHOW_SQL, show.toString());
1011 * Configure the connection parameters.
1012 * Overwrite values from properties-file.
1014 configure(properties, driver, DRIVER, JPA_JDBC_DRIVER);
1015 configure(properties, url, URL, JPA_JDBC_URL);
1016 configure(properties, username, USER, JPA_JDBC_USER);
1017 configure(properties, password, PASS, JPA_JDBC_PASSWORD);
1019 if (properties.isEmpty())
1021 getLog().error("No properties set!");
1022 throw new MojoFailureException("Hibernate configuration is missing!");
1025 getLog().info("Gathered configuration:");
1026 for (Entry<Object,Object> entry : properties.entrySet())
1027 getLog().info(" " + entry.getKey() + " = " + entry.getValue());
1030 private void configure(
1031 Properties properties,
1034 String alternativeKey
1037 configure(properties, value, key);
1039 if (properties.containsKey(alternativeKey))
1041 if (properties.containsKey(key))
1044 "Ignoring property " + alternativeKey + "=\"" +
1045 properties.getProperty(alternativeKey) +
1046 "\" in favour for property " + key + "=\"" +
1047 properties.getProperty(key) + "\""
1049 properties.remove(alternativeKey);
1053 value = properties.getProperty(alternativeKey);
1054 properties.remove(alternativeKey);
1056 "Using value \"" + value + "\" from property " + alternativeKey +
1057 " for property " + key
1059 properties.setProperty(key, value);
1064 private void configure(Properties properties, String value, String key)
1068 if (properties.containsKey(key))
1070 if (!properties.getProperty(key).equals(value))
1073 "Overwriting property " + key + "=\"" +
1074 properties.getProperty(key) +
1075 "\" with value \"" + value + "\""
1077 properties.setProperty(key, value);
1082 getLog().debug("Using value \"" + value + "\" for property " + key);
1083 properties.setProperty(key, value);
1088 private void configure(Properties properties, Boolean value, String key)
1090 configure(properties, value == null ? null : value.toString(), key);
1093 private File getOutputFile(String filename)
1095 MojoExecutionException
1097 File output = new File(filename);
1099 if (!output.isAbsolute())
1101 // Interpret relative file path relative to build directory
1102 output = new File(buildDirectory, filename);
1104 getLog().debug("Output file: " + output.getPath());
1106 // Ensure that directory path for specified file exists
1107 File outFileParentDir = output.getParentFile();
1108 if (null != outFileParentDir && !outFileParentDir.exists())
1113 "Creating directory path for output file:" +
1114 outFileParentDir.getPath()
1116 outFileParentDir.mkdirs();
1121 "Error creating directory path for output file: " + e.getMessage();
1122 getLog().error(error);
1123 throw new MojoExecutionException(error);
1129 output.createNewFile();
1131 catch (IOException e)
1133 String error = "Error creating output file: " + e.getMessage();
1134 getLog().error(error);
1135 throw new MojoExecutionException(error);
1138 if (!output.canWrite())
1141 "Output file " + output.getAbsolutePath() + " is not writable!";
1142 getLog().error(error);
1143 throw new MojoExecutionException(error);
1149 private void checkOutputFile(File output, ModificationTracker tracker)
1151 MojoExecutionException
1155 if (output.exists())
1156 tracker.track(SCRIPT, new FileInputStream(output));
1158 tracker.track(SCRIPT, ZonedDateTime.now().toString());
1160 catch (IOException e)
1163 "Error while checking the generated script: " + e.getMessage();
1164 getLog().error(error);
1165 throw new MojoExecutionException(error);
1169 private void addMappings(MetadataSources sources, ModificationTracker tracker)
1170 throws MojoFailureException
1172 getLog().debug("Adding explicitly configured mappings...");
1173 if (mappings != null)
1177 for (String filename : mappings.split("[\\s,]+"))
1179 // First try the filename as absolute/relative path
1180 File file = new File(filename);
1183 // If the file was not found, search for it in the resource-directories
1184 for (Resource resource : project.getResources())
1186 file = new File(resource.getDirectory() + File.separator + filename);
1193 if (file.isDirectory())
1194 // TODO: add support to read all mappings under a directory
1195 throw new MojoFailureException(file.getAbsolutePath() + " is a directory");
1196 if (tracker.track(filename, new FileInputStream(file)))
1197 getLog().debug(" - found new or modified mapping-file: " + filename);
1199 getLog().debug(" - mapping-file unchanged: " + filename);
1201 sources.addFile(file);
1204 throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
1207 catch (IOException e)
1209 throw new MojoFailureException("Cannot calculate MD5 sums!", e);
1214 private void addRoot(Set<URL> urls, String path) throws MojoFailureException
1218 File dir = new File(path);
1221 getLog().info(" - adding " + dir.getAbsolutePath());
1222 urls.add(dir.toURI().toURL());
1226 "The directory cannot be scanned for annotated classes, " +
1227 "because it does not exist: " +
1228 dir.getAbsolutePath()
1231 catch (MalformedURLException e)
1233 getLog().error("error while adding the project-root to the list of roots to scan!", e);
1234 throw new MojoFailureException(e.getMessage());
1238 private void addDependencies(Set<URL> urls) throws MojoFailureException
1242 if (scanDependencies != null)
1244 Matcher matcher = SPLIT.matcher(scanDependencies);
1245 while (matcher.find())
1247 getLog().debug("Adding dependencies from scope " + matcher.group() + " to the list of roots to scan");
1248 for (Artifact artifact : project.getDependencyArtifacts())
1250 if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
1252 if (artifact.getFile() == null)
1254 getLog().warn("Cannot add dependency " + artifact.getId() + ": no JAR-file available!");
1257 getLog().debug(" - adding " + artifact.getId());
1258 urls.add(artifact.getFile().toURI().toURL());
1263 catch (MalformedURLException e)
1265 getLog().error("Error while adding dependencies to the list of roots to scan!", e);
1266 throw new MojoFailureException(e.getMessage());
1270 private Set<String> scanUrls(Set<URL> scanRoots)
1272 MojoFailureException
1276 AnnotationDB db = new AnnotationDB();
1277 for (URL root : scanRoots)
1278 db.scanArchives(root);
1280 Set<String> classes = new HashSet<String>();
1281 if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
1282 classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
1283 if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
1284 classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
1285 if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
1286 classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
1292 getLog().error("Error while scanning!", e);
1293 throw new MojoFailureException(e.getMessage());
1297 private void addAnnotated(
1299 MetadataSources sources,
1300 ClassLoaderService classLoaderService,
1301 ModificationTracker tracker
1304 MojoFailureException,
1305 MojoExecutionException
1309 getLog().info("Adding annotated resource: " + name);
1310 String packageName = null;
1312 boolean error = false;
1315 Class<?> annotatedClass = classLoaderService.classForName(name);
1316 String resourceName = annotatedClass.getName();
1318 resourceName.substring(
1319 resourceName.lastIndexOf(".") + 1,
1320 resourceName.length()
1322 InputStream is = annotatedClass.getResourceAsStream(resourceName);
1325 if (tracker.track(name, is))
1326 getLog().debug("New or modified class: " + name);
1328 getLog().debug("Unchanged class: " + name);
1329 sources.addAnnotatedClass(annotatedClass);
1330 packageName = annotatedClass.getPackage().getName();
1334 getLog().error("cannot find ressource " + resourceName + " for class " + name);
1338 catch(ClassLoadingException e)
1344 throw new MojoExecutionException("error while inspecting annotated class " + name);
1347 while (packageName != null)
1349 if (packages.contains(packageName))
1351 String resource = packageName.replace('.', '/') + "/package-info.class";
1352 InputStream is = classLoaderService.locateResourceStream(resource);
1355 // No compiled package-info available: no package-level annotations!
1356 getLog().debug("Package " + packageName + " is not annotated.");
1360 if (tracker.track(packageName, is))
1361 getLog().debug("New or modified package: " + packageName);
1363 getLog().debug("Unchanged package: " + packageName);
1364 getLog().info("Adding annotations from package " + packageName);
1365 sources.addPackage(packageName);
1367 packages.add(packageName);
1368 int i = packageName.lastIndexOf('.');
1372 packageName = packageName.substring(0,i);
1377 getLog().error("Error while adding the annotated class " + name, e);
1378 throw new MojoFailureException(e.getMessage());
1382 private ParsedPersistenceXmlDescriptor loadPersistenceUnit(
1383 ClassLoader classLoader,
1384 Properties properties
1387 MojoFailureException
1389 Map<String, ? extends Object> settings =
1390 Collections.singletonMap(
1391 AvailableSettings.CLASSLOADERS,
1392 Collections.singletonList(classLoader)
1394 // Find all available persistent unit descriptors
1395 List<ParsedPersistenceXmlDescriptor> descriptors =
1396 PersistenceXmlParser.locatePersistenceUnits(settings);
1398 // Find all persistent units in the located descriptors
1399 Map<String, ParsedPersistenceXmlDescriptor> units = new HashMap<>();
1400 for (ParsedPersistenceXmlDescriptor descriptor : descriptors)
1402 String unit = descriptor.getName();
1403 if (units.containsKey(unit))
1405 "Persistence unit " + unit +
1406 " from " + descriptor.getPersistenceUnitRootUrl() +
1407 " overwrites unit with the same name from " +
1408 units.get(unit).getPersistenceUnitRootUrl()
1410 units.put(unit, descriptor);
1413 if (persistenceUnit == null)
1415 Iterator<String> names = units.keySet().iterator();
1416 if (!names.hasNext())
1418 getLog().info("Found no META-INF/persistence.xml.");
1422 String name = names.next();
1423 if (!names.hasNext())
1425 getLog().info("Using persistence-unit " + name);
1426 return units.get(name);
1429 StringBuilder builder = new StringBuilder();
1430 builder.append("No name provided and multiple persistence units found: ");
1431 builder.append(name);
1432 while(names.hasNext())
1434 builder.append(", ");
1435 builder.append(names.next());
1437 builder.append('.');
1438 throw new MojoFailureException(builder.toString());
1441 if (units.containsKey(persistenceUnit))
1443 getLog().info("Using configured persistence-unit " + persistenceUnit);
1444 return units.get(persistenceUnit);
1447 throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit);
1451 public static void printStrackTrace(StringBuilder builder, Throwable t)
1455 builder.append("\n\tCause: ");
1456 builder.append(t.getMessage() == null ? "" : t.getMessage().replaceAll("\\s+", " "));
1457 for (StackTraceElement trace : t.getStackTrace())
1459 builder.append("\n\t");
1460 builder.append(trace.getClassName());
1461 builder.append(".");
1462 builder.append(trace.getMethodName());
1463 builder.append("():");
1464 builder.append(trace.getLineNumber());