/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.tool.hbm2ddl; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataSources; 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 org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.service.ServiceRegistry; import org.hibernate.tool.schema.SourceType; import org.hibernate.tool.schema.TargetType; import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl; import org.hibernate.tool.schema.internal.ExceptionHandlerHaltImpl; import org.hibernate.tool.schema.internal.Helper; import org.hibernate.tool.schema.internal.SchemaCreatorImpl; import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.SchemaManagementException; import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; import org.hibernate.tool.schema.spi.ScriptSourceInput; import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.SourceDescriptor; import org.hibernate.tool.schema.spi.TargetDescriptor; /** * Command-line tool for exporting (create and/or drop) a database schema. The export can * be sent directly to the database, written to script or both. * * @author Daniel Bradby * @author Gavin King * @author Steve Ebersole */ public class SchemaExport { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SchemaExport.class ); public static enum Type { CREATE( Action.CREATE ), DROP( Action.DROP ), NONE( Action.NONE ), BOTH( Action.BOTH ); private final Action actionReplacement; Type(Action actionReplacement) { this.actionReplacement = actionReplacement; } public boolean doCreate() { return actionReplacement.doCreate(); } public boolean doDrop() { return actionReplacement.doDrop(); } } public static enum Action { /** * None - duh :P */ NONE, /** * Create only */ CREATE, /** * Drop only */ DROP, /** * Drop and then create */ BOTH; public boolean doCreate() { return this == BOTH || this == CREATE; } public boolean doDrop() { return this == BOTH || this == DROP; } private static Action interpret(boolean justDrop, boolean justCreate) { if ( justDrop ) { return Action.DROP; } else if ( justCreate ) { return Action.CREATE; } else { return Action.BOTH; } } public static Action parseCommandLineOption(String actionText) { if ( actionText.equalsIgnoreCase( "create" ) ) { return CREATE; } else if ( actionText.equalsIgnoreCase( "drop" ) ) { return DROP; } else if ( actionText.equalsIgnoreCase( "drop-and-create" ) ) { return BOTH; } else { return NONE; } } } boolean haltOnError = false; boolean format = false; boolean manageNamespaces = false; String delimiter = null; String outputFile = null; private String importFiles; private final List<Exception> exceptions = new ArrayList<Exception>(); /** * For generating a export script file, this is the file which will be written. * * @param filename The name of the file to which to write the export script. * * @return this */ public SchemaExport setOutputFile(String filename) { outputFile = filename; return this; } /** * Comma-separated list of resource names to use for database init commands on create. * * @param importFiles The comma-separated list of init file resources names * * @return this */ public SchemaExport setImportFiles(String importFiles) { this.importFiles = importFiles; return this; } /** * Set the end of statement delimiter * * @param delimiter The delimiter * * @return this */ public SchemaExport setDelimiter(String delimiter) { this.delimiter = delimiter; return this; } /** * Should we format the sql strings? * * @param format Should we format SQL strings * * @return this */ public SchemaExport setFormat(boolean format) { this.format = format; return this; } /** * Should we stop once an error occurs? * * @param haltOnError True if export should stop after error. * * @return this */ public SchemaExport setHaltOnError(boolean haltOnError) { this.haltOnError = haltOnError; return this; } public SchemaExport setManageNamespaces(boolean manageNamespaces) { this.manageNamespaces = manageNamespaces; return this; } public void drop(EnumSet<TargetType> targetTypes, Metadata metadata) { execute( targetTypes, Action.DROP, metadata ); } public void create(EnumSet<TargetType> targetTypes, Metadata metadata) { execute( targetTypes, Action.BOTH, metadata ); } public void createOnly(EnumSet<TargetType> targetTypes, Metadata metadata) { execute( targetTypes, Action.CREATE, metadata ); } public void execute(EnumSet<TargetType> targetTypes, Action action, Metadata metadata) { execute( targetTypes, action, metadata, ( (MetadataImplementor) metadata ).getMetadataBuildingOptions().getServiceRegistry() ); } @SuppressWarnings("unchecked") public void execute(EnumSet<TargetType> targetTypes, Action action, Metadata metadata, ServiceRegistry serviceRegistry) { if ( action == Action.NONE ) { LOG.debug( "Skipping SchemaExport as Action.NONE was passed" ); return; } if ( targetTypes.isEmpty() ) { LOG.debug( "Skipping SchemaExport as no targets were specified" ); return; } exceptions.clear(); LOG.runningHbm2ddlSchemaExport(); final TargetDescriptor targetDescriptor = buildTargetDescriptor( targetTypes, outputFile, serviceRegistry ); doExecution( action, needsJdbcConnection( targetTypes ), metadata, serviceRegistry, targetDescriptor ); } public void doExecution( Action action, boolean needsJdbc, Metadata metadata, ServiceRegistry serviceRegistry, TargetDescriptor targetDescriptor) { Map config = new HashMap(); config.putAll( serviceRegistry.getService( ConfigurationService.class ).getSettings() ); config.put( AvailableSettings.HBM2DDL_DELIMITER, delimiter ); config.put( AvailableSettings.FORMAT_SQL, format ); config.put( AvailableSettings.HBM2DDL_IMPORT_FILES, importFiles ); final SchemaManagementTool tool = serviceRegistry.getService( SchemaManagementTool.class ); final ExceptionHandler exceptionHandler = haltOnError ? ExceptionHandlerHaltImpl.INSTANCE : new ExceptionHandlerCollectingImpl(); final ExecutionOptions executionOptions = SchemaManagementToolCoordinator.buildExecutionOptions( config, exceptionHandler ); final SourceDescriptor sourceDescriptor = new SourceDescriptor() { @Override public SourceType getSourceType() { return SourceType.METADATA; } @Override public ScriptSourceInput getScriptSourceInput() { return null; } }; try { if ( action.doDrop() ) { tool.getSchemaDropper( config ).doDrop( metadata, executionOptions, sourceDescriptor, targetDescriptor ); } if ( action.doCreate() ) { tool.getSchemaCreator( config ).doCreation( metadata, executionOptions, sourceDescriptor, targetDescriptor ); } } finally { if ( exceptionHandler instanceof ExceptionHandlerCollectingImpl ) { exceptions.addAll( ( (ExceptionHandlerCollectingImpl) exceptionHandler ).getExceptions() ); } } } private boolean needsJdbcConnection(EnumSet<TargetType> targetTypes) { return targetTypes.contains( TargetType.DATABASE ); } public static TargetDescriptor buildTargetDescriptor( EnumSet<TargetType> targetTypes, String outputFile, ServiceRegistry serviceRegistry) { final ScriptTargetOutput scriptTarget; if ( targetTypes.contains( TargetType.SCRIPT ) ) { if ( outputFile == null ) { throw new SchemaManagementException( "Writing to script was requested, but no script file was specified" ); } scriptTarget = Helper.interpretScriptTargetSetting( outputFile, serviceRegistry.getService( ClassLoaderService.class ), (String) serviceRegistry.getService( ConfigurationService.class ).getSettings().get( AvailableSettings.HBM2DDL_CHARSET_NAME ) ); } else { scriptTarget = null; } return new TargetDescriptorImpl( targetTypes, scriptTarget ); } /** * For testing use */ public void perform(Action action, Metadata metadata, ScriptTargetOutput target) { doExecution( action, false, metadata, ( (MetadataImplementor) metadata ).getMetadataBuildingOptions().getServiceRegistry(), new TargetDescriptorImpl( EnumSet.of( TargetType.SCRIPT ), target ) ); } public static void main(String[] args) { try { final CommandLineArgs commandLineArgs = CommandLineArgs.parseCommandLineArgs( args ); execute( commandLineArgs ); } catch (Exception e) { LOG.unableToCreateSchema( e ); e.printStackTrace(); } } public static void execute(CommandLineArgs commandLineArgs) throws Exception { StandardServiceRegistry serviceRegistry = buildStandardServiceRegistry( commandLineArgs ); try { final MetadataImplementor metadata = buildMetadata( commandLineArgs, serviceRegistry ); new SchemaExport() .setHaltOnError( commandLineArgs.halt ) .setOutputFile( commandLineArgs.outputFile ) .setDelimiter( commandLineArgs.delimiter ) .setFormat( commandLineArgs.format ) .setManageNamespaces( commandLineArgs.manageNamespaces ) .setImportFiles( commandLineArgs.importFile ) .execute( commandLineArgs.targetTypes, commandLineArgs.action, metadata, serviceRegistry ); } finally { StandardServiceRegistryBuilder.destroy( serviceRegistry ); } } private static StandardServiceRegistry buildStandardServiceRegistry(CommandLineArgs commandLineArgs) throws Exception { final BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build(); final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder( bsr ); if ( commandLineArgs.cfgXmlFile != null ) { ssrBuilder.configure( commandLineArgs.cfgXmlFile ); } Properties properties = new Properties(); if ( commandLineArgs.propertiesFile != null ) { properties.load( new FileInputStream( commandLineArgs.propertiesFile ) ); } ssrBuilder.applySettings( properties ); return ssrBuilder.build(); } private static MetadataImplementor buildMetadata( CommandLineArgs parsedArgs, StandardServiceRegistry serviceRegistry) throws Exception { final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); for ( String filename : parsedArgs.hbmXmlFiles ) { metadataSources.addFile( filename ); } for ( String filename : parsedArgs.jarFiles ) { metadataSources.addJar( new File( filename ) ); } final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder(); final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class ); if ( parsedArgs.implicitNamingStrategyImplName != null ) { metadataBuilder.applyImplicitNamingStrategy( strategySelector.resolveStrategy( ImplicitNamingStrategy.class, parsedArgs.implicitNamingStrategyImplName ) ); } if ( parsedArgs.physicalNamingStrategyImplName != null ) { metadataBuilder.applyPhysicalNamingStrategy( strategySelector.resolveStrategy( PhysicalNamingStrategy.class, parsedArgs.physicalNamingStrategyImplName ) ); } return (MetadataImplementor) metadataBuilder.build(); } /** * Intended for test usage only. Builds a Metadata using the same algorithm as * {@link #main} * * @param args The "command line args" * * @return The built Metadata * * @throws Exception Problems building the Metadata */ public static MetadataImplementor buildMetadataFromMainArgs(String[] args) throws Exception { final CommandLineArgs commandLineArgs = CommandLineArgs.parseCommandLineArgs( args ); StandardServiceRegistry serviceRegistry = buildStandardServiceRegistry( commandLineArgs ); try { return buildMetadata( commandLineArgs, serviceRegistry ); } finally { StandardServiceRegistryBuilder.destroy( serviceRegistry ); } } /** * Returns a List of all Exceptions which occurred during the export. * * @return A List containing the Exceptions occurred during the export */ public List getExceptions() { return exceptions; } private static class CommandLineArgs { EnumSet<TargetType> targetTypes; Action action; boolean halt = false; boolean format = false; boolean manageNamespaces = false; String delimiter = null; String outputFile = null; String importFile = SchemaCreatorImpl.DEFAULT_IMPORT_FILE; String propertiesFile = null; String cfgXmlFile = null; String implicitNamingStrategyImplName = null; String physicalNamingStrategyImplName = null; List<String> hbmXmlFiles = new ArrayList<String>(); List<String> jarFiles = new ArrayList<String>(); public static CommandLineArgs parseCommandLineArgs(String[] args) { String targetText = null; boolean script = true; boolean export = true; String actionText = null; boolean drop = false; boolean create = false; CommandLineArgs parsedArgs = new CommandLineArgs(); for ( String arg : args ) { if ( arg.startsWith( "--" ) ) { if ( arg.equals( "--quiet" ) ) { script = false; } else if ( arg.equals( "--text" ) ) { export = false; } else if ( arg.equals( "--drop" ) ) { drop = true; } else if ( arg.equals( "--create" ) ) { create = true; } else if ( arg.startsWith( "--action=" ) ) { actionText = arg.substring( 9 ); } else if ( arg.startsWith( "--target=" ) ) { targetText = arg.substring( 9 ); } else if ( arg.equals( "--schemas" ) ) { parsedArgs.manageNamespaces = true; } else if ( arg.equals( "--haltonerror" ) ) { parsedArgs.halt = true; } else if ( arg.startsWith( "--output=" ) ) { parsedArgs.outputFile = arg.substring( 9 ); } else if ( arg.startsWith( "--import=" ) ) { parsedArgs.importFile = arg.substring( 9 ); } else if ( arg.startsWith( "--properties=" ) ) { parsedArgs.propertiesFile = arg.substring( 13 ); } else if ( arg.equals( "--format" ) ) { parsedArgs.format = true; } else if ( arg.startsWith( "--delimiter=" ) ) { parsedArgs.delimiter = arg.substring( 12 ); } else if ( arg.startsWith( "--config=" ) ) { parsedArgs.cfgXmlFile = arg.substring( 9 ); } else if ( arg.startsWith( "--naming=" ) ) { DeprecationLogger.DEPRECATION_LOGGER.logDeprecatedNamingStrategyArgument(); } else if ( arg.startsWith( "--implicit-naming=" ) ) { parsedArgs.implicitNamingStrategyImplName = arg.substring( 18 ); } else if ( arg.startsWith( "--physical-naming=" ) ) { parsedArgs.physicalNamingStrategyImplName = arg.substring( 18 ); } } else { if ( arg.endsWith( ".jar" ) ) { parsedArgs.jarFiles.add( arg ); } else { parsedArgs.hbmXmlFiles.add( arg ); } } } if ( actionText == null ) { parsedArgs.action = Action.interpret( drop, create ); } else { if ( drop || create ) { LOG.warn( "--drop or --create was used; prefer --action=none|create|drop|drop-and-create instead" ); } parsedArgs.action = Action.parseCommandLineOption( actionText ); } if ( targetText == null ) { parsedArgs.targetTypes = TargetTypeHelper.parseLegacyCommandLineOptions( script, export, parsedArgs.outputFile ); } else { if ( !script || !export ) { LOG.warn( "--text or --quiet was used; prefer --target=none|(stdout|database|script)*" ); } parsedArgs.targetTypes = TargetTypeHelper.parseCommandLineOptions( targetText ); } return parsedArgs; } } private static class TargetDescriptorImpl implements TargetDescriptor { private final EnumSet<TargetType> targetTypes; private final ScriptTargetOutput scriptTarget; public TargetDescriptorImpl( EnumSet<TargetType> targetTypes, ScriptTargetOutput scriptTarget) { this.targetTypes = targetTypes; this.scriptTarget = scriptTarget; } @Override public EnumSet<TargetType> getTargetTypes() { return targetTypes; } @Override public ScriptTargetOutput getScriptTargetOutput() { return scriptTarget; } } }