--- /dev/null
+package de.halbekunst;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.pyx4j.log4j.MavenLogAppender;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import javax.persistence.Entity;
+import javax.persistence.Embeddable;
+import javax.persistence.MappedSuperclass;
+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.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+import org.hibernate.tool.hbm2ddl.SchemaExport.Type;
+import org.hibernate.tool.hbm2ddl.Target;
+import org.scannotation.AnnotationDB;
+
+
+/**
+ * Goal which extracts the hibernate-mapping-configuration and
+ * exports an according SQL-database-schema.
+ *
+ * @goal export
+ * @phase process-classes
+ * @threadSafe
+ * @requiresDependencyResolution runtime
+ */
+public class Hbm2DdlMojo extends AbstractMojo
+{
+ /**
+ * The project whose project files to create.
+ *
+ * @parameter expression="${project}"
+ * @required
+ * @readonly
+ */
+ private MavenProject project;
+
+ /**
+ * Directories to scan.
+ *
+ * @parameter expression="${project.build.outputDirectory}"
+ */
+ private String outputDirectory;
+
+ /**
+ * Skip execution
+ *
+ * @parameter default-value="false"
+ */
+ private boolean skip;
+
+ /**
+ * SQL-Driver name.
+ *
+ * @parameter expression="${hibernate.connection.driver_class}
+ */
+ private String driverClassName;
+
+ /**
+ * Database URL.
+ *
+ * @parameter expression="${hibernate.connection.url}"
+ */
+ private String url;
+
+ /**
+ * Database username
+ *
+ * @parameter expression="${hibernate.connection.username}"
+ */
+ private String username;
+
+ /**
+ * Database password
+ *
+ * @parameter expression="${hibernate.connection.password}"
+ */
+ private String password;
+
+ /**
+ * Hibernate dialect.
+ *
+ * @parameter expression="${hibernate.dialect}"
+ */
+ private String hibernateDialect;
+
+ /**
+ * Hibernate configuration file.
+ *
+ * @parameter default-value="${project.build.outputDirectory}/hibernate.properties"
+ */
+ private String hibernateProperties;
+
+ /**
+ * Target of execution:
+ * <ul>
+ * <li><strong>NONE</strong> do nothing - just validate the configuration</li>
+ * <li><strong>EXPORT</strong> create database <strong>(DEFAULT!)</strong></li>
+ * <li><strong>SCRIPT</strong> export schema to SQL-script</li>
+ * <li><strong>BOTH</strong></li>
+ * </ul>
+ * @parameter default-value="EXPORT"
+ */
+ private String target;
+
+ /**
+ * Type of export.
+ * <ul>
+ * <li><strong>NONE</strong> do nothing - just validate the configuration</li>
+ * <li><strong>CREATE</strong> create database-schema</li>
+ * <li><strong>DROP</strong> drop database-schema</li>
+ * <li><strong>BOTH</strong> <strong>(DEFAULT!)</strong></li>
+ * </ul>
+ * @parameter default-value="BOTH"
+ */
+ private String type;
+
+ /**
+ * Output file.
+ *
+ * @parameter default-value="${project.build.outputDirectory}/schema.sql"
+ */
+ private String outputFile;
+
+ /**
+ * Delimiter in output-file.
+ *
+ * @parameter default-value=";"
+ */
+ private String delimiter;
+
+ /**
+ * Format output-file.
+ *
+ * @parameter default-value="true"
+ */
+ private boolean format;
+
+
+ @Override
+ public void execute()
+ throws
+ MojoFailureException,
+ MojoExecutionException
+ {
+ if (skip)
+ return;
+
+ File dir = new File(outputDirectory);
+ if (!dir.exists())
+ throw new MojoExecutionException("Cannot scan for annotated classes in " + outputDirectory + ": directory does not exist!");
+
+
+ Set<String> classes = new HashSet<String>();
+ URL dirUrl = null;
+ try {
+ AnnotationDB db = new AnnotationDB();
+ getLog().info("Scanning directory " + outputDirectory + " for annotated classes...");
+ dirUrl = dir.toURI().toURL();
+ db.scanArchives(dirUrl);
+ if (db.getAnnotationIndex().containsKey(Entity.class.getName()))
+ classes.addAll(db.getAnnotationIndex().get(Entity.class.getName()));
+ if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName()))
+ classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName()));
+ if (db.getAnnotationIndex().containsKey(Embeddable.class.getName()))
+ classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName()));
+ }
+ catch (IOException e) {
+ getLog().error("Error while scanning!", e);
+ throw new MojoFailureException(e.getMessage());
+ }
+ if (classes.isEmpty())
+ throw new MojoFailureException("No annotated classes found in directory " + outputDirectory);
+
+ Properties properties = new Properties();
+
+ /** Try to read configuration from properties-file */
+ try {
+ File file = new File(hibernateProperties);
+ if (file.exists()) {
+ getLog().info("Reading properties from file " + hibernateProperties + "...");
+ properties.load(new FileInputStream(file));
+ }
+ else
+ getLog().info("Ignoring nonexistent properties-file " + hibernateProperties + "!");
+ }
+ catch (IOException e) {
+ getLog().error("Error while reading properties!", e);
+ throw new MojoExecutionException(e.getMessage());
+ }
+
+ /** Overwrite values from propertie-file or set if given */
+ if (driverClassName != null)
+ properties.setProperty("hibernate.connection.driver_class", driverClassName);
+ if (url != null)
+ properties.setProperty("hibernate.connection.url", url);
+ if (username != null)
+ properties.setProperty("hibernate.connection.username", username);
+ if (password != null)
+ properties.setProperty("hibernate.connection.password", password);
+ if (hibernateDialect != null)
+ properties.setProperty("hibernate.dialect", hibernateDialect);
+
+ if (properties.isEmpty())
+ getLog().warn("No properties set!");
+ for (Entry<Object,Object> entry : properties.entrySet())
+ getLog().debug(entry.getKey() + " = " + entry.getValue());
+
+ ClassLoader classLoader = null;
+ try {
+ getLog().debug("Creating ClassLoader for project-dependencies...");
+ List<String> classpathFiles = project.getCompileClasspathElements();
+ URL[] urls = new URL[classpathFiles.size()];
+ for (int i = 0; i < classpathFiles.size(); ++i) {
+ getLog().debug("Dependency: " + classpathFiles.get(i));
+ urls[i] = new File(classpathFiles.get(i)).toURI().toURL();
+ }
+ classLoader = new URLClassLoader(urls, getClass().getClassLoader());
+ }
+ catch (Exception e) {
+ getLog().error("Error while creating ClassLoader!", e);
+ throw new MojoExecutionException(e.getMessage());
+ }
+
+ Configuration config = new Configuration();
+ config.setProperties(properties);
+ try {
+ getLog().debug("Adding annotated classes to hibernate-mapping-configuration...");
+ for (String annotatedClass : classes) {
+ getLog().debug("Class " + annotatedClass);
+ config.addAnnotatedClass(classLoader.loadClass(annotatedClass));
+ }
+ }
+ catch (ClassNotFoundException e) {
+ getLog().error("Error while adding annotated classes!", e);
+ throw new MojoExecutionException(e.getMessage());
+ }
+
+ Target target = null;
+ try {
+ target = Target.valueOf(this.target);
+ }
+ catch (IllegalArgumentException e) {
+ getLog().error("Invalid value for configuration-option \"target\": " + this.target);
+ getLog().error("Valid values are: NONE, SCRIPT, EXPORT, BOTH");
+ throw new MojoExecutionException("Invalid value for configuration-option \"target\"");
+ }
+ Type type = null;
+ try {
+ type = Type.valueOf(this.type);
+ }
+ catch (IllegalArgumentException e) {
+ getLog().error("Invalid value for configuration-option \"type\": " + this.type);
+ getLog().error("Valid values are: NONE, CREATE, DROP, BOTH");
+ throw new MojoExecutionException("Invalid value for configuration-option \"type\"");
+ }
+
+ Connection connection = null;
+ try {
+ /**
+ * The connection must be established outside of hibernate, because
+ * hibernate does not use the context-classloader of the current
+ * thread and, hence, would not be able to resolve the driver-class!
+ */
+ switch (target) {
+ case EXPORT:
+ case BOTH:
+ switch (type) {
+ case CREATE:
+ case DROP:
+ case BOTH:
+ Class driverClass = classLoader.loadClass(driverClassName);
+ getLog().debug("Registering JDBC-driver " + driverClass.getName());
+ DriverManager.registerDriver(new DriverProxy((Driver)driverClass.newInstance()));
+ getLog().debug("Opening JDBC-connection to " + url + " as " + username + " with password " + password);
+ connection = DriverManager.getConnection(url, username, password);
+ }
+ }
+ }
+ catch (ClassNotFoundException e) {
+ getLog().error("Dependency for driver-class " + driverClassName + " is missing!");
+ throw new MojoExecutionException(e.getMessage());
+ }
+ catch (Exception e) {
+ getLog().error("Cannot establish connection to database!");
+ Enumeration<Driver> drivers = DriverManager.getDrivers();
+ if (!drivers.hasMoreElements())
+ getLog().error("No drivers registered!");
+ while (drivers.hasMoreElements())
+ getLog().debug("Driver: " + drivers.nextElement());
+ throw new MojoExecutionException(e.getMessage());
+ }
+
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ MavenLogAppender.startPluginLog(this);
+ try {
+ /**
+ * Change class-loader of current thread, so that hibernate can
+ * see all dependencies!
+ */
+ Thread.currentThread().setContextClassLoader(classLoader);
+
+ SchemaExport export = new SchemaExport(config, connection);
+ export.setOutputFile(outputFile);
+ export.setDelimiter(delimiter);
+ export.setFormat(format);
+ export.execute(target, type);
+
+ for (Object exception : export.getExceptions())
+ getLog().debug(exception.toString());
+ }
+ finally {
+ /** Stop Log-Capturing */
+ MavenLogAppender.endPluginLog(this);
+
+ /** Restore the old class-loader (TODO: is this really necessary?) */
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+
+ /** Close the connection */
+ try {
+ connection.close();
+ }
+ catch (SQLException e) {
+ getLog().error("Error while closing connection: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Needed, because DriverManager won't pick up drivers, that were not
+ * loaded by the system-classloader!
+ * See:
+ * http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location
+ */
+ static final class DriverProxy implements Driver {
+
+ private final Driver target;
+
+ DriverProxy(Driver target) {
+ if (target == null) {
+ throw new NullPointerException();
+ }
+ this.target = target;
+ }
+
+ public java.sql.Driver getTarget() {
+ return target;
+ }
+
+ @Override
+ public boolean acceptsURL(String url) throws SQLException {
+ return target.acceptsURL(url);
+ }
+
+ @Override
+ public java.sql.Connection connect(
+ String url, java.util.Properties info) throws SQLException {
+ return target.connect(url, info);
+ }
+
+ @Override
+ public int getMajorVersion() {
+ return target.getMajorVersion();
+ }
+
+ @Override
+ public int getMinorVersion() {
+ return target.getMinorVersion();
+ }
+
+ @Override
+ public DriverPropertyInfo[] getPropertyInfo(
+ String url, Properties info) throws SQLException {
+ return target.getPropertyInfo(url, info);
+ }
+
+ @Override
+ public boolean jdbcCompliant() {
+ return target.jdbcCompliant();
+ }
+
+ @Override
+ public String toString() {
+ return "Proxy: " + target;
+ }
+
+ @Override
+ public int hashCode() {
+ return target.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DriverProxy)) {
+ return false;
+ }
+ DriverProxy other = (DriverProxy) obj;
+ return this.target.equals(other.target);
+ }
+}
+}