package de.juplo.plugins.hibernate; import com.pyx4j.log4j.MavenLogAppender; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; 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; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.registry.BootstrapServiceRegistry; 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.selector.spi.StrategySelector; import org.hibernate.boot.spi.MetadataImplementor; import static org.hibernate.cfg.AvailableSettings.DIALECT; import static org.hibernate.cfg.AvailableSettings.DRIVER; import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY; import static org.hibernate.cfg.AvailableSettings.PASS; import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY; import static org.hibernate.cfg.AvailableSettings.USER; import static org.hibernate.cfg.AvailableSettings.URL; 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.scannotation.AnnotationDB; /** * Baseclass with common attributes and methods. * * @phase process-classes * @threadSafe * @requiresDependencyResolution runtime */ public abstract class AbstractSchemaMojo extends AbstractMojo { public final static String EXPORT_SKIPPED_PROPERTY = "hibernate.export.skipped"; private final static Pattern SPLIT = Pattern.compile("[^,\\s]+"); /** * The maven project. *
* Only needed internally. * * @parameter property="project" * @required * @readonly */ private MavenProject project; /** * Build-directory. *
* Only needed internally. * * @parameter property="project.build.directory" * @required * @readonly */ String buildDirectory; /** * Classes-Directory to scan. *
* This parameter defaults to the maven build-output-directory for classes. * Additionally, all dependencies are scanned for annotated classes. * * @parameter property="project.build.outputDirectory" * @since 1.0 */ private String outputDirectory; /** * Whether to scan test-classes too, or not. *
* If this parameter is set to true
the test-classes of the
* artifact will be scanned for hibernate-annotated classes additionally.
*
* @parameter property="hibernate.export.scan_testclasses" default-value="false"
* @since 1.0.1
*/
private boolean scanTestClasses;
/**
* Dependency-Scopes, that should be scanned for annotated classes.
*
* 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
.
*
* The plugin does not scan for annotated classes in transitive * 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" * @since 1.0.3 */ private String scanDependencies; /** * Test-Classes-Directory to scan. *
* This parameter defaults to the maven build-output-directory for * test-classes. *
* This parameter is only used, when scanTestClasses
is set
* to true
!
*
* @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;
/**
* SQL-Driver name.
*
* @parameter property="hibernate.connection.driver_class"
* @since 1.0
*/
private String driver;
/**
* Database URL.
*
* @parameter property="hibernate.connection.url"
* @since 1.0
*/
private String url;
/**
* Database username
*
* @parameter property="hibernate.connection.username"
* @since 1.0
*/
private String username;
/**
* Database password
*
* @parameter property="hibernate.connection.password"
* @since 1.0
*/
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;
/**
* Path to a file or name of a ressource with hibernate properties.
* If this parameter is specified, the plugin will try to load configuration
* values from a file with the given path or a ressource on the classpath with
* the given name. If both fails, the execution of the plugin will fail.
*
* If this parameter is not set the plugin will load configuration values
* from a ressource named hibernate.properties
on the classpath,
* if it is present, but will not fail if there is no such ressource.
*
* During ressource-lookup, the test-classpath takes precedence. * * @parameter * @since 1.0 */ private String hibernateProperties; /** * Path to Hibernate configuration file (.cfg.xml). * If this parameter is specified, the plugin will try to load configuration * values from a file with the given path or a ressource on the classpath with * the given name. If both fails, the execution of the plugin will fail. *
* If this parameter is not set the plugin will load configuration values
* from a ressource named hibernate.cfg.xml
on the classpath,
* if it is present, but will not fail if there is no such ressource.
*
* During ressource-lookup, the test-classpath takes precedence. *
* Settings in this file will overwrite settings in the properties file. * * @parameter * @since 1.1.0 */ private String hibernateConfig; /** * Name of the persistence-unit. * If this parameter is specified, the plugin will try to load configuration * values from a persistence-unit with the specified name. If no such * persistence-unit can be found, the plugin will throw an exception. *
* If this parameter is not set and there is only one persistence-unit * available, that unit will be used automatically. But if this parameter is * not set and there are multiple persistence-units available on, * the class-path, the execution of the plugin will fail. *
* Settings in this file will overwrite settings in the properties or the
* configuration file.
*
* @parameter
* @since 1.1.0
*/
private String persistenceUnit;
/**
* List of Hibernate-Mapping-Files (XML).
* Multiple files can be separated with white-spaces and/or commas.
*
* @parameter property="hibernate.mapping"
* @since 1.0.2
*/
private String mappings;
@Override
public final void execute()
throws
MojoFailureException,
MojoExecutionException
{
if (skip)
{
getLog().info("Execution of hibernate-maven-plugin was skipped!");
project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
return;
}
ModificationTracker tracker;
try
{
tracker = new ModificationTracker(buildDirectory, getLog());
}
catch (NoSuchAlgorithmException e)
{
throw new MojoFailureException("Digest-Algorithm MD5 is missing!", e);
}
SimpleConnectionProvider connectionProvider =
new SimpleConnectionProvider(getLog());
try
{
/** Start extended logging */
MavenLogAppender.startPluginLog(this);
/** Load checksums for old mapping and configuration */
tracker.load();
/** Create a BootstrapServiceRegistry with special ClassLoader */
BootstrapServiceRegistry bootstrapServiceRegitry =
new BootstrapServiceRegistryBuilder()
.applyClassLoader(createClassLoader())
.build();
ClassLoaderService classLoaderService =
bootstrapServiceRegitry.getService(ClassLoaderService.class);
Properties properties = new Properties();
ConfigLoader configLoader = new ConfigLoader(bootstrapServiceRegitry);
/** Loading and merging configuration */
properties.putAll(loadProperties(configLoader));
properties.putAll(loadConfig(configLoader));
properties.putAll(loadPersistenceUnit(classLoaderService, properties));
/** Overwriting/Completing configuration */
configure(properties);
/** Check configuration for modifications */
if(tracker.check(properties))
getLog().debug("Configuration has changed.");
else
getLog().debug("Configuration unchanged.");
/** Configure Hibernate */
StandardServiceRegistry serviceRegistry =
new StandardServiceRegistryBuilder(bootstrapServiceRegitry)
.applySettings(properties)
.addService(ConnectionProvider.class, connectionProvider)
.build();
/** Load Mappings */
MetadataSources sources = new MetadataSources(serviceRegistry);
addAnnotatedClasses(sources, classLoaderService, tracker);
addMappings(sources, tracker);
/** Skip execution, if mapping and configuration is unchanged */
if (!tracker.modified())
{
getLog().info(
"Mapping and configuration unchanged."
);
if (force)
getLog().info("Schema generation is forced!");
else
{
getLog().info("Skipping schema generation!");
project.getProperties().setProperty(EXPORT_SKIPPED_PROPERTY, "true");
return;
}
}
/** Create a connection, if sufficient configuration infromation is available */
connectionProvider.open(classLoaderService, properties);
MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
StrategySelector strategySelector =
serviceRegistry.getService(StrategySelector.class);
if (properties.containsKey(IMPLICIT_NAMING_STRATEGY))
{
metadataBuilder.applyImplicitNamingStrategy(
strategySelector.resolveStrategy(
ImplicitNamingStrategy.class,
properties.getProperty(IMPLICIT_NAMING_STRATEGY)
)
);
}
if (properties.containsKey(PHYSICAL_NAMING_STRATEGY))
{
metadataBuilder.applyPhysicalNamingStrategy(
strategySelector.resolveStrategy(
PhysicalNamingStrategy.class,
properties.getProperty(PHYSICAL_NAMING_STRATEGY)
)
);
}
build((MetadataImplementor)metadataBuilder.build());
}
finally
{
/** Remember mappings and configuration */
tracker.save();
/** Close the connection - if one was opened */
connectionProvider.close();
/** Stop Log-Capturing */
MavenLogAppender.endPluginLog(this);
}
}
abstract void build(MetadataImplementor metadata)
throws
MojoFailureException,
MojoExecutionException;
private URLClassLoader createClassLoader() throws MojoExecutionException
{
try
{
getLog().debug("Creating ClassLoader for project-dependencies...");
List