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.HashSet;
16 import java.util.Iterator;
17 import java.util.LinkedHashSet;
18 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Properties;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import javax.persistence.Embeddable;
26 import javax.persistence.Entity;
27 import javax.persistence.MappedSuperclass;
28 import javax.persistence.spi.PersistenceUnitTransactionType;
29 import org.apache.maven.artifact.Artifact;
30 import org.apache.maven.model.Resource;
31 import org.apache.maven.plugin.AbstractMojo;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.MojoFailureException;
34 import org.apache.maven.project.MavenProject;
35 import org.hibernate.boot.MetadataBuilder;
36 import org.hibernate.boot.MetadataSources;
37 import org.hibernate.boot.cfgxml.internal.ConfigLoader;
38 import org.hibernate.boot.cfgxml.spi.LoadedConfig;
39 import org.hibernate.boot.cfgxml.spi.MappingReference;
40 import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
41 import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
42 import org.hibernate.boot.registry.BootstrapServiceRegistry;
43 import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
44 import org.hibernate.boot.registry.StandardServiceRegistry;
45 import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
46 import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
47 import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
48 import org.hibernate.boot.registry.selector.spi.StrategySelector;
49 import org.hibernate.boot.spi.MetadataImplementor;
50 import static org.hibernate.cfg.AvailableSettings.DIALECT;
51 import static org.hibernate.cfg.AvailableSettings.DRIVER;
52 import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL;
53 import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DELIMITER;
54 import static org.hibernate.cfg.AvailableSettings.HBM2DLL_CREATE_NAMESPACES;
55 import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY;
56 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER;
57 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD;
58 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL;
59 import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER;
60 import static org.hibernate.cfg.AvailableSettings.PASS;
61 import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY;
62 import static org.hibernate.cfg.AvailableSettings.SHOW_SQL;
63 import static org.hibernate.cfg.AvailableSettings.USER;
64 import static org.hibernate.cfg.AvailableSettings.URL;
65 import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
66 import org.hibernate.internal.util.config.ConfigurationException;
67 import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
68 import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
69 import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl;
70 import org.scannotation.AnnotationDB;
74 * Baseclass with common attributes and methods.
76 * @phase process-classes
78 * @requiresDependencyResolution runtime
80 public abstract class AbstractSchemaMojo extends AbstractMojo
82 public final static String EXECUTE = "hibernate.schema.execute";
83 public final static String OUTPUTDIRECTORY = "project.build.outputDirectory";
84 public final static String SCAN_CLASSES = "hibernate.schema.scan.classes";
85 public final static String SCAN_DEPENDENCIES = "hibernate.schema.scan.dependencies";
86 public final static String SCAN_TESTCLASSES = "hibernate.schema.scan.test_classes";
87 public final static String TEST_OUTPUTDIRECTORY = "project.build.testOutputDirectory";
88 public final static String SKIPPED = "hibernate.schema.skipped";
89 public final static String SCRIPT = "hibernate.schema.script";
91 private final static Pattern SPLIT = Pattern.compile("[^,\\s]+");
93 private final Set<String> packages = new HashSet<String>();
99 * Only needed internally.
101 * @parameter property="project"
105 private MavenProject project;
110 * Only needed internally.
112 * @parameter property="project.build.directory"
116 private String buildDirectory;
119 /** Parameters to configure the genaration of the SQL *********************/
122 * Excecute the generated SQL.
123 * If set to <code>false</code>, only the SQL-script is created and the
124 * database is not touched.
126 * <strong>Important:</strong>
127 * This configuration value can only be configured through the
128 * <code>pom.xml</code>, or by the definition of a system-property, because
129 * it is not known by Hibernate nor JPA and, hence, not picked up from
130 * their configuration!
132 * @parameter property="hibernate.schema.execute" default-value="true"
135 private Boolean execute;
140 * If set to <code>true</code>, the execution is skipped.
142 * A skipped execution is signaled via the maven-property
143 * <code>${hibernate.schema.skipped}</code>.
145 * The execution is skipped automatically, if no modified or newly added
146 * annotated classes are found and the dialect was not changed.
148 * <strong>Important:</strong>
149 * This configuration value can only be configured through the
150 * <code>pom.xml</code>, or by the definition of a system-property, because
151 * it is not known by Hibernate nor JPA and, hence, not picked up from
152 * their configuration!
154 * @parameter property="hibernate.schema.skip" default-value="${maven.test.skip}"
157 private boolean skip;
160 * Force generation/execution
162 * Force the generation and (if configured) the execution of the SQL, even if
163 * no modified or newly added annotated classes where found and the
164 * configuration was not changed.
166 * <code>skip</code> takes precedence over <code>force</code>.
168 * <strong>Important:</strong>
169 * This configuration value can only be configured through the
170 * <code>pom.xml</code>, or by the definition of a system-property, because
171 * it is not known by Hibernate nor JPA and, hence, not picked up from
172 * their configuration!
174 * @parameter property="hibernate.schema.force" default-value="false"
177 private boolean force;
182 * @parameter property="hibernate.dialect"
185 private String dialect;
188 * Delimiter in output-file.
190 * <strong>Important:</strong>
191 * This configuration value can only be configured through the
192 * <code>pom.xml</code>, or by the definition of a system-property, because
193 * it is not known by Hibernate nor JPA and, hence, not picked up from
194 * their configuration!
196 * @parameter property="hibernate.hbm2ddl.delimiter" default-value=";"
199 private String delimiter;
202 * Show the generated SQL in the command-line output.
204 * @parameter property="hibernate.show_sql"
207 private Boolean show;
210 * Format output-file.
212 * @parameter property="hibernate.format_sql"
215 private Boolean format;
218 * Specifies whether to automatically create also the database schema/catalog.
220 * @parameter property="hibernate.hbm2dll.create_namespaces" default-value="false"
223 private Boolean createNamespaces;
226 * Implicit naming strategy
228 * @parameter property="hibernate.implicit_naming_strategy"
231 private String implicitNamingStrategy;
234 * Physical naming strategy
236 * @parameter property="hibernate.physical_naming_strategy"
239 private String physicalNamingStrategy;
242 * Wether the project should be scanned for annotated-classes, or not
244 * This parameter is intended to allow overwriting of the parameter
245 * <code>exclude-unlisted-classes</code> of a <code>persistence-unit</code>.
246 * If not specified, it defaults to <code>true</code>
248 * @parameter property="hibernate.schema.scan.classes"
251 private Boolean scanClasses;
254 * Classes-Directory to scan.
256 * This parameter defaults to the maven build-output-directory for classes.
257 * Additionally, all dependencies are scanned for annotated classes.
259 * <strong>Important:</strong>
260 * This configuration value can only be configured through the
261 * <code>pom.xml</code>, or by the definition of a system-property, because
262 * it is not known by Hibernate nor JPA and, hence, not picked up from
263 * their configuration!
265 * @parameter property="project.build.outputDirectory"
268 private String outputDirectory;
271 * Dependency-Scopes, that should be scanned for annotated classes.
273 * By default, only dependencies in the scope <code>compile</code> are
274 * scanned for annotated classes. Multiple scopes can be seperated by
275 * white space or commas.
277 * If you do not want any dependencies to be scanned for annotated
278 * classes, set this parameter to <code>none</code>.
280 * The plugin does not scan for annotated classes in transitive
281 * dependencies. If some of your annotated classes are hidden in a
282 * transitive dependency, you can simply add that dependency explicitly.
284 * @parameter property="hibernate.schema.scan.dependencies" default-value="compile"
287 private String scanDependencies;
290 * Whether to scan the test-branch of the project for annotated classes, or
293 * If this parameter is set to <code>true</code> the test-classes of the
294 * artifact will be scanned for hibernate-annotated classes additionally.
296 * <strong>Important:</strong>
297 * This configuration value can only be configured through the
298 * <code>pom.xml</code>, or by the definition of a system-property, because
299 * it is not known by Hibernate nor JPA and, hence, not picked up from
300 * their configuration!
302 * @parameter property="hibernate.schema.scan.test_classes" default-value="false"
305 private Boolean scanTestClasses;
308 * Test-Classes-Directory to scan.
310 * This parameter defaults to the maven build-output-directory for
313 * This parameter is only used, when <code>scanTestClasses</code> is set
314 * to <code>true</code>!
316 * <strong>Important:</strong>
317 * This configuration value can only be configured through the
318 * <code>pom.xml</code>, or by the definition of a system-property, because
319 * it is not known by Hibernate nor JPA and, hence, not picked up from
320 * their configuration!
322 * @parameter property="project.build.testOutputDirectory"
325 private String testOutputDirectory;
328 /** Conection parameters *************************************************/
333 * @parameter property="hibernate.connection.driver_class"
336 private String driver;
341 * @parameter property="hibernate.connection.url"
349 * @parameter property="hibernate.connection.username"
352 private String username;
357 * @parameter property="hibernate.connection.password"
360 private String password;
363 /** Parameters to locate configuration sources ****************************/
366 * Path to a file or name of a ressource with hibernate properties.
367 * If this parameter is specified, the plugin will try to load configuration
368 * values from a file with the given path or a ressource on the classpath with
369 * the given name. If both fails, the execution of the plugin will fail.
371 * If this parameter is not set the plugin will load configuration values
372 * from a ressource named <code>hibernate.properties</code> on the classpath,
373 * if it is present, but will not fail if there is no such ressource.
375 * During ressource-lookup, the test-classpath takes precedence.
380 private String hibernateProperties;
383 * Path to Hibernate configuration file (.cfg.xml).
384 * If this parameter is specified, the plugin will try to load configuration
385 * values from a file with the given path or a ressource on the classpath with
386 * the given name. If both fails, the execution of the plugin will fail.
388 * If this parameter is not set the plugin will load configuration values
389 * from a ressource named <code>hibernate.cfg.xml</code> on the classpath,
390 * if it is present, but will not fail if there is no such ressource.
392 * During ressource-lookup, the test-classpath takes precedence.
394 * Settings in this file will overwrite settings in the properties file.
399 private String hibernateConfig;
402 * Name of the persistence-unit.
403 * If this parameter is specified, the plugin will try to load configuration
404 * values from a persistence-unit with the specified name. If no such
405 * persistence-unit can be found, the plugin will throw an exception.
407 * If this parameter is not set and there is only one persistence-unit
408 * available, that unit will be used automatically. But if this parameter is
409 * not set and there are multiple persistence-units available on,
410 * the class-path, the execution of the plugin will fail.
412 * Settings in this file will overwrite settings in the properties or the
413 * configuration file.
418 private String persistenceUnit;
421 * List of Hibernate-Mapping-Files (XML).
422 * Multiple files can be separated with white-spaces and/or commas.
424 * @parameter property="hibernate.mapping"
427 private String mappings;
431 public final void execute(String filename)
433 MojoFailureException,
434 MojoExecutionException
438 getLog().info("Execution of hibernate-maven-plugin was skipped!");
439 project.getProperties().setProperty(SKIPPED, "true");
443 ModificationTracker tracker;
446 tracker = new ModificationTracker(buildDirectory, filename, getLog());
448 catch (NoSuchAlgorithmException e)
450 throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
453 final SimpleConnectionProvider connectionProvider =
454 new SimpleConnectionProvider(getLog());
458 /** Start extended logging */
459 MavenLogAppender.startPluginLog(this);
461 /** Load checksums for old mapping and configuration */
464 /** Create the ClassLoader */
465 MutableClassLoader classLoader = createClassLoader();
467 /** Create a BootstrapServiceRegistry with the created ClassLoader */
468 BootstrapServiceRegistry bootstrapServiceRegitry =
469 new BootstrapServiceRegistryBuilder()
470 .applyClassLoader(classLoader)
472 ClassLoaderService classLoaderService =
473 bootstrapServiceRegitry.getService(ClassLoaderService.class);
475 Properties properties = new Properties();
476 ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
478 /** Loading and merging configuration */
479 properties.putAll(loadProperties(configLoader));
480 LoadedConfig config = loadConfig(configLoader);
482 properties.putAll(config.getConfigurationValues());
483 ParsedPersistenceXmlDescriptor unit =
484 loadPersistenceUnit(classLoaderService, properties);
486 properties.putAll(unit.getProperties());
488 /** Overwriting/Completing configuration */
489 configure(properties, tracker);
491 /** Check configuration for modifications */
492 if(tracker.track(properties))
493 getLog().debug("Configuration has changed.");
495 getLog().debug("Configuration unchanged.");
497 /** Check, that the outputfile is writable */
498 final File output = getOutputFile(filename);
499 /** Check, if the outputfile is missing or was changed */
500 checkOutputFile(output, tracker);
502 /** Configure Hibernate */
503 final StandardServiceRegistry serviceRegistry =
504 new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
505 .applySettings(properties)
506 .addService(ConnectionProvider.class, connectionProvider)
508 final MetadataSources sources = new MetadataSources(serviceRegistry);
510 /** Add the remaining class-path-elements */
511 completeClassPath(classLoader);
513 /** Apply mappings from hibernate-configuration, if present */
516 for (MappingReference mapping : config.getMappingReferences())
517 mapping.apply(sources);
523 /** No persistent unit: default behaviour */
524 if (scanClasses == null)
526 Set<URL> urls = new HashSet<URL>();
528 addRoot(urls, outputDirectory);
530 addRoot(urls, testOutputDirectory);
531 addDependencies(urls);
532 classes = scanUrls(urls);
536 /** Follow configuration in persisten unit */
537 if (scanClasses == null)
538 scanClasses = !unit.isExcludeUnlistedClasses();
539 Set<URL> urls = new HashSet<URL>();
543 * Scan the root of the persiten unit and configured jars for
546 urls.add(unit.getPersistenceUnitRootUrl());
547 for (URL url : unit.getJarFileUrls())
551 addRoot(urls, testOutputDirectory);
552 classes = scanUrls(urls);
553 for (String className : unit.getManagedClassNames())
554 classes.add(className);
556 * Add mappings from the default mapping-file
557 * <code>META-INF/orm.xml</code>, if present
559 boolean error = false;
561 is = classLoader.getResourceAsStream("META-INF/orm.xml");
564 getLog().info("Adding default JPA-XML-mapping from META-INF/orm.xml");
567 tracker.track("META-INF/orm.xml", is);
568 sources.addResource("META-INF/orm.xml");
570 catch (IOException e)
572 getLog().error("cannot read META-INF/orm.xml: " + e);
578 getLog().debug("no META-INF/orm.xml found");
581 * Add mappings from files, that are explicitly configured in the
584 for (String mapping : unit.getMappingFileNames())
586 getLog().info("Adding explicitly configured mapping from " + mapping);
587 is = classLoader.getResourceAsStream(mapping);
592 tracker.track(mapping, is);
593 sources.addResource(mapping);
595 catch (IOException e)
597 getLog().info("cannot read mapping-file " + mapping + ": " + e);
603 getLog().error("cannot find mapping-file " + mapping);
608 throw new MojoFailureException(
609 "error, while reading mappings configured in persistence-unit \"" +
615 /** Add the configured/collected annotated classes */
616 for (String className : classes)
617 addAnnotated(className, sources, classLoaderService, tracker);
619 /** Add explicitly configured classes */
620 addMappings(sources, tracker);
622 /** Skip execution, if mapping and configuration is unchanged */
623 if (!tracker.modified())
625 getLog().info("Mapping and configuration unchanged.");
627 getLog().info("Generation/execution is forced!");
630 getLog().info("Skipping schema generation!");
631 project.getProperties().setProperty(SKIPPED, "true");
637 /** Truncate output file */
640 new FileOutputStream(output).getChannel().truncate(0).close();
642 catch (IOException e)
645 "Error while truncating " + output.getAbsolutePath() + ": "
647 getLog().warn(error);
648 throw new MojoExecutionException(error);
651 /** Create a connection, if sufficient configuration infromation is available */
652 connectionProvider.open(classLoaderService, properties);
654 MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
656 StrategySelector strategySelector =
657 serviceRegistry.getService(StrategySelector.class);
659 if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
661 metadataBuilder.applyImplicitNamingStrategy(
662 strategySelector.resolveStrategy(
663 ImplicitNamingStrategy.class,
664 properties.getProperty(IMPLICIT_NAMING_STRATEGY)
669 if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
671 metadataBuilder.applyPhysicalNamingStrategy(
672 strategySelector.resolveStrategy(
673 PhysicalNamingStrategy.class,
674 properties.getProperty(PHYSICAL_NAMING_STRATEGY)
680 * Change class-loader of current thread.
681 * This is necessary, because still not all parts of Hibernate 5 use
682 * the newly introduced ClassLoaderService and will fail otherwise!
684 Thread thread = Thread.currentThread();
685 ClassLoader contextClassLoader = thread.getContextClassLoader();
688 thread.setContextClassLoader(classLoader);
689 ExceptionHandlerCollectingImpl handler =
690 build((MetadataImplementor)metadataBuilder.build(), output);
691 if (handler.getExceptions().size() > 0)
693 StringBuilder builder = new StringBuilder();
694 builder.append("Hibernate failed:");
695 for (Exception e : handler.getExceptions())
697 builder.append("\n * ");
698 builder.append(e.getMessage());
700 String error = builder.toString();
701 getLog().error(error);
702 throw new MojoFailureException(error);
707 thread.setContextClassLoader(contextClassLoader);
708 /** Track, the content of the generated script */
709 checkOutputFile(output, tracker);
712 catch (MojoExecutionException e)
717 catch (MojoFailureException e)
722 catch (RuntimeException e)
729 /** Remember mappings and configuration */
732 /** Close the connection - if one was opened */
733 connectionProvider.close();
735 /** Stop Log-Capturing */
736 MavenLogAppender.endPluginLog(this);
741 abstract ExceptionHandlerCollectingImpl build(
742 MetadataImplementor metadata,
746 MojoFailureException,
747 MojoExecutionException;
750 private MutableClassLoader createClassLoader() throws MojoExecutionException
754 getLog().debug("Creating ClassLoader for project-dependencies...");
755 LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
758 file = new File(testOutputDirectory);
761 getLog().info("Creating test-output-directory: " + testOutputDirectory);
764 urls.add(file.toURI().toURL());
766 file = new File(outputDirectory);
769 getLog().info("Creating output-directory: " + outputDirectory);
772 urls.add(file.toURI().toURL());
774 return new MutableClassLoader(urls, getLog());
778 getLog().error("Error while creating ClassLoader!", e);
779 throw new MojoExecutionException(e.getMessage());
783 private void completeClassPath(MutableClassLoader classLoader)
785 MojoExecutionException
789 getLog().debug("Completing class-paths of the ClassLoader for project-dependencies...");
790 List<String> classpathFiles = project.getCompileClasspathElements();
792 classpathFiles.addAll(project.getTestClasspathElements());
793 LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
794 for (String pathElement : classpathFiles)
796 getLog().debug("Dependency: " + pathElement);
797 urls.add(new File(pathElement).toURI().toURL());
799 classLoader.add(urls);
803 getLog().error("Error while creating ClassLoader!", e);
804 throw new MojoExecutionException(e.getMessage());
808 private Map loadProperties(ConfigLoader configLoader)
810 MojoExecutionException
812 /** Try to read configuration from properties-file */
813 if (hibernateProperties == null)
817 return configLoader.loadProperties("hibernate.properties");
819 catch (ConfigurationException e)
821 getLog().debug(e.getMessage());
822 return Collections.EMPTY_MAP;
829 File file = new File(hibernateProperties);
832 getLog().info("Reading settings from file " + hibernateProperties + "...");
833 return configLoader.loadProperties(file);
836 return configLoader.loadProperties(hibernateProperties);
838 catch (ConfigurationException e)
840 getLog().error("Error while reading properties!", e);
841 throw new MojoExecutionException(e.getMessage());
846 private LoadedConfig loadConfig(ConfigLoader configLoader)
847 throws MojoExecutionException
849 /** Try to read configuration from configuration-file */
850 if (hibernateConfig == null)
854 return configLoader.loadConfigXmlResource("hibernate.cfg.xml");
856 catch (ConfigurationException e)
858 getLog().debug(e.getMessage());
866 File file = new File(hibernateConfig);
869 getLog().info("Reading configuration from file " + hibernateConfig + "...");
870 return configLoader.loadConfigXmlFile(file);
874 return configLoader.loadConfigXmlResource(hibernateConfig);
877 catch (ConfigurationException e)
879 getLog().error("Error while reading configuration!", e);
880 throw new MojoExecutionException(e.getMessage());
885 private void configure(Properties properties, ModificationTracker tracker)
886 throws MojoFailureException
889 * Special treatment for the configuration-value "execute": if it is
890 * switched to "true", the genearation fo the schema should be forced!
892 if (tracker.check(EXECUTE, execute.toString()) && execute)
895 "hibernate.schema.execute was switched on: " +
896 "forcing generation/execution of SQL"
900 configure(properties, execute, EXECUTE);
903 * Configure the generation of the SQL.
904 * Overwrite values from properties-file if the configuration parameter is
905 * known to Hibernate.
907 configure(properties, dialect, DIALECT);
908 configure(properties, delimiter, HBM2DDL_DELIMITER);
909 configure(properties, format, FORMAT_SQL);
910 configure(properties, createNamespaces, HBM2DLL_CREATE_NAMESPACES);
911 configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY);
912 configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY);
913 configure(properties, outputDirectory, OUTPUTDIRECTORY);
914 configure(properties, scanDependencies, SCAN_DEPENDENCIES);
915 configure(properties, scanTestClasses, SCAN_TESTCLASSES);
916 configure(properties, testOutputDirectory, TEST_OUTPUTDIRECTORY);
919 * Special treatment for the configuration-value "show": a change of its
920 * configured value should not lead to a regeneration of the database
924 show = Boolean.valueOf(properties.getProperty(SHOW_SQL));
926 properties.setProperty(SHOW_SQL, show.toString());
929 * Configure the connection parameters.
930 * Overwrite values from properties-file.
932 configure(properties, driver, DRIVER, JPA_JDBC_DRIVER);
933 configure(properties, url, URL, JPA_JDBC_URL);
934 configure(properties, username, USER, JPA_JDBC_USER);
935 configure(properties, password, PASS, JPA_JDBC_PASSWORD);
937 if (properties.isEmpty())
939 getLog().error("No properties set!");
940 throw new MojoFailureException("Hibernate configuration is missing!");
943 getLog().info("Gathered configuration:");
944 for (Entry<Object,Object> entry : properties.entrySet())
945 getLog().info(" " + entry.getKey() + " = " + entry.getValue());
948 private void configure(
949 Properties properties,
952 String alternativeKey
955 configure(properties, value, key);
957 if (properties.containsKey(alternativeKey))
959 if (properties.containsKey(key))
962 "Ignoring property " + alternativeKey + "=\"" +
963 properties.getProperty(alternativeKey) +
964 "\" in favour for property " + key + "=\"" +
965 properties.getProperty(key) + "\""
967 properties.remove(alternativeKey);
971 value = properties.getProperty(alternativeKey);
972 properties.remove(alternativeKey);
974 "Using value \"" + value + "\" from property " + alternativeKey +
975 " for property " + key
977 properties.setProperty(key, value);
982 private void configure(Properties properties, String value, String key)
986 if (properties.containsKey(key))
988 if (!properties.getProperty(key).equals(value))
991 "Overwriting property " + key + "=\"" +
992 properties.getProperty(key) +
993 "\" with value \"" + value + "\""
995 properties.setProperty(key, value);
1000 getLog().debug("Using value \"" + value + "\" for property " + key);
1001 properties.setProperty(key, value);
1006 private void configure(Properties properties, Boolean value, String key)
1008 configure(properties, value == null ? null : value.toString(), key);
1011 private File getOutputFile(String filename)
1013 MojoExecutionException
1015 File output = new File(filename);
1017 if (!output.isAbsolute())
1019 // Interpret relative file path relative to build directory
1020 output = new File(buildDirectory, filename);
1022 getLog().debug("Output file: " + output.getPath());
1024 // Ensure that directory path for specified file exists
1025 File outFileParentDir = output.getParentFile();
1026 if (null != outFileParentDir && !outFileParentDir.exists())
1031 "Creating directory path for output file:" +
1032 outFileParentDir.getPath()
1034 outFileParentDir.mkdirs();
1039 "Error creating directory path for output file: " + e.getMessage();
1040 getLog().error(error);
1041 throw new MojoExecutionException(error);
1047 output.createNewFile();
1049 catch (IOException e)
1051 String error = "Error creating output file: " + e.getMessage();
1052 getLog().error(error);
1053 throw new MojoExecutionException(error);
1056 if (!output.canWrite())
1059 "Output file " + output.getAbsolutePath() + " is not writable!";
1060 getLog().error(error);
1061 throw new MojoExecutionException(error);
1067 private void checkOutputFile(File output, ModificationTracker tracker)
1069 MojoExecutionException
1073 if (output.exists())
1074 tracker.track(SCRIPT, new FileInputStream(output));
1076 tracker.track(SCRIPT, ZonedDateTime.now().toString());
1078 catch (IOException e)
1081 "Error while checking the generated script: " + e.getMessage();
1082 getLog().error(error);
1083 throw new MojoExecutionException(error);
1087 private void addMappings(MetadataSources sources, ModificationTracker tracker)
1088 throws MojoFailureException
1090 getLog().debug("Adding explicitly configured mappings...");
1091 if (mappings != null)
1095 for (String filename : mappings.split("[\\s,]+"))
1097 // First try the filename as absolute/relative path
1098 File file = new File(filename);
1101 // If the file was not found, search for it in the resource-directories
1102 for (Resource resource : project.getResources())
1104 file = new File(resource.getDirectory() + File.separator + filename);
1111 if (file.isDirectory())
1112 // TODO: add support to read all mappings under a directory
1113 throw new MojoFailureException(file.getAbsolutePath() + " is a directory");
1114 if (tracker.track(filename, new FileInputStream(file)))
1115 getLog().debug("Found new or modified mapping-file: " + filename);
1117 getLog().debug("Mapping-file unchanged: " + filename);
1119 sources.addFile(file);
1122 throw new MojoFailureException("File " + filename + " could not be found in any of the configured resource-directories!");
1125 catch (IOException e)
1127 throw new MojoFailureException("Cannot calculate MD5 sums!", e);
1132 private void addRoot(Set<URL> urls, String path) throws MojoFailureException
1136 File dir = new File(outputDirectory);
1139 getLog().info("Adding " + dir.getAbsolutePath() + " to the list of roots to scan...");
1140 urls.add(dir.toURI().toURL());
1143 catch (MalformedURLException e)
1145 getLog().error("error while adding the project-root to the list of roots to scan!", e);
1146 throw new MojoFailureException(e.getMessage());
1150 private void addDependencies(Set<URL> urls) throws MojoFailureException
1154 if (scanDependencies != null)
1156 Matcher matcher = SPLIT.matcher(scanDependencies);
1157 while (matcher.find())
1159 getLog().info("Adding dependencies from scope " + matcher.group() + " to the list of roots to scan");
1160 for (Artifact artifact : project.getDependencyArtifacts())
1162 if (!artifact.getScope().equalsIgnoreCase(matcher.group()))
1164 if (artifact.getFile() == null)
1166 getLog().warn("Cannot add dependency " + artifact.getId() + ": no JAR-file available!");
1169 getLog().info("Adding dependencies from scope " + artifact.getId() + " to the list of roots to scan");
1170 urls.add(artifact.getFile().toURI().toURL());
1175 catch (MalformedURLException e)
1177 getLog().error("Error while adding dependencies to the list of roots to scan!", e);
1178 throw new MojoFailureException(e.getMessage());
1182 private Set<String> scanUrls(Set<URL> scanRoots)
1184 MojoFailureException
1188 AnnotationDB db = new AnnotationDB();
1189 for (URL root : scanRoots)
1190 db.scanArchives(root);
1192 Set<String> classes = new HashSet<String>();
1193 if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
1194 classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
1195 if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
1196 classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
1197 if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
1198 classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
1204 getLog().error("Error while scanning!", e);
1205 throw new MojoFailureException(e.getMessage());
1209 private void addAnnotated(
1211 MetadataSources sources,
1212 ClassLoaderService classLoaderService,
1213 ModificationTracker tracker
1216 MojoFailureException,
1217 MojoExecutionException
1221 getLog().info("Adding annotated resource: " + name);
1222 String packageName = null;
1224 boolean error = false;
1227 Class<?> annotatedClass = classLoaderService.classForName(name);
1228 String resourceName = annotatedClass.getName();
1230 resourceName.substring(
1231 resourceName.lastIndexOf(".") + 1,
1232 resourceName.length()
1234 InputStream is = annotatedClass.getResourceAsStream(resourceName);
1237 if (tracker.track(name, is))
1238 getLog().debug("New or modified class: " + name);
1240 getLog().debug("Unchanged class: " + name);
1241 sources.addAnnotatedClass(annotatedClass);
1242 packageName = annotatedClass.getPackage().getName();
1246 getLog().error("cannot find ressource " + resourceName + " for class " + name);
1250 catch(ClassLoadingException e)
1256 throw new MojoExecutionException("error while inspecting annotated class " + name);
1259 while (packageName != null)
1261 if (packages.contains(packageName))
1263 String resource = packageName.replace('.', '/') + "/package-info.class";
1264 InputStream is = classLoaderService.locateResourceStream(resource);
1267 // No compiled package-info available: no package-level annotations!
1268 getLog().debug("Package " + packageName + " is not annotated.");
1272 if (tracker.track(packageName, is))
1273 getLog().debug("New or modified package: " + packageName);
1275 getLog().debug("Unchanged package: " + packageName);
1276 getLog().info("Adding annotations from package " + packageName);
1277 sources.addPackage(packageName);
1279 packages.add(packageName);
1280 int i = packageName.lastIndexOf('.');
1284 packageName = packageName.substring(0,i);
1289 getLog().error("Error while adding the annotated class " + name, e);
1290 throw new MojoFailureException(e.getMessage());
1294 private ParsedPersistenceXmlDescriptor loadPersistenceUnit(
1295 ClassLoaderService classLoaderService,
1296 Properties properties
1299 MojoFailureException
1301 PersistenceXmlParser parser =
1302 new PersistenceXmlParser(
1304 PersistenceUnitTransactionType.RESOURCE_LOCAL
1307 Map<String, ParsedPersistenceXmlDescriptor> units =
1308 parser.doResolve(properties);
1310 if (persistenceUnit == null)
1312 Iterator<String> names = units.keySet().iterator();
1313 if (!names.hasNext())
1315 getLog().info("Found no META-INF/persistence.xml.");
1319 String name = names.next();
1320 if (!names.hasNext())
1322 getLog().info("Using persistence-unit " + name);
1323 return units.get(name);
1326 StringBuilder builder = new StringBuilder();
1327 builder.append("No name provided and multiple persistence units found: ");
1328 builder.append(name);
1329 while(names.hasNext())
1331 builder.append(", ");
1332 builder.append(names.next());
1334 builder.append('.');
1335 throw new MojoFailureException(builder.toString());
1338 if (units.containsKey(persistenceUnit))
1340 getLog().info("Using configured persistence-unit " + persistenceUnit);
1341 return units.get(persistenceUnit);
1344 throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit);