X-Git-Url: https://juplo.de/gitweb/?p=hibernate4-maven-plugin;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fde%2Fjuplo%2Fplugins%2Fhibernate%2FAbstractSchemaMojo.java;h=b7acbf007f39dbd04c9aae0577da3bb738d41996;hp=4135b618e360db7c1e3c6591c2a4e94353ff7145;hb=9e37fd1049f8a6794396cd7c8879dc413fbcb87c;hpb=b316a5b4122c3490047b68e1e4a6df205645aad5 diff --git a/src/main/java/de/juplo/plugins/hibernate/AbstractSchemaMojo.java b/src/main/java/de/juplo/plugins/hibernate/AbstractSchemaMojo.java index 4135b618..b7acbf00 100644 --- a/src/main/java/de/juplo/plugins/hibernate/AbstractSchemaMojo.java +++ b/src/main/java/de/juplo/plugins/hibernate/AbstractSchemaMojo.java @@ -4,15 +4,19 @@ package de.juplo.plugins.hibernate; import com.pyx4j.log4j.MavenLogAppender; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -23,7 +27,6 @@ import java.util.regex.Pattern; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; -import javax.persistence.spi.PersistenceUnitTransactionType; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; @@ -33,6 +36,8 @@ import org.apache.maven.project.MavenProject; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.cfgxml.internal.ConfigLoader; +import org.hibernate.boot.cfgxml.spi.LoadedConfig; +import org.hibernate.boot.cfgxml.spi.MappingReference; import org.hibernate.boot.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.registry.BootstrapServiceRegistry; @@ -40,24 +45,37 @@ import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; import static org.hibernate.cfg.AvailableSettings.DIALECT; import static org.hibernate.cfg.AvailableSettings.DRIVER; +import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL; +import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DELIMITER; +import static org.hibernate.cfg.AvailableSettings.HBM2DLL_CREATE_NAMESPACES; import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY; +import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER; +import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD; +import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL; +import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER; import static org.hibernate.cfg.AvailableSettings.PASS; import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY; +import static org.hibernate.cfg.AvailableSettings.SHOW_SQL; import static org.hibernate.cfg.AvailableSettings.USER; import static org.hibernate.cfg.AvailableSettings.URL; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.internal.util.config.ConfigurationException; -import static org.hibernate.jpa.AvailableSettings.JDBC_DRIVER; -import static org.hibernate.jpa.AvailableSettings.JDBC_PASSWORD; -import static org.hibernate.jpa.AvailableSettings.JDBC_URL; -import static org.hibernate.jpa.AvailableSettings.JDBC_USER; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; -import org.hibernate.jpa.boot.spi.ProviderChecker; +import org.hibernate.tool.schema.TargetType; +import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl; +import org.hibernate.tool.schema.internal.exec.ScriptTargetOutputToFile; +import org.hibernate.tool.schema.spi.ExecutionOptions; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.tool.schema.spi.ScriptTargetOutput; +import org.hibernate.tool.schema.spi.TargetDescriptor; import org.scannotation.AnnotationDB; @@ -70,10 +88,19 @@ import org.scannotation.AnnotationDB; */ public abstract class AbstractSchemaMojo extends AbstractMojo { - public final static String EXPORT_SKIPPED_PROPERTY = "hibernate.export.skipped"; + public final static String EXECUTE = "hibernate.schema.execute"; + public final static String OUTPUTDIRECTORY = "project.build.outputDirectory"; + public final static String SCAN_CLASSES = "hibernate.schema.scan.classes"; + public final static String SCAN_DEPENDENCIES = "hibernate.schema.scan.dependencies"; + public final static String SCAN_TESTCLASSES = "hibernate.schema.scan.test_classes"; + public final static String TEST_OUTPUTDIRECTORY = "project.build.testOutputDirectory"; + public final static String SKIPPED = "hibernate.schema.skipped"; + public final static String SCRIPT = "hibernate.schema.script"; private final static Pattern SPLIT = Pattern.compile("[^,\\s]+"); + private final Set packages = new HashSet(); + /** * The maven project. @@ -95,29 +122,159 @@ public abstract class AbstractSchemaMojo extends AbstractMojo * @required * @readonly */ - String buildDirectory; + private String buildDirectory; + + + /** Parameters to configure the genaration of the SQL *********************/ /** - * Classes-Directory to scan. + * Excecute the generated SQL. + * If set to false, only the SQL-script is created and the + * database is not touched. *

- * This parameter defaults to the maven build-output-directory for classes. - * Additionally, all dependencies are scanned for annotated classes. + * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! * - * @parameter property="project.build.outputDirectory" + * @parameter property="hibernate.schema.execute" default-value="true" + * @since 2.0 + */ + private Boolean execute; + + /** + * Skip execution + *

+ * If set to true, the execution is skipped. + *

+ * A skipped execution is signaled via the maven-property + * ${hibernate.schema.skipped}. + *

+ * The execution is skipped automatically, if no modified or newly added + * annotated classes are found and the dialect was not changed. + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! + * + * @parameter property="hibernate.schema.skip" default-value="${maven.test.skip}" * @since 1.0 */ - private String outputDirectory; + private boolean skip; /** - * Whether to scan test-classes too, or not. + * Force generation/execution *

- * If this parameter is set to true the test-classes of the - * artifact will be scanned for hibernate-annotated classes additionally. + * Force the generation and (if configured) the execution of the SQL, even if + * no modified or newly added annotated classes where found and the + * configuration was not changed. + *

+ * skip takes precedence over force. + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! * - * @parameter property="hibernate.export.scan_testclasses" default-value="false" - * @since 1.0.1 + * @parameter property="hibernate.schema.force" default-value="false" + * @since 1.0 + */ + private boolean force; + + /** + * Hibernate dialect. + * + * @parameter property="hibernate.dialect" + * @since 1.0 + */ + private String dialect; + + /** + * Delimiter in output-file. + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! + * + * @parameter property="hibernate.hbm2ddl.delimiter" default-value=";" + * @since 1.0 + */ + private String delimiter; + + /** + * Show the generated SQL in the command-line output. + * + * @parameter property="hibernate.show_sql" + * @since 1.0 + */ + private Boolean show; + + /** + * Format output-file. + * + * @parameter property="hibernate.format_sql" + * @since 1.0 + */ + private Boolean format; + + /** + * Specifies whether to automatically create also the database schema/catalog. + * + * @parameter property="hibernate.hbm2dll.create_namespaces" default-value="false" + * @since 2.0 + */ + private Boolean createNamespaces; + + /** + * Implicit naming strategy + * + * @parameter property="hibernate.implicit_naming_strategy" + * @since 2.0 + */ + private String implicitNamingStrategy; + + /** + * Physical naming strategy + * + * @parameter property="hibernate.physical_naming_strategy" + * @since 2.0 + */ + private String physicalNamingStrategy; + + /** + * Wether the project should be scanned for annotated-classes, or not + *

+ * This parameter is intended to allow overwriting of the parameter + * exclude-unlisted-classes of a persistence-unit. + * If not specified, it defaults to true + * + * @parameter property="hibernate.schema.scan.classes" + * @since 2.0 + */ + private Boolean scanClasses; + + /** + * Classes-Directory to scan. + *

+ * This parameter defaults to the maven build-output-directory for classes. + * Additionally, all dependencies are scanned for annotated classes. + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! + * + * @parameter property="project.build.outputDirectory" + * @since 1.0 */ - private boolean scanTestClasses; + private String outputDirectory; /** * Dependency-Scopes, that should be scanned for annotated classes. @@ -125,7 +282,7 @@ public abstract class AbstractSchemaMojo extends AbstractMojo * By default, only dependencies in the scope compile are * scanned for annotated classes. Multiple scopes can be seperated by * white space or commas. - *

md5s + *

* If you do not want any dependencies to be scanned for annotated * classes, set this parameter to none. *

@@ -133,11 +290,29 @@ public abstract class AbstractSchemaMojo extends AbstractMojo * dependencies. If some of your annotated classes are hidden in a * transitive dependency, you can simply add that dependency explicitly. * - * @parameter property="hibernate.export.scan_dependencies" default-value="compile" + * @parameter property="hibernate.schema.scan.dependencies" default-value="compile" * @since 1.0.3 */ private String scanDependencies; + /** + * Whether to scan the test-branch of the project for annotated classes, or + * not. + *

+ * If this parameter is set to true the test-classes of the + * artifact will be scanned for hibernate-annotated classes additionally. + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! + * + * @parameter property="hibernate.schema.scan.test_classes" default-value="false" + * @since 1.0.1 + */ + private Boolean scanTestClasses; + /** * Test-Classes-Directory to scan. *

@@ -146,40 +321,20 @@ public abstract class AbstractSchemaMojo extends AbstractMojo *

* This parameter is only used, when scanTestClasses is set * to true! + *

+ * Important: + * This configuration value can only be configured through the + * pom.xml, or by the definition of a system-property, because + * it is not known by Hibernate nor JPA and, hence, not picked up from + * their configuration! * * @parameter property="project.build.testOutputDirectory" * @since 1.0.2 */ private String testOutputDirectory; - /** - * Skip execution - *

- * If set to true, the execution is skipped. - *

- * A skipped execution is signaled via the maven-property - * ${hibernate.export.skipped}. - *

- * The execution is skipped automatically, if no modified or newly added - * annotated classes are found and the dialect was not changed. - * - * @parameter property="hibernate.skip" default-value="${maven.test.skip}" - * @since 1.0 - */ - private boolean skip; - /** - * Force execution - *

- * Force execution, even if no modified or newly added annotated classes - * where found and the dialect was not changed. - *

- * skip takes precedence over force. - * - * @parameter property="hibernate.export.force" default-value="false" - * @since 1.0 - */ - private boolean force; + /** Conection parameters *************************************************/ /** * SQL-Driver name. @@ -213,29 +368,8 @@ public abstract class AbstractSchemaMojo extends AbstractMojo */ private String password; - /** - * Hibernate dialect. - * - * @parameter property="hibernate.dialect" - * @since 1.0 - */ - private String dialect; - - /** - * Implicit naming strategy - * - * @parameter property=IMPLICIT_NAMING_STRATEGY - * @since 2.0 - */ - private String implicitNamingStrategy; - /** - * Physical naming strategy - * - * @parameter property=PHYSICAL_NAMING_STRATEGY - * @since 2.0 - */ - private String physicalNamingStrategy; + /** Parameters to locate configuration sources ****************************/ /** * Path to a file or name of a ressource with hibernate properties. @@ -302,8 +436,8 @@ public abstract class AbstractSchemaMojo extends AbstractMojo private String mappings; - @Override - public final void execute() + + public final void execute(String filename) throws MojoFailureException, MojoExecutionException @@ -311,21 +445,21 @@ public abstract class AbstractSchemaMojo extends AbstractMojo if (skip) { getLog().info("Execution of hibernate-maven-plugin was skipped!"); - project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true"); + project.getProperties().setProperty(SKIPPED, "true"); return; } ModificationTracker tracker; try { - tracker = new ModificationTracker(buildDirectory, getLog()); + tracker = new ModificationTracker(buildDirectory, filename, getLog()); } catch (NoSuchAlgorithmException e) { throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e); } - SimpleConnectionProvider connectionProvider = + final SimpleConnectionProvider connectionProvider = new SimpleConnectionProvider(getLog()); try @@ -336,10 +470,13 @@ public abstract class AbstractSchemaMojo extends AbstractMojo /** Load checksums for old mapping and configuration */ tracker.load(); - /** Create a BootstrapServiceRegistry with special ClassLoader */ + /** Create the ClassLoader */ + MutableClassLoader classLoader = createClassLoader(); + + /** Create a BootstrapServiceRegistry with the created ClassLoader */ BootstrapServiceRegistry bootstrapServiceRegitry = new BootstrapServiceRegistryBuilder() - .applyClassLoader(createClassLoader()) + .applyClassLoader(classLoader) .build(); ClassLoaderService classLoaderService = bootstrapServiceRegitry.getService(ClassLoaderService.class); @@ -347,49 +484,195 @@ public abstract class AbstractSchemaMojo extends AbstractMojo Properties properties = new Properties(); ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry); - /** Loading and merging configuration */ + /** Loading configuration */ properties.putAll(loadProperties(configLoader)); - properties.putAll(loadConfig(configLoader)); - properties.putAll(loadPersistenceUnit(classLoaderService, properties)); + LoadedConfig config = loadConfig(configLoader); + if (config != null) + properties.putAll(config.getConfigurationValues()); + + /** Add the remaining class-path-elements */ + addDirectDependenciesClassPath(classLoader); + + /** Loading and merging configuration from persistence-unit(s) */ + ParsedPersistenceXmlDescriptor unit = + loadPersistenceUnit(classLoader, properties); + if (unit != null) + properties.putAll(unit.getProperties()); /** Overwriting/Completing configuration */ - configure(properties); + configure(properties, tracker); /** Check configuration for modifications */ - if(tracker.check(properties)) + if(tracker.track(properties)) getLog().debug("Configuration has changed."); else getLog().debug("Configuration unchanged."); + /** Check, that the outputfile is writable */ + final File output = getOutputFile(filename); + /** Check, if the outputfile is missing or was changed */ + checkOutputFile(output, tracker); + /** Configure Hibernate */ - StandardServiceRegistry serviceRegistry = + final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapServiceRegitry) .applySettings(properties) .addService(ConnectionProvider.class, connectionProvider) .build(); + final MetadataSources sources = new MetadataSources(serviceRegistry); - /** Load Mappings */ - MetadataSources sources = new MetadataSources(serviceRegistry); - addAnnotatedClasses(sources, classLoaderService, tracker); + /** Add the remaining class-path-elements */ + addAllDependenciesToClassPath(classLoader); + + /** Apply mappings from hibernate-configuration, if present */ + if (config != null) + { + for (MappingReference mapping : config.getMappingReferences()) + mapping.apply(sources); + } + + Set classes; + if (unit == null) + { + /** No persistent unit: default behaviour */ + if (scanClasses == null) + scanClasses = true; + Set urls = new HashSet(); + getLog().debug("Compiling the dependencies, that are scanned for annotated classes"); + if (scanClasses) + addRoot(urls, outputDirectory); + if (scanTestClasses) + addRoot(urls, testOutputDirectory); + addDependencies(urls); + classes = scanUrls(urls); + } + else + { + /** Follow configuration in persisten unit */ + if (scanClasses == null) + scanClasses = !unit.isExcludeUnlistedClasses(); + getLog().debug("Compiling the dependencies, that are scanned for annotated classes"); + + Set urls = new HashSet(); + if (scanClasses) + { + getLog().debug("Only dependencies relative to persistent-unit " + unit.getName() + " are scanned!"); + /** + * Scan the root of the persiten unit and configured jars for + * annotated classes + */ + getLog().debug(" - adding " + unit.getPersistenceUnitRootUrl()); + urls.add(unit.getPersistenceUnitRootUrl()); + for (URL url : unit.getJarFileUrls()) + { + getLog().debug(" - adding " + url); + urls.add(url); + } + if (scanTestClasses) + addRoot(urls, testOutputDirectory); + } + else + getLog().debug("Scanning of unlisted classes is prohibited in persistent-unit " + unit.getName()); + classes = scanUrls(urls); + for (String className : unit.getManagedClassNames()) + classes.add(className); + /** + * Add mappings from the default mapping-file + * META-INF/orm.xml, if present + */ + boolean error = false; + InputStream is; + is = classLoader.getResourceAsStream("META-INF/orm.xml"); + if (is != null) + { + getLog().info("Adding default JPA-XML-mapping from META-INF/orm.xml"); + try + { + tracker.track("META-INF/orm.xml", is); + sources.addResource("META-INF/orm.xml"); + } + catch (IOException e) + { + getLog().error("cannot read META-INF/orm.xml: " + e); + error = true; + } + } + else + { + getLog().debug("no META-INF/orm.xml found"); + } + /** + * Add mappings from files, that are explicitly configured in the + * persistence unit + */ + getLog().info("Adding mappings from persistence-unit " + unit.getName()); + for (String mapping : unit.getMappingFileNames()) + { + getLog().info(" - adding " + mapping); + is = classLoader.getResourceAsStream(mapping); + if (is != null) + { + try + { + tracker.track(mapping, is); + sources.addResource(mapping); + } + catch (IOException e) + { + getLog().info("cannot read mapping-file " + mapping + ": " + e); + error = true; + } + } + else + { + getLog().error("cannot find mapping-file " + mapping); + error = true; + } + } + if (error) + throw new MojoFailureException( + "error, while reading mappings configured in persistence-unit \"" + + unit.getName() + + "\"" + ); + } + + /** Add the configured/collected annotated classes */ + for (String className : classes) + addAnnotated(className, sources, classLoaderService, tracker); + + /** Add explicitly configured classes */ addMappings(sources, tracker); /** Skip execution, if mapping and configuration is unchanged */ if (!tracker.modified()) { - getLog().info( - "Mapping and configuration unchanged." - ); + getLog().info("Mapping and configuration unchanged."); if (force) - getLog().info("Schema generation is forced!"); + getLog().info("Generation/execution is forced!"); else { getLog().info("Skipping schema generation!"); - project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true"); + project.getProperties().setProperty(SKIPPED, "true"); return; } } + /** Truncate output file */ + try + { + new FileOutputStream(output).getChannel().truncate(0).close(); + } + catch (IOException e) + { + String error = + "Error while truncating " + output.getAbsolutePath() + ": " + + e.getMessage(); + getLog().warn(error); + throw new MojoExecutionException(error); + } + /** Create a connection, if sufficient configuration infromation is available */ connectionProvider.open(classLoaderService, properties); @@ -418,7 +701,90 @@ public abstract class AbstractSchemaMojo extends AbstractMojo ); } - build((MetadataImplementor)metadataBuilder.build()); + /** Prepare the generation of the SQL */ + Map settings = new HashMap(); + settings.putAll( + serviceRegistry + .getService(ConfigurationService.class) + .getSettings() + ); + ExceptionHandlerCollectingImpl handler = + new ExceptionHandlerCollectingImpl(); + ExecutionOptions options = + SchemaManagementToolCoordinator + .buildExecutionOptions(settings, handler); + final EnumSet targetTypes = EnumSet.of(TargetType.SCRIPT); + if (execute) + targetTypes.add(TargetType.DATABASE); + TargetDescriptor target = new TargetDescriptor() + { + @Override + public EnumSet getTargetTypes() + { + return targetTypes; + } + + @Override + public ScriptTargetOutput getScriptTargetOutput() + { + String charset = + (String) + serviceRegistry + .getService(ConfigurationService.class) + .getSettings() + .get(AvailableSettings.HBM2DDL_CHARSET_NAME); + return new ScriptTargetOutputToFile(output, charset); + } + }; + + /** + * Change class-loader of current thread. + * This is necessary, because still not all parts of Hibernate 5 use + * the newly introduced ClassLoaderService and will fail otherwise! + */ + Thread thread = Thread.currentThread(); + ClassLoader contextClassLoader = thread.getContextClassLoader(); + try + { + thread.setContextClassLoader(classLoader); + build((MetadataImplementor)metadataBuilder.build(), options, target); + if (handler.getExceptions().size() > 0) + { + StringBuilder builder = new StringBuilder(); + builder.append("Hibernate failed:"); + for (Exception e : handler.getExceptions()) + { + builder.append("\n * "); + builder.append(e.getMessage()); + AbstractSchemaMojo.printStrackTrace(builder, e); + builder.append("\n"); + } + String error = builder.toString(); + getLog().error(error); + throw new MojoFailureException(error); + } + } + finally + { + thread.setContextClassLoader(contextClassLoader); + /** Track, the content of the generated script */ + checkOutputFile(output, tracker); + } + } + catch (MojoExecutionException e) + { + tracker.failed(); + throw e; + } + catch (MojoFailureException e) + { + tracker.failed(); + throw e; + } + catch (RuntimeException e) + { + tracker.failed(); + throw e; } finally { @@ -434,46 +800,85 @@ public abstract class AbstractSchemaMojo extends AbstractMojo } - abstract void build(MetadataImplementor metadata) + abstract void build( + MetadataImplementor metadata, + ExecutionOptions options, + TargetDescriptor target + ) throws MojoFailureException, MojoExecutionException; - private URLClassLoader createClassLoader() throws MojoExecutionException + private MutableClassLoader createClassLoader() throws MojoExecutionException { try { getLog().debug("Creating ClassLoader for project-dependencies..."); - List classpathFiles = project.getCompileClasspathElements(); - if (scanTestClasses) - classpathFiles.addAll(project.getTestClasspathElements()); - List urls = new LinkedList(); + LinkedHashSet urls = new LinkedHashSet(); File file; + file = new File(testOutputDirectory); if (!file.exists()) { - getLog().info("creating test-output-directory: " + testOutputDirectory); + getLog().info("Creating test-output-directory: " + testOutputDirectory); file.mkdirs(); } urls.add(file.toURI().toURL()); + file = new File(outputDirectory); if (!file.exists()) { - getLog().info("creating output-directory: " + outputDirectory); + getLog().info("Creating output-directory: " + outputDirectory); file.mkdirs(); } urls.add(file.toURI().toURL()); + + return new MutableClassLoader(urls, getLog()); + } + catch (Exception e) + { + getLog().error("Error while creating ClassLoader!", e); + throw new MojoExecutionException(e.getMessage()); + } + } + + private void addDirectDependenciesClassPath(MutableClassLoader classLoader) + throws + MojoExecutionException + { + try + { + getLog().debug("Adding all direct project-dependencies to the ClassLoader..."); + LinkedHashSet urls = new LinkedHashSet(); + addDependencies(urls); + if (scanTestClasses) + addRoot(urls, testOutputDirectory); + classLoader.add(urls); + } + catch (Exception e) + { + getLog().error("Error while creating ClassLoader!", e); + throw new MojoExecutionException(e.getMessage()); + } + } + + private void addAllDependenciesToClassPath(MutableClassLoader classLoader) + throws + MojoExecutionException + { + try + { + getLog().debug("Adding all project-dependencies to the ClassLoader..."); + List classpathFiles = project.getCompileClasspathElements(); + classpathFiles.addAll(project.getTestClasspathElements()); + LinkedHashSet urls = new LinkedHashSet(); for (String pathElement : classpathFiles) { - getLog().debug("Dependency: " + pathElement); + getLog().debug(" - adding " + pathElement); urls.add(new File(pathElement).toURI().toURL()); } - return - new URLClassLoader( - urls.toArray(new URL[urls.size()]), - getClass().getClassLoader() - ); + classLoader.add(urls); } catch (Exception e) { @@ -520,7 +925,7 @@ public abstract class AbstractSchemaMojo extends AbstractMojo } } - private Map loadConfig(ConfigLoader configLoader) + private LoadedConfig loadConfig(ConfigLoader configLoader) throws MojoExecutionException { /** Try to read configuration from configuration-file */ @@ -528,15 +933,12 @@ public abstract class AbstractSchemaMojo extends AbstractMojo { try { - return - configLoader - .loadConfigXmlResource("hibernate.cfg.xml") - .getConfigurationValues(); + return configLoader.loadConfigXmlResource("hibernate.cfg.xml"); } catch (ConfigurationException e) { getLog().debug(e.getMessage()); - return Collections.EMPTY_MAP; + return null; } } else @@ -547,13 +949,12 @@ public abstract class AbstractSchemaMojo extends AbstractMojo if (file.exists()) { getLog().info("Reading configuration from file " + hibernateConfig + "..."); - return configLoader.loadConfigXmlFile(file).getConfigurationValues(); + return configLoader.loadConfigXmlFile(file); } else - return - configLoader - .loadConfigXmlResource(hibernateConfig) - .getConfigurationValues(); + { + return configLoader.loadConfigXmlResource(hibernateConfig); + } } catch (ConfigurationException e) { @@ -563,18 +964,57 @@ public abstract class AbstractSchemaMojo extends AbstractMojo } } - private void configure(Properties properties) + private void configure(Properties properties, ModificationTracker tracker) throws MojoFailureException { - /** Overwrite values from properties-file or set, if given */ + /** + * Special treatment for the configuration-value "execute": if it is + * switched to "true", the genearation fo the schema should be forced! + */ + if (tracker.check(EXECUTE, execute.toString()) && execute) + { + getLog().info( + "hibernate.schema.execute was switched on: " + + "forcing generation/execution of SQL" + ); + tracker.touch(); + } + configure(properties, execute, EXECUTE); - configure(properties, driver, DRIVER, JDBC_DRIVER); - configure(properties, url, URL, JDBC_URL); - configure(properties, username, USER, JDBC_USER); - configure(properties, password, PASS, JDBC_PASSWORD); + /** + * Configure the generation of the SQL. + * Overwrite values from properties-file if the configuration parameter is + * known to Hibernate. + */ configure(properties, dialect, DIALECT); + configure(properties, delimiter, HBM2DDL_DELIMITER); + configure(properties, format, FORMAT_SQL); + configure(properties, createNamespaces, HBM2DLL_CREATE_NAMESPACES); configure(properties, implicitNamingStrategy, IMPLICIT_NAMING_STRATEGY); configure(properties, physicalNamingStrategy, PHYSICAL_NAMING_STRATEGY); + configure(properties, outputDirectory, OUTPUTDIRECTORY); + configure(properties, scanDependencies, SCAN_DEPENDENCIES); + configure(properties, scanTestClasses, SCAN_TESTCLASSES); + configure(properties, testOutputDirectory, TEST_OUTPUTDIRECTORY); + + /** + * Special treatment for the configuration-value "show": a change of its + * configured value should not lead to a regeneration of the database + * schama! + */ + if (show == null) + show = Boolean.valueOf(properties.getProperty(SHOW_SQL)); + else + properties.setProperty(SHOW_SQL, show.toString()); + + /** + * Configure the connection parameters. + * Overwrite values from properties-file. + */ + configure(properties, driver, DRIVER, JPA_JDBC_DRIVER); + configure(properties, url, URL, JPA_JDBC_URL); + configure(properties, username, USER, JPA_JDBC_USER); + configure(properties, password, PASS, JPA_JDBC_PASSWORD); if (properties.isEmpty()) { @@ -582,7 +1022,7 @@ public abstract class AbstractSchemaMojo extends AbstractMojo throw new MojoFailureException("Hibernate configuration is missing!"); } - getLog().info("Gathered hibernate-configuration (turn on debugging for details):"); + getLog().info("Gathered configuration:"); for (Entry entry : properties.entrySet()) getLog().info(" " + entry.getKey() + " = " + entry.getValue()); } @@ -595,14 +1035,29 @@ public abstract class AbstractSchemaMojo extends AbstractMojo ) { configure(properties, value, key); - if (properties.containsKey(key) && properties.containsKey(alternativeKey)) + + if (properties.containsKey(alternativeKey)) { - getLog().warn( - "Ignoring property " + alternativeKey + "=" + - properties.getProperty(alternativeKey) + " in favour for property " + - key + "=" + properties.getProperty(key) - ); - properties.remove(JDBC_DRIVER); + if (properties.containsKey(key)) + { + getLog().warn( + "Ignoring property " + alternativeKey + "=\"" + + properties.getProperty(alternativeKey) + + "\" in favour for property " + key + "=\"" + + properties.getProperty(key) + "\"" + ); + properties.remove(alternativeKey); + } + else + { + value = properties.getProperty(alternativeKey); + properties.remove(alternativeKey); + getLog().info( + "Using value \"" + value + "\" from property " + alternativeKey + + " for property " + key + ); + properties.setProperty(key, value); + } } } @@ -611,13 +1066,103 @@ public abstract class AbstractSchemaMojo extends AbstractMojo if (value != null) { if (properties.containsKey(key)) - getLog().debug( - "Overwriting property " + key + "=" + properties.getProperty(key) + - " with the value " + value + { + if (!properties.getProperty(key).equals(value)) + { + getLog().info( + "Overwriting property " + key + "=\"" + + properties.getProperty(key) + + "\" with value \"" + value + "\"" + ); + properties.setProperty(key, value); + } + } + else + { + getLog().debug("Using value \"" + value + "\" for property " + key); + properties.setProperty(key, value); + } + } + } + + private void configure(Properties properties, Boolean value, String key) + { + configure(properties, value == null ? null : value.toString(), key); + } + + private File getOutputFile(String filename) + throws + MojoExecutionException + { + File output = new File(filename); + + if (!output.isAbsolute()) + { + // Interpret relative file path relative to build directory + output = new File(buildDirectory, filename); + } + getLog().debug("Output file: " + output.getPath()); + + // Ensure that directory path for specified file exists + File outFileParentDir = output.getParentFile(); + if (null != outFileParentDir && !outFileParentDir.exists()) + { + try + { + getLog().info( + "Creating directory path for output file:" + + outFileParentDir.getPath() ); + outFileParentDir.mkdirs(); + } + catch (Exception e) + { + String error = + "Error creating directory path for output file: " + e.getMessage(); + getLog().error(error); + throw new MojoExecutionException(error); + } + } + + try + { + output.createNewFile(); + } + catch (IOException e) + { + String error = "Error creating output file: " + e.getMessage(); + getLog().error(error); + throw new MojoExecutionException(error); + } + + if (!output.canWrite()) + { + String error = + "Output file " + output.getAbsolutePath() + " is not writable!"; + getLog().error(error); + throw new MojoExecutionException(error); + } + + return output; + } + + private void checkOutputFile(File output, ModificationTracker tracker) + throws + MojoExecutionException + { + try + { + if (output.exists()) + tracker.track(SCRIPT, new FileInputStream(output)); else - getLog().debug("Using the value " + value + " for property " + key); - properties.setProperty(key, value); + tracker.track(SCRIPT, ZonedDateTime.now().toString()); + } + catch (IOException e) + { + String error = + "Error while checking the generated script: " + e.getMessage(); + getLog().error(error); + throw new MojoExecutionException(error); } } @@ -648,10 +1193,10 @@ public abstract class AbstractSchemaMojo extends AbstractMojo if (file.isDirectory()) // TODO: add support to read all mappings under a directory throw new MojoFailureException(file.getAbsolutePath() + " is a directory"); - if (tracker.check(filename, new FileInputStream(file))) - getLog().debug("Found new or modified mapping-file: " + filename); + if (tracker.track(filename, new FileInputStream(file))) + getLog().debug(" - found new or modified mapping-file: " + filename); else - getLog().debug("mapping-file unchanged: " + filename); + getLog().debug(" - mapping-file unchanged: " + filename); sources.addFile(file); } @@ -666,60 +1211,71 @@ public abstract class AbstractSchemaMojo extends AbstractMojo } } - private void addAnnotatedClasses( - MetadataSources sources, - ClassLoaderService classLoaderService, - ModificationTracker tracker - ) - throws - MojoFailureException, - MojoExecutionException - + private void addRoot(Set urls, String path) throws MojoFailureException { try { - AnnotationDB db = new AnnotationDB(); - File dir; - - dir = new File(outputDirectory); + File dir = new File(path); if (dir.exists()) { - getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes..."); - URL dirUrl = dir.toURI().toURL(); - db.scanArchives(dirUrl); - } - - if (scanTestClasses) - { - dir = new File(testOutputDirectory); - if (dir.exists()) - { - getLog().info("Scanning directory " + dir.getAbsolutePath() + " for annotated classes..."); - URL dirUrl = dir.toURI().toURL(); - db.scanArchives(dirUrl); - } + getLog().info(" - adding " + dir.getAbsolutePath()); + urls.add(dir.toURI().toURL()); } + else + getLog().warn( + "The directory cannot be scanned for annotated classes, " + + "because it does not exist: " + + dir.getAbsolutePath() + ); + } + catch (MalformedURLException e) + { + getLog().error("error while adding the project-root to the list of roots to scan!", e); + throw new MojoFailureException(e.getMessage()); + } + } + private void addDependencies(Set urls) throws MojoFailureException + { + try + { if (scanDependencies != null) { Matcher matcher = SPLIT.matcher(scanDependencies); while (matcher.find()) { - getLog().info("Scanning dependencies for scope " + matcher.group()); + getLog().debug("Adding dependencies from scope " + matcher.group() + " to the list of roots to scan"); for (Artifact artifact : project.getDependencyArtifacts()) { if (!artifact.getScope().equalsIgnoreCase(matcher.group())) continue; if (artifact.getFile() == null) { - getLog().warn("Cannot scan dependency " + artifact.getId() + ": no JAR-file available!"); + getLog().warn("Cannot add dependency " + artifact.getId() + ": no JAR-file available!"); continue; } - getLog().info("Scanning dependency " + artifact.getId() + " for annotated classes..."); - db.scanArchives(artifact.getFile().toURI().toURL()); + getLog().debug(" - adding " + artifact.getId()); + urls.add(artifact.getFile().toURI().toURL()); } } } + } + catch (MalformedURLException e) + { + getLog().error("Error while adding dependencies to the list of roots to scan!", e); + throw new MojoFailureException(e.getMessage()); + } + } + + private Set scanUrls(Set scanRoots) + throws + MojoFailureException + { + try + { + AnnotationDB db = new AnnotationDB(); + for (URL root : scanRoots) + db.scanArchives(root); Set classes = new HashSet(); if (db.getAnnotationIndex().containsKey(Entity.class.getName())) @@ -729,113 +1285,185 @@ public abstract class AbstractSchemaMojo extends AbstractMojo if (db.getAnnotationIndex().containsKey(Embeddable.class.getName())) classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName())); - Set packages = new HashSet(); + return classes; + } + catch (Exception e) + { + getLog().error("Error while scanning!", e); + throw new MojoFailureException(e.getMessage()); + } + } - for (String name : classes) + private void addAnnotated( + String name, + MetadataSources sources, + ClassLoaderService classLoaderService, + ModificationTracker tracker + ) + throws + MojoFailureException, + MojoExecutionException + { + try + { + getLog().info("Adding annotated resource: " + name); + String packageName = null; + + boolean error = false; + try { Class annotatedClass = classLoaderService.classForName(name); - String packageName = annotatedClass.getPackage().getName(); - if (!packages.contains(packageName)) - { - InputStream is = - annotatedClass.getResourceAsStream("package-info.class"); - if (is == null) - { - // No compiled package-info available: no package-level annotations! - getLog().debug("Package " + packageName + " is not annotated."); - } - else - { - if (tracker.check(packageName, is)) - getLog().debug("New or modified package: " + packageName); - else - getLog().debug("Unchanged package: " + packageName); - getLog().info("Adding annotated package " + packageName); - sources.addPackage(packageName); - } - packages.add(packageName); - } String resourceName = annotatedClass.getName(); resourceName = resourceName.substring( resourceName.lastIndexOf(".") + 1, resourceName.length() ) + ".class"; - InputStream is = - annotatedClass - .getResourceAsStream(resourceName); - if (tracker.check(name, is)) - getLog().debug("New or modified class: " + name); + InputStream is = annotatedClass.getResourceAsStream(resourceName); + if (is != null) + { + if (tracker.track(name, is)) + getLog().debug("New or modified class: " + name); + else + getLog().debug("Unchanged class: " + name); + sources.addAnnotatedClass(annotatedClass); + packageName = annotatedClass.getPackage().getName(); + } else - getLog().debug("Unchanged class: " + name); - getLog().info("Adding annotated class " + annotatedClass); - sources.addAnnotatedClass(annotatedClass); + { + getLog().error("cannot find ressource " + resourceName + " for class " + name); + error = true; + } + } + catch(ClassLoadingException e) + { + packageName = name; + } + if (error) + { + throw new MojoExecutionException("error while inspecting annotated class " + name); + } + + while (packageName != null) + { + if (packages.contains(packageName)) + return; + String resource = packageName.replace('.', '/') + "/package-info.class"; + InputStream is = classLoaderService.locateResourceStream(resource); + if (is == null) + { + // No compiled package-info available: no package-level annotations! + getLog().debug("Package " + packageName + " is not annotated."); + } + else + { + if (tracker.track(packageName, is)) + getLog().debug("New or modified package: " + packageName); + else + getLog().debug("Unchanged package: " + packageName); + getLog().info("Adding annotations from package " + packageName); + sources.addPackage(packageName); + } + packages.add(packageName); + int i = packageName.lastIndexOf('.'); + if (i < 0) + packageName = null; + else + packageName = packageName.substring(0,i); } } catch (Exception e) { - getLog().error("Error while scanning!", e); + getLog().error("Error while adding the annotated class " + name, e); throw new MojoFailureException(e.getMessage()); } } - private Properties loadPersistenceUnit( - ClassLoaderService classLoaderService, + private ParsedPersistenceXmlDescriptor loadPersistenceUnit( + ClassLoader classLoader, Properties properties ) throws MojoFailureException { - PersistenceXmlParser parser = - new PersistenceXmlParser( - classLoaderService, - PersistenceUnitTransactionType.RESOURCE_LOCAL - ); + Map settings = + Collections.singletonMap( + AvailableSettings.CLASSLOADERS, + Collections.singletonList(classLoader) + ); + // Find all available persistent unit descriptors + List descriptors = + PersistenceXmlParser.locatePersistenceUnits(settings); - List units = parser.doResolve(properties); + // Find all persistent units in the located descriptors + Map units = new HashMap<>(); + for (ParsedPersistenceXmlDescriptor descriptor : descriptors) + { + String unit = descriptor.getName(); + if (units.containsKey(unit)) + getLog().warn( + "Persistence unit " + unit + + " from " + descriptor.getPersistenceUnitRootUrl() + + " overwrites unit with the same name from " + + units.get(unit).getPersistenceUnitRootUrl() + ); + units.put(unit, descriptor); + } if (persistenceUnit == null) { - switch (units.size()) + Iterator names = units.keySet().iterator(); + if (!names.hasNext()) { - case 0: - getLog().info("Found no META-INF/persistence.xml."); - return new Properties(); - case 1: - getLog().info("Using persistence-unit " + units.get(0).getName()); - return units.get(0).getProperties(); - default: - StringBuilder builder = new StringBuilder(); - builder.append("No name provided and multiple persistence units found: "); - Iterator it = units.iterator(); - builder.append(it.next().getName()); - while (it.hasNext()) - { - builder.append(", "); - builder.append(it.next().getName()); - } - builder.append('.'); - throw new MojoFailureException(builder.toString()); + getLog().info("Found no META-INF/persistence.xml."); + return null; } - } - for (ParsedPersistenceXmlDescriptor unit : units) - { - getLog().debug("Found persistence-unit " + unit.getName()); - if (!unit.getName().equals(persistenceUnit)) - continue; + String name = names.next(); + if (!names.hasNext()) + { + getLog().info("Using persistence-unit " + name); + return units.get(name); + } - // See if we (Hibernate) are the persistence provider - if (!ProviderChecker.isProvider(unit, properties)) + StringBuilder builder = new StringBuilder(); + builder.append("No name provided and multiple persistence units found: "); + builder.append(name); + while(names.hasNext()) { - getLog().debug("Wrong provider: " + unit.getProviderClassName()); - continue; + builder.append(", "); + builder.append(names.next()); } + builder.append('.'); + throw new MojoFailureException(builder.toString()); + } - getLog().info("Using persistence-unit " + unit.getName()); - return unit.getProperties(); + if (units.containsKey(persistenceUnit)) + { + getLog().info("Using configured persistence-unit " + persistenceUnit); + return units.get(persistenceUnit); } throw new MojoFailureException("Could not find persistence-unit " + persistenceUnit); } + + + public static void printStrackTrace(StringBuilder builder, Throwable t) + { + while (t != null) + { + builder.append("\n\tCause: "); + builder.append(t.getMessage() == null ? "" : t.getMessage().replaceAll("\\s+", " ")); + for (StackTraceElement trace : t.getStackTrace()) + { + builder.append("\n\t"); + builder.append(trace.getClassName()); + builder.append("."); + builder.append(trace.getMethodName()); + builder.append("():"); + builder.append(trace.getLineNumber()); + } + t = t.getCause(); + } + } }