From 9ab924041db36333b76e510b65ad73200ae0cfe9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Sun, 3 Feb 2013 14:31:43 -0600 Subject: [PATCH] HHH-7957 - Integrate Public Review Draft of the JPA 2.1 spec : schema generation --- .../java/org/hibernate/cfg/Configuration.java | 2 +- .../spi/DatabaseInfoDialectResolver.java | 4 +- .../id/PersistentIdentifierGenerator.java | 3 + .../org/hibernate/jpa/AvailableSettings.java | 167 +++++++ .../jpa/HibernatePersistenceProvider.java | 19 +- .../org/hibernate/jpa/SchemaGenAction.java | 101 +++++ .../org/hibernate/jpa/SchemaGenSource.java | 103 +++++ .../org/hibernate/jpa/SchemaGenTarget.java | 92 ++++ .../EntityManagerFactoryBuilderImpl.java | 27 ++ .../boot/spi/EntityManagerFactoryBuilder.java | 5 + .../internal/schemagen/DatabaseTarget.java | 102 +++++ .../internal/schemagen/FileScriptSource.java | 75 +++ .../internal/schemagen/GenerationSource.java | 53 +++ .../internal/schemagen/GenerationTarget.java | 53 +++ .../schemagen/JdbcConnectionContext.java | 67 +++ .../schemagen/JpaSchemaGenerator.java | 428 ++++++++++++++++++ .../internal/schemagen/MetadataSource.java | 57 +++ .../schemagen/ReaderScriptSource.java | 63 +++ .../jpa/internal/schemagen/ScriptSource.java | 84 ++++ .../jpa/internal/schemagen/ScriptsTarget.java | 153 +++++++ .../internal/schemagen/SqlScriptReader.java | 36 ++ 21 files changed, 1687 insertions(+), 7 deletions(-) create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenTarget.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java create mode 100644 hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index fdc0c029a4..6dfe1ace55 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -876,7 +876,7 @@ public class Configuration implements Serializable { @SuppressWarnings({ "unchecked" }) - private Iterator iterateGenerators(Dialect dialect) throws MappingException { + public Iterator iterateGenerators(Dialect dialect) throws MappingException { TreeMap generators = new TreeMap(); String defaultCatalog = properties.getProperty( Environment.DEFAULT_CATALOG ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java index de2efd779c..9eccc26347 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/dialect/spi/DatabaseInfoDialectResolver.java @@ -32,8 +32,6 @@ import org.hibernate.service.Service; * @author Steve Ebersole */ public interface DatabaseInfoDialectResolver extends Service { - public static final int NO_VERSION = -9999; - /** * Determine the {@link Dialect} to use based on the given information. Implementations are * expected to return the {@link Dialect} instance to use, or {@code null} if the they did not locate a match. @@ -45,6 +43,8 @@ public interface DatabaseInfoDialectResolver extends Service { public Dialect resolve(DatabaseInfo databaseInfo); public static interface DatabaseInfo { + public static final int NO_VERSION = -9999; + /** * Obtain access to the database name, as returned from {@link java.sql.DatabaseMetaData#getDatabaseProductName()} * for the target database diff --git a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java index 96dc25a88d..51548171b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -96,6 +96,9 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator { */ public Object generatorKey(); + public String getSchema(); + + public String getCatalog(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java index 35ea9ba54f..4c68a1f5eb 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/AvailableSettings.java @@ -24,6 +24,8 @@ package org.hibernate.jpa; +import org.hibernate.internal.util.StringHelper; + /** * Defines the available HEM settings, both JPA-defined as well as Hibernate-specific *

@@ -192,6 +194,171 @@ public interface AvailableSettings { */ public static final String CDI_BEAN_MANAGER = "javax.persistence.bean.manager"; + /** + * Specifies the action to be taken by the persistence provider. The set of possible values are:

    + *
  • none
  • + *
  • create
  • + *
  • drop
  • + *
  • drop-and-create
  • + *
+ * + * If no value is specified, the default is "none". + * + * @see SchemaGenAction + */ + public static final String SCHEMA_GEN_ACTION = "javax.persistence.schema-generation-action"; + + /** + * Specifies whether the schema is to be created in the database, whether scripts are to be generated, or both. + * The values for this property are:
    + *
  • database
  • + *
  • scripts
  • + *
  • database-and-scripts
  • + *
+ * If no value is specified, a default is assumed as follows:
    + *
  • + * if script targets are specified (per {@value #SCHEMA_GEN_CREATE_SCRIPT_TARGET} and + * {@value #SCHEMA_GEN_DROP_SCRIPT_TARGET}), then the default is assumed to be "scripts" + *
  • + *
  • + * Otherwise, "database" is assumed + *
  • + *
+ * + * @see SchemaGenTarget + */ + public static final String SCHEMA_GEN_TARGET = "javax.persistence.schema-generation-target"; + + /** + * If schema creations scripts are to be generated, the target/location for these scripts must be specified. This + * target may take the form of either a {@link java.io.Writer} or a string designating a + * {@link java.net.URL}. + *

+ * Create and drop scripts are written separately (though the same Writer/URL could be passed). + * {@value #SCHEMA_GEN_CREATE_SCRIPT_TARGET} specifies the target for the create script. + * + * @see #SCHEMA_GEN_DROP_SCRIPT_TARGET + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_CREATE_SCRIPT_TARGET = "javax.persistence.ddl-create-script-target"; + + /** + * If schema creations scripts are to be generated, the target/location for these scripts must be specified. This + * target may take the form of either a {@link java.io.Writer} or a string designating a + * {@link java.net.URL}. + *

+ * Create and drop scripts are written separately (though the same Writer/URL could be passed). + * {@value #SCHEMA_GEN_DROP_SCRIPT_TARGET} specifies the target for the create script. + * + * @see #SCHEMA_GEN_CREATE_SCRIPT_TARGET + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_DROP_SCRIPT_TARGET = "javax.persistence.ddl-drop-script-target"; + + /** + * Specifies whether schema generation is to occur on the basis of the object/relational mapping metadata, DDL + * scripts, or a combination of the two. The valid values for this property are:

    + *
  • metadata
  • + *
  • scripts
  • + *
  • metadata-then-scripts
  • + *
  • scripts-then-metadata
  • + *
+ * If no value is specified, a default is assumed as follows:
    + *
  • + * if source scripts are specified (per {@value #SCHEMA_GEN_CREATE_SCRIPT_SOURCE} and + * {@value #SCHEMA_GEN_DROP_SCRIPT_SOURCE}),then "scripts" is assumed + *
  • + *
  • + * otherwise, "metadata" is assumed + *
  • + *
+ * + * @see SchemaGenSource + */ + public static final String SCHEMA_GEN_SOURCE = "javax.persistence.schema-generation-source"; + + /** + * Specifies the CREATE script file as either a {@link java.io.Reader} configured for reading of the DDL script + * file or a string designating a file {@link java.net.URL} for the DDL script. + * + * @see #SCHEMA_GEN_DROP_SCRIPT_SOURCE + */ + public static final String SCHEMA_GEN_CREATE_SCRIPT_SOURCE = "javax.persistence.ddl-create-script-source"; + + /** + * Specifies the DROP script file as either a {@link java.io.Reader} configured for reading of the DDL script + * file or a string designating a file {@link java.net.URL} for the DDL script. + * + * @see #SCHEMA_GEN_CREATE_SCRIPT_SOURCE + */ + public static final String SCHEMA_GEN_DROP_SCRIPT_SOURCE = "javax.persistence.ddl-drop-script-source"; + + /** + * Specifies whether the persistence provider is to create the database schema(s) in addition to creating + * database objects (tables, sequences, constraints, etc). The value of this boolean property should be set + * to {@code true} if the persistence provider is to create schemas in the database or to generate DDL that + * contains “CREATE SCHEMA” commands. If this property is not supplied (or is explicitly {@code false}), the + * provider should not attempt to create database schemas. + */ + public static final String SCHEMA_GEN_CREATE_SCHEMAS = "javax.persistence.create-database-schemas"; + + /** + * Allows passing the specific {@link java.sql.Connection} instance to be used for performing schema generation + * where the target is "database". + *

+ * May also be used to determine the values for {@value #SCHEMA_GEN_DB_NAME}, + * {@value #SCHEMA_GEN_DB_MAJOR_VERSION} and {@value #SCHEMA_GEN_DB_MINOR_VERSION}. + */ + public static final String SCHEMA_GEN_CONNECTION = "javax.persistence.schema-generation-connection"; + + /** + * Specifies the name of the database provider in cases where a Connection to the underlying database is + * not available (aka, mainly in generating scripts). In such cases, a value for + * {@value #SCHEMA_GEN_DB_NAME} *must* be specified. + *

+ * The value of this setting is expected to match the value returned by + * {@link java.sql.DatabaseMetaData#getDatabaseProductName()} for the target database. + *

+ * Additionally specifying {@value #SCHEMA_GEN_DB_MAJOR_VERSION} and/or {@value #SCHEMA_GEN_DB_MINOR_VERSION} + * may be required to understand exactly how to generate the required schema commands. + * + * @see #SCHEMA_GEN_DB_MAJOR_VERSION + * @see #SCHEMA_GEN_DB_MINOR_VERSION + */ + @SuppressWarnings("JavaDoc") + public static final String SCHEMA_GEN_DB_NAME = "javax.persistence.database-product-name"; + + /** + * Specifies the major version of the underlying database, as would be returned by + * {@link java.sql.DatabaseMetaData#getDatabaseMajorVersion} for the target database. This value is used to + * help more precisely determine how to perform schema generation tasks for the underlying database in cases + * where {@value #SCHEMA_GEN_DB_NAME} does not provide enough distinction. + + * @see #SCHEMA_GEN_DB_NAME + * @see #SCHEMA_GEN_DB_MINOR_VERSION + */ + public static final String SCHEMA_GEN_DB_MAJOR_VERSION = "javax.persistence.database-major-version"; + + /** + * Specifies the minor version of the underlying database, as would be returned by + * {@link java.sql.DatabaseMetaData#getDatabaseMinorVersion} for the target database. This value is used to + * help more precisely determine how to perform schema generation tasks for the underlying database in cases + * where te combination of {@value #SCHEMA_GEN_DB_NAME} and {@value #SCHEMA_GEN_DB_MAJOR_VERSION} does not provide + * enough distinction. + * + * @see #SCHEMA_GEN_DB_NAME + * @see #SCHEMA_GEN_DB_MAJOR_VERSION + */ + public static final String SCHEMA_GEN_DB_MINOR_VERSION = "javax.persistence.database-minor-version"; + + /** + * Specifies a {@link java.io.Reader} configured for reading of the SQL load script or a string designating the + * file {@link java.net.URL} for the SQL load script. + *

+ * A "SQL load script" is a script that performs some database initialization (INSERT, etc). + */ + public static final String SCHEMA_GEN_LOAD_SCRIPT_SOURCE = "javax.persistence.sql-load-script-source"; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hibernate specific settings diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java index 961a126ac4..1e54878404 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/HibernatePersistenceProvider.java @@ -36,6 +36,7 @@ import java.util.Map; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.ProviderChecker; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; @@ -56,6 +57,11 @@ public class HibernatePersistenceProvider implements PersistenceProvider { */ @Override public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties) { + final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, properties ); + return builder == null ? null : builder.build(); + } + + private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties) { final Map integration = wrap( properties ); final List units = PersistenceXmlParser.locatePersistenceUnits( integration ); @@ -75,7 +81,7 @@ public class HibernatePersistenceProvider implements PersistenceProvider { continue; } - return Bootstrap.getEntityManagerFactoryBuilder( persistenceUnit, integration ).build(); + return Bootstrap.getEntityManagerFactoryBuilder( persistenceUnit, integration ); } return null; @@ -98,13 +104,18 @@ public class HibernatePersistenceProvider implements PersistenceProvider { @Override public void generateSchema(PersistenceUnitInfo info, Map map) { - // todo : implement + EntityManagerFactoryBuilder builder = Bootstrap.getEntityManagerFactoryBuilder( info, map ); + builder.generateSchema(); } @Override public boolean generateSchema(String persistenceUnitName, Map map) { - // todo : implement - return false; + final EntityManagerFactoryBuilder builder = getEntityManagerFactoryBuilderOrNull( persistenceUnitName, map ); + if ( builder == null ) { + return false; + } + builder.generateSchema(); + return true; } private final ProviderUtil providerUtil = new ProviderUtil() { diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java new file mode 100644 index 0000000000..6c7a7ad481 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenAction.java @@ -0,0 +1,101 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa; + +import org.hibernate.internal.util.StringHelper; + +/** + * Describes the allowable values of the {@value AvailableSettings#SCHEMA_GEN_ACTION} setting. + * + * @see AvailableSettings#SCHEMA_GEN_ACTION + * + * @author Steve Ebersole + */ +public enum SchemaGenAction { + /** + * "none" - no actions will be performed (aka, generation is disabled). + */ + NONE( "none" ), + /** + * "create" - database creation will be generated + */ + CREATE( "create" ), + /** + * "drop" - database dropping will be generated + */ + DROP( "drop" ), + /** + * "drop-and-create" - both database creation and database dropping will be generated. + */ + BOTH( "drop-and-create" ); + + private final String externalName; + + private SchemaGenAction(String externalName) { + this.externalName = externalName; + } + + /** + * Used when processing JPA configuration to interpret the {@value AvailableSettings#SCHEMA_GEN_ACTION} setting. + * + * @param value The encountered value of {@value AvailableSettings#SCHEMA_GEN_ACTION} + * + * @return The matching enum value. An empty value will return {@link #NONE}. + * + * @throws IllegalArgumentException If the incoming value is unrecognized + */ + public static SchemaGenAction interpret(String value) { + if ( StringHelper.isEmpty( value ) ) { + // default is NONE + return NONE; + } + + if ( CREATE.externalName.equals( value ) ) { + return CREATE; + } + else if ( DROP.externalName.equals( value ) ) { + return DROP; + } + else if ( BOTH.externalName.equals( value ) ) { + return BOTH; + } + + throw new IllegalArgumentException( + String.format( "Unrecognized '%s' value : %s", AvailableSettings.SCHEMA_GEN_ACTION, value ) + ); + } + + public boolean includesCreate() { + return this == CREATE || this == BOTH; + } + + public boolean includesDrop() { + return this == DROP || this == BOTH; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + externalName + ")"; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java new file mode 100644 index 0000000000..3634f11e87 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenSource.java @@ -0,0 +1,103 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa; + +import org.hibernate.internal.util.StringHelper; + +/** + * Describes the allowable values of the {@value AvailableSettings#SCHEMA_GEN_SOURCE} setting. + * + * @see AvailableSettings#SCHEMA_GEN_SOURCE + * + * @author Steve Ebersole + */ +public enum SchemaGenSource { + /** + * "metadata" - The O/RM metadata is used as the exclusive source for generation + */ + METADATA( "metadata" ), + /** + * "scripts" - External DDL script(s) are used as the exclusive source for generation. The scripts for schema + * creation and dropping come from different sources. The creation DDL script is identified by the + * {@value AvailableSettings#SCHEMA_GEN_CREATE_SCRIPT_SOURCE} setting; the drop DDL script is identified by the + * {@value AvailableSettings#SCHEMA_GEN_DROP_SCRIPT_SOURCE} setting. + * + * @see AvailableSettings#SCHEMA_GEN_CREATE_SCRIPT_SOURCE + * @see AvailableSettings#SCHEMA_GEN_DROP_SCRIPT_SOURCE + */ + SCRIPTS( "scripts" ), + /** + * "metadata-then-scripts" - Both the O/RM metadata and external DDL scripts are used as sources for generation, + * with the O/RM metadata being applied first. + * + * @see #METADATA + * @see #SCRIPTS + */ + METADATA_THEN_SCRIPTS( "metadata-then-scripts" ), + /** + * "scripts-then-metadata" - Both the O/RM metadata and external DDL scripts are used as sources for generation, + * with the commands from the external DDL script(s) being applied first + * + * @see #SCRIPTS + * @see #METADATA + */ + SCRIPTS_THEN_METADATA( "scripts-then-metadata" ); + + private final String externalName; + + private SchemaGenSource(String externalName) { + this.externalName = externalName; + } + + /** + * Used when processing JPA configuration to interpret the {@value AvailableSettings#SCHEMA_GEN_SOURCE} setting. + * + * @param value The encountered value of {@value AvailableSettings#SCHEMA_GEN_SOURCE} + * + * @return The matching enum value. An empty value will return {@code null}. + * + * @throws IllegalArgumentException If the incoming value is unrecognized + */ + public static SchemaGenSource interpret(String value) { + if ( StringHelper.isEmpty( value ) ) { + // empty is in fact valid as means to interpret default value based on other settings + return null; + } + + if ( METADATA.externalName.equals( value ) ) { + return METADATA; + } + else if ( SCRIPTS.externalName.equals( value ) ) { + return SCRIPTS; + } + else if ( METADATA_THEN_SCRIPTS.externalName.equals( value ) ) { + return METADATA_THEN_SCRIPTS; + } + else if ( SCRIPTS_THEN_METADATA.externalName.equals( value ) ) { + return SCRIPTS_THEN_METADATA; + } + + throw new IllegalArgumentException( "Unrecognized schema generation source value : " + value ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenTarget.java new file mode 100644 index 0000000000..fcb0d4c302 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/SchemaGenTarget.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa; + +import org.hibernate.internal.util.StringHelper; + +/** + * Describes the allowable values of the {@value AvailableSettings#SCHEMA_GEN_TARGET} setting. + * + * @see AvailableSettings#SCHEMA_GEN_TARGET + * + * @author Steve Ebersole + */ +public enum SchemaGenTarget { + /** + * "database" - Generation commands will be executed directly against the database (via JDBC Statements). + */ + DATABASE( "database" ), + /** + * "scripts" - Generation commands will be written to script (text) "targets" as indicated by the + * {@value AvailableSettings#SCHEMA_GEN_CREATE_SCRIPT_TARGET} and + * {@value AvailableSettings#SCHEMA_GEN_DROP_SCRIPT_TARGET} settings. + */ + SCRIPTS( "scripts" ), + /** + * "database-and-scripts" - Generation commands will be sent to both. + * + * @see #DATABASE + * @see #SCRIPTS + */ + BOTH( "database-and-scripts" ); + + private final String externalName; + + private SchemaGenTarget(String externalName) { + this.externalName = externalName; + } + + /** + * Used when processing JPA configuration to interpret the {@value AvailableSettings#SCHEMA_GEN_TARGET} setting. + * + * @param value The encountered value of {@value AvailableSettings#SCHEMA_GEN_TARGET} + * + * @return The matching enum value. An empty value will return {@code null}. + * + * @throws IllegalArgumentException If the incoming value is unrecognized + */ + public static SchemaGenTarget interpret(String value) { + if ( StringHelper.isEmpty( value ) ) { + // empty is in fact valid as means to interpret default value based on other settings + return null; + } + + if ( DATABASE.externalName.equals( value ) ) { + return DATABASE; + } + else if ( SCRIPTS.externalName.equals( value ) ) { + return SCRIPTS; + } + else if ( BOTH.externalName.equals( value ) ) { + return BOTH; + } + + throw new IllegalArgumentException( "Unknown schema generation target value : " + value ); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + externalName + ")"; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 5f9fe063b2..46a3167632 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -81,6 +81,7 @@ import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.IntegratorProvider; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.event.spi.JpaIntegrator; +import org.hibernate.jpa.internal.schemagen.JpaSchemaGenerator; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import org.hibernate.jpa.internal.EntityManagerMessageLogger; import org.hibernate.jpa.internal.util.LogHelper; @@ -731,6 +732,32 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil } + @Override + public void generateSchema() { + processProperties(); + + final ServiceRegistry serviceRegistry = buildServiceRegistry(); + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + + // IMPL NOTE : TCCL handling here is temporary. + // It is needed because this code still uses Hibernate Configuration and Hibernate commons-annotations + // in turn which relies on TCCL being set. + + ( (ClassLoaderServiceImpl) classLoaderService ).withTccl( + new ClassLoaderServiceImpl.Work() { + @Override + public Object perform() { + final Configuration hibernateConfiguration = buildHibernateConfiguration( serviceRegistry ); + JpaSchemaGenerator.performGeneration( hibernateConfiguration, serviceRegistry ); + return null; + } + } + ); + + // release this builder + cancel(); + } + @SuppressWarnings("unchecked") public EntityManagerFactory build() { processProperties(); diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java index abd50f6a94..dc560c8a82 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java @@ -73,4 +73,9 @@ public interface EntityManagerFactoryBuilder { * something having gone wrong during the bootstrap process */ public void cancel(); + + /** + * Perform an explicit schema generation (rather than an "auto" one) based on the + */ + public void generateSchema(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java new file mode 100644 index 0000000000..457bc8c99c --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/DatabaseTarget.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; +import java.sql.Statement; + +import org.jboss.logging.Logger; + +/** + * GenerationTarget implementation for handling generation directly to the database + * + * @see org.hibernate.jpa.SchemaGenTarget#DATABASE + * @see org.hibernate.jpa.SchemaGenTarget#BOTH + * + * @author Steve Ebersole + */ +class DatabaseTarget implements GenerationTarget { + private static final Logger log = Logger.getLogger( DatabaseTarget.class ); + + private final JdbcConnectionContext jdbcConnectionContext; + + private Statement jdbcStatement; + + DatabaseTarget(JdbcConnectionContext jdbcConnectionContext) { + this.jdbcConnectionContext = jdbcConnectionContext; + } + + @Override + public void acceptCreateCommands(Iterable commands) { + for ( String command : commands ) { + try { + jdbcStatement().execute( command ); + } + catch (SQLException e) { + throw new PersistenceException( + "Unable to execute JPA schema generation create command [" + command + "]" + ); + } + } + } + + private Statement jdbcStatement() { + if ( jdbcStatement == null ) { + try { + jdbcStatement = jdbcConnectionContext.getJdbcConnection().createStatement(); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to generate JDBC Statement object for schema generation" ); + } + } + return jdbcStatement; + } + + @Override + public void acceptDropCommands(Iterable commands) { + for ( String command : commands ) { + try { + jdbcStatement().execute( command ); + } + catch (SQLException e) { + throw new PersistenceException( + "Unable to execute JPA schema generation drop command [" + command + "]" + ); + } + } + } + + @Override + public void release() { + if ( jdbcStatement != null ) { + try { + jdbcStatement.close(); + } + catch (SQLException e) { + log.debug( "Unable to close JDBC statement after JPA schema generation : " + e.toString() ); + } + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java new file mode 100644 index 0000000000..297c90fb8e --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/FileScriptSource.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; + +import org.jboss.logging.Logger; + +/** + * SqlScriptReader implementation for File references. A reader is opened here and then explicitly closed on + * {@link #reader}. + * + * @author Steve Ebersole + */ +class FileScriptSource extends ReaderScriptSource implements SqlScriptReader { + private static final Logger log = Logger.getLogger( FileScriptSource.class ); + + public FileScriptSource(String fileUrl) { + super( toFileReader( fileUrl ) ); + } + + @Override + public void release() { + try { + reader().close(); + } + catch (IOException e) { + log.warn( "Unable to close file reader for generation script source" ); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static Reader toFileReader(String fileUrl) { + final File file = new File( fileUrl ); + try { + // best effort, since this is very well not allowed in EE environments + file.createNewFile(); + } + catch (Exception e) { + log.debug( "Exception calling File#createNewFile : " + e.toString() ); + } + try { + return new FileReader( file ); + } + catch (IOException e) { + throw new PersistenceException( "Unable to open specified script target file for writing : " + fileUrl ); + } + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java new file mode 100644 index 0000000000..549661919e --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationSource.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +/** + * Contract describing a generation source for create commands + * + * @see org.hibernate.jpa.SchemaGenSource + * @see org.hibernate.jpa.AvailableSettings#SCHEMA_GEN_SOURCE + * + * @author Steve Ebersole + */ +interface GenerationSource { + /** + * Retrieve the create generation commands from this source. + * + * @return The generation commands + */ + public Iterable getCreateCommands(); + + /** + * Retrieve the drop generation commands from this source + * + * @return The generation commands + */ + public Iterable getDropCommands(); + + /** + * Release this source. Give it a change to release its resources, if any. + */ + public void release(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java new file mode 100644 index 0000000000..6e674455fc --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/GenerationTarget.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +/** + * Describes a schema generation target + * + * @see org.hibernate.jpa.SchemaGenTarget + * @see org.hibernate.jpa.AvailableSettings#SCHEMA_GEN_TARGET + * + * @author Steve Ebersole + */ +interface GenerationTarget { + /** + * Accept a group of create generation commands + * + * @param commands The commands + */ + public void acceptCreateCommands(Iterable commands); + + /** + * Accept a group of drop generation commands. + * + * @param commands The commands + */ + public void acceptDropCommands(Iterable commands); + + /** + * Release this target, giving it a change to release its resources. + */ + public void release(); +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java new file mode 100644 index 0000000000..cc6d90c534 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JdbcConnectionContext.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.sql.Connection; +import java.sql.SQLException; + +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; + +/** + * Defines access to a JDBC Connection for use in Schema generation + * + * @author Steve Ebersole + */ +class JdbcConnectionContext { + private final JdbcConnectionAccess jdbcConnectionAccess; + private Connection jdbcConnection; + + JdbcConnectionContext(JdbcConnectionAccess jdbcConnectionAccess) { + this.jdbcConnectionAccess = jdbcConnectionAccess; + } + + public Connection getJdbcConnection() { + if ( jdbcConnection == null ) { + try { + this.jdbcConnection = jdbcConnectionAccess.obtainConnection(); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to obtain JDBC Connection", e ); + } + } + return jdbcConnection; + } + + public void release() { + if ( jdbcConnection != null ) { + try { + jdbcConnectionAccess.releaseConnection( jdbcConnection ); + } + catch (SQLException e) { + throw new PersistenceException( "Unable to release JDBC Connection", e ); + } + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java new file mode 100644 index 0000000000..011f6bc4dc --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/JpaSchemaGenerator.java @@ -0,0 +1,428 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.Reader; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseInfoDialectResolver; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.PersistentIdentifierGenerator; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.SchemaGenAction; +import org.hibernate.jpa.SchemaGenSource; +import org.hibernate.jpa.SchemaGenTarget; +import org.hibernate.mapping.Table; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * Class responsible for the JPA-defined schema generation behavior. + * + * @author Steve Ebersole + */ +public class JpaSchemaGenerator { + private static final Logger log = Logger.getLogger( JpaSchemaGenerator.class ); + + public static void performGeneration(Configuration hibernateConfiguration, ServiceRegistry serviceRegistry) { + + // First, determine the actions (if any) to be performed ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final SchemaGenAction action = SchemaGenAction.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_ACTION ) + ); + if ( action == SchemaGenAction.NONE ) { + // no generation requested + return; + } + + + // Figure out the JDBC Connection context, if any ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final JdbcConnectionContext jdbcConnectionContext = determineAppropriateJdbcConnectionContext( + hibernateConfiguration, + serviceRegistry + ); + + final Dialect dialect = determineDialect( jdbcConnectionContext, hibernateConfiguration, serviceRegistry ); + + final ImportSqlCommandExtractor scriptCommandExtractor = serviceRegistry.getService( ImportSqlCommandExtractor.class ); + + + // Next, determine the targets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + final List generationTargetList = new ArrayList(); + + SchemaGenTarget target = SchemaGenTarget.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_TARGET ) + ); + + // the default is dependent upon whether script targets were also specified... + final Object createScriptTargetSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CREATE_SCRIPT_TARGET + ); + final Object dropScriptTargetSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CREATE_SCRIPT_TARGET + ); + + if ( target == null ) { + if ( createScriptTargetSetting != null && dropScriptTargetSetting != null ) { + target = SchemaGenTarget.SCRIPTS; + } + else { + target = SchemaGenTarget.DATABASE; + } + } + + if ( target == SchemaGenTarget.DATABASE || target == SchemaGenTarget.BOTH ) { + generationTargetList.add( new DatabaseTarget( jdbcConnectionContext ) ); + } + if ( target == SchemaGenTarget.SCRIPTS || target == SchemaGenTarget.BOTH ) { + // both create and drop scripts are expected per JPA spec + if ( createScriptTargetSetting == null ) { + throw new IllegalArgumentException( "For schema generation creation script target missing" ); + } + if ( dropScriptTargetSetting == null ) { + throw new IllegalArgumentException( "For schema generation drop script target missing" ); + } + generationTargetList.add( new ScriptsTarget( createScriptTargetSetting, dropScriptTargetSetting ) ); + } + + + // determine sources ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + final List generationSourceList = new ArrayList(); + + SchemaGenSource source = SchemaGenSource.interpret( + hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_SOURCE ) + ); + + // the default for sources is dependent upon whether script sources were specified... + final Object createScriptSourceSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CREATE_SCRIPT_SOURCE + ); + final Object dropScriptSourceSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_DROP_SCRIPT_SOURCE + ); + + if ( source == null ) { + if ( createScriptSourceSetting != null && dropScriptSourceSetting != null ) { + source = SchemaGenSource.SCRIPTS; + } + else { + source = SchemaGenSource.METADATA; + } + } + + final boolean createSchemas = ConfigurationHelper.getBoolean( + AvailableSettings.SCHEMA_GEN_CREATE_SCHEMAS, + hibernateConfiguration.getProperties(), + false + ); + if ( createSchemas ) { + // todo : does it make sense to generate schema(s) defined in metadata if only script sources are to be used? + generationSourceList.add( new CreateSchemaCommandSource( hibernateConfiguration, dialect ) ); + } + + if ( source == SchemaGenSource.METADATA ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect ) ); + } + else if ( source == SchemaGenSource.SCRIPTS ) { + generationSourceList.add( new ScriptSource( createScriptSourceSetting, dropScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( source == SchemaGenSource.METADATA_THEN_SCRIPTS ) { + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect ) ); + generationSourceList.add( new ScriptSource( createScriptSourceSetting, dropScriptSourceSetting, scriptCommandExtractor ) ); + } + else if ( source == SchemaGenSource.SCRIPTS_THEN_METADATA ) { + generationSourceList.add( new ScriptSource( createScriptSourceSetting, dropScriptSourceSetting, scriptCommandExtractor ) ); + generationSourceList.add( new MetadataSource( hibernateConfiguration, dialect ) ); + } + + final Object importScriptSetting = hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_LOAD_SCRIPT_SOURCE + ); + if ( importScriptSetting != null ) { + generationSourceList.add( new ImportScriptSource( importScriptSetting, scriptCommandExtractor ) ); + } + + + // do the generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + try { + doGeneration( action, generationSourceList, generationTargetList ); + } + finally { + releaseResources( generationSourceList, generationTargetList, jdbcConnectionContext ); + } + } + + + private static JdbcConnectionContext determineAppropriateJdbcConnectionContext( + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry) { + // see if a specific connection has been provided: + final Connection providedConnection = (Connection) hibernateConfiguration.getProperties().get( + AvailableSettings.SCHEMA_GEN_CONNECTION + ); + + if ( providedConnection != null ) { + return new JdbcConnectionContext( + new JdbcConnectionAccess() { + @Override + public Connection obtainConnection() throws SQLException { + return providedConnection; + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + // do nothing + } + + @Override + public boolean supportsAggressiveRelease() { + return false; + } + } + ); + } + + final ConnectionProvider connectionProvider = serviceRegistry.getService( ConnectionProvider.class ); + if ( connectionProvider != null ) { + return new JdbcConnectionContext( + new JdbcConnectionAccess() { + @Override + public Connection obtainConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + connectionProvider.closeConnection( connection ); + } + + @Override + public boolean supportsAggressiveRelease() { + return connectionProvider.supportsAggressiveRelease(); + } + } + ); + } + + // otherwise, return a no-op impl + return new JdbcConnectionContext( null ) { + @Override + public Connection getJdbcConnection() { + throw new PersistenceException( "No connection information supplied" ); + } + }; + } + + private static Dialect determineDialect( + JdbcConnectionContext jdbcConnectionContext, + Configuration hibernateConfiguration, + ServiceRegistry serviceRegistry) { + final String explicitDbName = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_NAME ); + final String explicitDbMajor = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_MAJOR_VERSION ); + final String explicitDbMinor = hibernateConfiguration.getProperty( AvailableSettings.SCHEMA_GEN_DB_MINOR_VERSION ); + + if ( StringHelper.isNotEmpty( explicitDbName ) ) { + serviceRegistry.getService( DatabaseInfoDialectResolver.class ).resolve( + new DatabaseInfoDialectResolver.DatabaseInfo() { + @Override + public String getDatabaseName() { + return explicitDbName; + } + + @Override + public int getDatabaseMajorVersion() { + return StringHelper.isEmpty( explicitDbMajor ) + ? NO_VERSION + : Integer.parseInt( explicitDbMajor ); + } + + @Override + public int getDatabaseMinorVersion() { + return StringHelper.isEmpty( explicitDbMinor ) + ? NO_VERSION + : Integer.parseInt( explicitDbMinor ); + } + } + ); + } + + return serviceRegistry.getService( JdbcServices.class ).getDialect(); + } + + private static void doGeneration( + SchemaGenAction action, + List generationSourceList, + List generationTargetList) { + + for ( GenerationSource source : generationSourceList ) { + if ( action.includesCreate() ) { + final Iterable createCommands = source.getCreateCommands(); + for ( GenerationTarget target : generationTargetList ) { + target.acceptCreateCommands( createCommands ); + } + } + + if ( action.includesDrop() ) { + final Iterable dropCommands = source.getDropCommands(); + for ( GenerationTarget target : generationTargetList ) { + target.acceptDropCommands( dropCommands ); + } + } + } + } + + private static void releaseResources( + List generationSourceList, + List generationTargetList, + JdbcConnectionContext jdbcConnectionContext) { + for ( GenerationTarget target : generationTargetList ) { + try { + target.release(); + } + catch (Exception e) { + log.debug( "Problem releasing generation target : " + e.toString() ); + } + } + + for ( GenerationSource source : generationSourceList ) { + try { + source.release(); + } + catch (Exception e) { + log.debug( "Problem releasing generation source : " + e.toString() ); + } + } + + try { + jdbcConnectionContext.release(); + } + catch (Exception e) { + log.debug( "Unable to release JDBC connection after generation" ); + } + } + + + private static class CreateSchemaCommandSource implements GenerationSource { + private final List commands; + + private CreateSchemaCommandSource(Configuration hibernateConfiguration, Dialect dialect) { + final HashSet schemas = new HashSet(); +// final HashSet catalogs = new HashSet(); + + final Iterator tables = hibernateConfiguration.getTableMappings(); + while ( tables.hasNext() ) { + final Table table = tables.next(); +// catalogs.add( table.getCatalog() ); + schemas.add( table.getSchema() ); + } + + final Iterator generators = hibernateConfiguration.iterateGenerators( dialect ); + while ( generators.hasNext() ) { + final IdentifierGenerator generator = generators.next(); + if ( PersistentIdentifierGenerator.class.isInstance( generator ) ) { +// catalogs.add( ( (PersistentIdentifierGenerator) generator ).getCatalog() ); + schemas.add( ( (PersistentIdentifierGenerator) generator ).getCatalog() ); + } + } + +// if ( schemas.isEmpty() && catalogs.isEmpty() ) { + if ( schemas.isEmpty() ) { + commands = Collections.emptyList(); + return; + } + + commands = new ArrayList(); + + for ( String schema : schemas ) { + commands.add( dialect.getCreateSchemaCommand( schema ) ); + } + + // generate "create catalog" commands + } + + @Override + public Iterable getCreateCommands() { + return commands; + } + + @Override + public Iterable getDropCommands() { + return Collections.emptyList(); + } + + @Override + public void release() { + // nothing to do + } + } + + private static class ImportScriptSource implements GenerationSource { + private final SqlScriptReader sourceReader; + private final ImportSqlCommandExtractor scriptCommandExtractor; + + public ImportScriptSource(Object scriptSourceSetting, ImportSqlCommandExtractor scriptCommandExtractor) { + this.scriptCommandExtractor = scriptCommandExtractor; + + if ( Reader.class.isInstance( scriptSourceSetting ) ) { + sourceReader = new ReaderScriptSource( (Reader) scriptSourceSetting ); + } + else { + sourceReader = new FileScriptSource( scriptSourceSetting.toString() ); + } + } + + @Override + public Iterable getCreateCommands() { + return sourceReader.read( scriptCommandExtractor ); + } + + @Override + public Iterable getDropCommands() { + return Collections.emptyList(); + } + + @Override + public void release() { + sourceReader.release(); + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java new file mode 100644 index 0000000000..aaea15f222 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/MetadataSource.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import java.util.Arrays; + +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; + +/** + * @author Steve Ebersole + */ +public class MetadataSource implements GenerationSource { + private final Configuration hibernateConfiguration; + private final Dialect dialect; + + public MetadataSource(Configuration hibernateConfiguration, Dialect dialect) { + this.hibernateConfiguration = hibernateConfiguration; + this.dialect = dialect; + } + + @Override + public Iterable getCreateCommands() { + return Arrays.asList( hibernateConfiguration.generateSchemaCreationScript( dialect ) ); + } + + @Override + public Iterable getDropCommands() { + return Arrays.asList( hibernateConfiguration.generateDropSchemaScript( dialect ) ); + } + + @Override + public void release() { + // nothing to do + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java new file mode 100644 index 0000000000..8127a7abe0 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ReaderScriptSource.java @@ -0,0 +1,63 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import java.io.Reader; +import java.util.Arrays; +import java.util.Collections; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * SqlScriptReader implementation for explicitly given Readers. The readers are not released by this class. + * + * @author Steve Ebersole + */ +class ReaderScriptSource implements SqlScriptReader { + private final Reader reader; + + public ReaderScriptSource(Reader reader) { + this.reader = reader; + } + + @Override + public Iterable read(ImportSqlCommandExtractor commandExtractor) { + final String[] commands = commandExtractor.extractCommands( reader ); + if ( commands == null ) { + return Collections.emptyList(); + } + else { + return Arrays.asList( commands ); + } + } + + @Override + public void release() { + // nothing to do here + } + + protected Reader reader() { + return reader; + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java new file mode 100644 index 0000000000..c81c9fcaf4 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptSource.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; + +import org.jboss.logging.Logger; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * @author Steve Ebersole + */ +public class ScriptSource implements GenerationSource { + private static final Logger log = Logger.getLogger( ScriptSource.class ); + + private final SqlScriptReader createSource; + private final SqlScriptReader dropSource; + private final ImportSqlCommandExtractor scriptCommandExtractor; + + public ScriptSource( + Object createScriptSourceSetting, + Object dropScriptSourceSetting, + ImportSqlCommandExtractor scriptCommandExtractor) { + this.scriptCommandExtractor = scriptCommandExtractor; + + if ( Reader.class.isInstance( createScriptSourceSetting ) ) { + createSource = new ReaderScriptSource( (Reader) createScriptSourceSetting ); + } + else { + createSource = new FileScriptSource( createScriptSourceSetting.toString() ); + } + + if ( Writer.class.isInstance( dropScriptSourceSetting ) ) { + dropSource = new ReaderScriptSource( (Reader) dropScriptSourceSetting ); + } + else { + dropSource = new FileScriptSource( dropScriptSourceSetting.toString() ); + } + } + + @Override + public Iterable getCreateCommands() { + return createSource.read( scriptCommandExtractor ); + } + + @Override + public Iterable getDropCommands() { + return dropSource.read( scriptCommandExtractor ); + } + + @Override + public void release() { + createSource.release(); + dropSource.release(); + } + +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java new file mode 100644 index 0000000000..1802f1d9d4 --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/ScriptsTarget.java @@ -0,0 +1,153 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import javax.persistence.PersistenceException; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + +import org.jboss.logging.Logger; + +/** + * GenerationTarget implementation for handling generation to scripts + * + * @see org.hibernate.jpa.SchemaGenTarget#SCRIPTS + * @see org.hibernate.jpa.SchemaGenTarget#BOTH + * + * @author Steve Ebersole + */ +class ScriptsTarget implements GenerationTarget { + private static final Logger log = Logger.getLogger( ScriptsTarget.class ); + + private final ScriptTargetTarget createScriptTarget; + private final ScriptTargetTarget dropScriptTarget; + + public ScriptsTarget(Object createScriptTargetSetting, Object dropScriptTargetSetting) { + if ( Writer.class.isInstance( createScriptTargetSetting ) ) { + createScriptTarget = new WriterScriptTarget( (Writer) createScriptTargetSetting ); + } + else { + createScriptTarget = new FileScriptTarget( createScriptTargetSetting.toString() ); + } + + if ( Writer.class.isInstance( dropScriptTargetSetting ) ) { + dropScriptTarget = new WriterScriptTarget( (Writer) dropScriptTargetSetting ); + } + else { + dropScriptTarget = new FileScriptTarget( dropScriptTargetSetting.toString() ); + } + } + + @Override + public void acceptCreateCommands(Iterable commands) { + for ( String command : commands ) { + createScriptTarget.accept( command ); + } + } + + @Override + public void acceptDropCommands(Iterable commands) { + for ( String command : commands ) { + dropScriptTarget.accept( command ); + } + } + + @Override + public void release() { + createScriptTarget.release(); + dropScriptTarget.release(); + } + + /** + * Internal contract for handling Writer/File differences + */ + private static interface ScriptTargetTarget { + public void accept(String command); + public void release(); + } + + private static class WriterScriptTarget implements ScriptTargetTarget { + private final Writer writer; + + public WriterScriptTarget(Writer writer) { + this.writer = writer; + } + + @Override + public void accept(String command) { + try { + writer.write( command ); + writer.flush(); + } + catch (IOException e) { + throw new PersistenceException( "Could not write to target script file", e ); + } + } + + @Override + public void release() { + // nothing to do for a supplied writer + } + + protected Writer writer() { + return writer; + } + } + + private static class FileScriptTarget extends WriterScriptTarget implements ScriptTargetTarget { + public FileScriptTarget(String fileUrl) { + super( toFileWriter( fileUrl ) ); + } + + @Override + public void release() { + try { + writer().close(); + } + catch (IOException e) { + throw new PersistenceException( "Unable to close file writer : " + e.toString() ); + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static Writer toFileWriter(String fileUrl) { + final File file = new File( fileUrl ); + try { + // best effort, since this is very well not allowed in EE environments + file.createNewFile(); + } + catch (Exception e) { + log.debug( "Exception calling File#createNewFile : " + e.toString() ); + } + try { + return new FileWriter( file ); + } + catch (IOException e) { + throw new PersistenceException( "Unable to open specified script target file for writing : " + fileUrl ); + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java new file mode 100644 index 0000000000..d36cb2d93b --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/schemagen/SqlScriptReader.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jpa.internal.schemagen; + +import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor; + +/** + * Contract for handling Reader/File differences + * + * @author Steve Ebersole + */ +public interface SqlScriptReader { + public Iterable read(ImportSqlCommandExtractor commandExtractor); + public void release(); +}