1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.tool.hbm2ddl;
26
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileWriter;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.Reader;
35 import java.io.Writer;
36 import java.sql.Connection;
37 import java.sql.SQLException;
38 import java.sql.SQLWarning;
39 import java.sql.Statement;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Properties;
43
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import org.hibernate.HibernateException;
48 import org.hibernate.JDBCException;
49 import org.hibernate.cfg.Configuration;
50 import org.hibernate.cfg.Environment;
51 import org.hibernate.cfg.NamingStrategy;
52 import org.hibernate.cfg.Settings;
53 import org.hibernate.dialect.Dialect;
54 import org.hibernate.jdbc.util.FormatStyle;
55 import org.hibernate.jdbc.util.Formatter;
56 import org.hibernate.jdbc.util.SQLStatementLogger;
57 import org.hibernate.util.ConfigHelper;
58 import org.hibernate.util.JDBCExceptionReporter;
59 import org.hibernate.util.PropertiesHelper;
60 import org.hibernate.util.ReflectHelper;
61
62 /**
63 * Commandline tool to export table schema to the database. This class may also be called from inside an application.
64 *
65 * @author Daniel Bradby
66 * @author Gavin King
67 */
68 public class SchemaExport {
69
70 private static final Logger log = LoggerFactory.getLogger( SchemaExport.class );
71
72 private ConnectionHelper connectionHelper;
73 private String[] dropSQL;
74 private String[] createSQL;
75 private String outputFile = null;
76 private String importFile = "/import.sql";
77 private Dialect dialect;
78 private String delimiter;
79 private final List exceptions = new ArrayList();
80 private boolean haltOnError = false;
81 private Formatter formatter;
82 private SQLStatementLogger sqlStatementLogger;
83
84 /**
85 * Create a schema exporter for the given Configuration
86 *
87 * @param cfg The configuration from which to build a schema export.
88 * @throws HibernateException Indicates problem preparing for schema export.
89 */
90 public SchemaExport(Configuration cfg) throws HibernateException {
91 this( cfg, cfg.getProperties() );
92 }
93
94 /**
95 * Create a schema exporter for the given Configuration and given settings
96 *
97 * @param cfg The configuration from which to build a schema export.
98 * @param settings The 'parsed' settings.
99 * @throws HibernateException Indicates problem preparing for schema export.
100 */
101 public SchemaExport(Configuration cfg, Settings settings) throws HibernateException {
102 dialect = settings.getDialect();
103 connectionHelper = new SuppliedConnectionProviderConnectionHelper( settings.getConnectionProvider() );
104 dropSQL = cfg.generateDropSchemaScript( dialect );
105 createSQL = cfg.generateSchemaCreationScript( dialect );
106 sqlStatementLogger = settings.getSqlStatementLogger();
107 formatter = ( sqlStatementLogger.isFormatSql() ? FormatStyle.DDL : FormatStyle.NONE ).getFormatter();
108 }
109
110 /**
111 * Create a schema exporter for the given Configuration, with the given
112 * database connection properties.
113 *
114 * @param cfg The configuration from which to build a schema export.
115 * @param properties The properties from which to configure connectivity etc.
116 * @throws HibernateException Indicates problem preparing for schema export.
117 *
118 * @deprecated properties may be specified via the Configuration object
119 */
120 public SchemaExport(Configuration cfg, Properties properties) throws HibernateException {
121 dialect = Dialect.getDialect( properties );
122
123 Properties props = new Properties();
124 props.putAll( dialect.getDefaultProperties() );
125 props.putAll( properties );
126
127 connectionHelper = new ManagedProviderConnectionHelper( props );
128 dropSQL = cfg.generateDropSchemaScript( dialect );
129 createSQL = cfg.generateSchemaCreationScript( dialect );
130
131 formatter = ( PropertiesHelper.getBoolean( Environment.FORMAT_SQL, props ) ? FormatStyle.DDL : FormatStyle.NONE ).getFormatter();
132 }
133
134 /**
135 * Create a schema exporter for the given Configuration, using the supplied connection for connectivity.
136 *
137 * @param cfg The configuration to use.
138 * @param connection The JDBC connection to use.
139 * @throws HibernateException Indicates problem preparing for schema export.
140 */
141 public SchemaExport(Configuration cfg, Connection connection) throws HibernateException {
142 this.connectionHelper = new SuppliedConnectionHelper( connection );
143 dialect = Dialect.getDialect( cfg.getProperties() );
144 dropSQL = cfg.generateDropSchemaScript( dialect );
145 createSQL = cfg.generateSchemaCreationScript( dialect );
146 formatter = ( PropertiesHelper.getBoolean( Environment.FORMAT_SQL, cfg.getProperties() ) ? FormatStyle.DDL : FormatStyle.NONE ).getFormatter();
147 }
148
149 /**
150 * For generating a export script file, this is the file which will be written.
151 *
152 * @param filename The name of the file to which to write the export script.
153 * @return this
154 */
155 public SchemaExport setOutputFile(String filename) {
156 outputFile = filename;
157 return this;
158 }
159
160 /**
161 * An import file, containing raw SQL statements to be executed.
162 *
163 * @param filename The import file name.
164 * @return this
165 */
166 public SchemaExport setImportFile(String filename) {
167 importFile = filename;
168 return this;
169 }
170
171 /**
172 * Set the end of statement delimiter
173 *
174 * @param delimiter The delimiter
175 * @return this
176 */
177 public SchemaExport setDelimiter(String delimiter) {
178 this.delimiter = delimiter;
179 return this;
180 }
181
182 /**
183 * Should we format the sql strings?
184 *
185 * @param format Should we format SQL strings
186 * @return this
187 */
188 public SchemaExport setFormat(boolean format) {
189 this.formatter = ( format ? FormatStyle.DDL : FormatStyle.NONE ).getFormatter();
190 return this;
191 }
192
193 /**
194 * Should we stop once an error occurs?
195 *
196 * @param haltOnError True if export should stop after error.
197 * @return this
198 */
199 public SchemaExport setHaltOnError(boolean haltOnError) {
200 this.haltOnError = haltOnError;
201 return this;
202 }
203
204 /**
205 * Run the schema creation script.
206 *
207 * @param script print the DDL to the console
208 * @param export export the script to the database
209 */
210 public void create(boolean script, boolean export) {
211 execute( script, export, false, false );
212 }
213
214 /**
215 * Run the drop schema script.
216 *
217 * @param script print the DDL to the console
218 * @param export export the script to the database
219 */
220 public void drop(boolean script, boolean export) {
221 execute( script, export, true, false );
222 }
223
224 public void execute(boolean script, boolean export, boolean justDrop, boolean justCreate) {
225
226 log.info( "Running hbm2ddl schema export" );
227
228 Connection connection = null;
229 Writer outputFileWriter = null;
230 Reader importFileReader = null;
231 Statement statement = null;
232
233 exceptions.clear();
234
235 try {
236
237 try {
238 InputStream stream = ConfigHelper.getResourceAsStream( importFile );
239 importFileReader = new InputStreamReader( stream );
240 }
241 catch ( HibernateException e ) {
242 log.debug( "import file not found: " + importFile );
243 }
244
245 if ( outputFile != null ) {
246 log.info( "writing generated schema to file: " + outputFile );
247 outputFileWriter = new FileWriter( outputFile );
248 }
249
250 if ( export ) {
251 log.info( "exporting generated schema to database" );
252 connectionHelper.prepare( true );
253 connection = connectionHelper.getConnection();
254 statement = connection.createStatement();
255 }
256
257 if ( !justCreate ) {
258 drop( script, export, outputFileWriter, statement );
259 }
260
261 if ( !justDrop ) {
262 create( script, export, outputFileWriter, statement );
263 if ( export && importFileReader != null ) {
264 importScript( importFileReader, statement );
265 }
266 }
267
268 log.info( "schema export complete" );
269
270 }
271
272 catch ( Exception e ) {
273 exceptions.add( e );
274 log.error( "schema export unsuccessful", e );
275 }
276
277 finally {
278
279 try {
280 if ( statement != null ) {
281 statement.close();
282 }
283 if ( connection != null ) {
284 connectionHelper.release();
285 }
286 }
287 catch ( Exception e ) {
288 exceptions.add( e );
289 log.error( "Could not close connection", e );
290 }
291
292 try {
293 if ( outputFileWriter != null ) {
294 outputFileWriter.close();
295 }
296 if ( importFileReader != null ) {
297 importFileReader.close();
298 }
299 }
300 catch ( IOException ioe ) {
301 exceptions.add( ioe );
302 log.error( "Error closing output file: " + outputFile, ioe );
303 }
304
305 }
306 }
307
308 private void importScript(Reader importFileReader, Statement statement)
309 throws IOException {
310 log.info( "Executing import script: " + importFile );
311 BufferedReader reader = new BufferedReader( importFileReader );
312 long lineNo = 0;
313 for ( String sql = reader.readLine(); sql != null; sql = reader.readLine() ) {
314 try {
315 lineNo++;
316 String trimmedSql = sql.trim();
317 if ( trimmedSql.length() == 0 ||
318 trimmedSql.startsWith( "--" ) ||
319 trimmedSql.startsWith( "//" ) ||
320 trimmedSql.startsWith( "/*" ) ) {
321 continue;
322 }
323 else {
324 if ( trimmedSql.endsWith( ";" ) ) {
325 trimmedSql = trimmedSql.substring( 0, trimmedSql.length() - 1 );
326 }
327 log.debug( trimmedSql );
328 statement.execute( trimmedSql );
329 }
330 }
331 catch ( SQLException e ) {
332 throw new JDBCException( "Error during import script execution at line " + lineNo, e );
333 }
334 }
335 }
336
337 private void create(boolean script, boolean export, Writer fileOutput, Statement statement)
338 throws IOException {
339 for ( int j = 0; j < createSQL.length; j++ ) {
340 try {
341 execute( script, export, fileOutput, statement, createSQL[j] );
342 }
343 catch ( SQLException e ) {
344 if ( haltOnError ) {
345 throw new JDBCException( "Error during DDL export", e );
346 }
347 exceptions.add( e );
348 log.error( "Unsuccessful: " + createSQL[j] );
349 log.error( e.getMessage() );
350 }
351 }
352 }
353
354 private void drop(boolean script, boolean export, Writer fileOutput, Statement statement)
355 throws IOException {
356 for ( int i = 0; i < dropSQL.length; i++ ) {
357 try {
358 execute( script, export, fileOutput, statement, dropSQL[i] );
359 }
360 catch ( SQLException e ) {
361 exceptions.add( e );
362 log.debug( "Unsuccessful: " + dropSQL[i] );
363 log.debug( e.getMessage() );
364 }
365 }
366 }
367
368 private void execute(boolean script, boolean export, Writer fileOutput, Statement statement, final String sql)
369 throws IOException, SQLException {
370 String formatted = formatter.format( sql );
371 if ( delimiter != null ) {
372 formatted += delimiter;
373 }
374 if ( script ) {
375 System.out.println( formatted );
376 }
377 log.debug( formatted );
378 if ( outputFile != null ) {
379 fileOutput.write( formatted + "\n" );
380 }
381 if ( export ) {
382
383 statement.executeUpdate( sql );
384 try {
385 SQLWarning warnings = statement.getWarnings();
386 if ( warnings != null) {
387 JDBCExceptionReporter.logAndClearWarnings( connectionHelper.getConnection() );
388 }
389 }
390 catch( SQLException sqle ) {
391 log.warn( "unable to log SQLWarnings : " + sqle );
392 }
393 }
394
395
396 }
397
398 public static void main(String[] args) {
399 try {
400 Configuration cfg = new Configuration();
401
402 boolean script = true;
403 boolean drop = false;
404 boolean create = false;
405 boolean halt = false;
406 boolean export = true;
407 String outFile = null;
408 String importFile = "/import.sql";
409 String propFile = null;
410 boolean format = false;
411 String delim = null;
412
413 for ( int i = 0; i < args.length; i++ ) {
414 if ( args[i].startsWith( "--" ) ) {
415 if ( args[i].equals( "--quiet" ) ) {
416 script = false;
417 }
418 else if ( args[i].equals( "--drop" ) ) {
419 drop = true;
420 }
421 else if ( args[i].equals( "--create" ) ) {
422 create = true;
423 }
424 else if ( args[i].equals( "--haltonerror" ) ) {
425 halt = true;
426 }
427 else if ( args[i].equals( "--text" ) ) {
428 export = false;
429 }
430 else if ( args[i].startsWith( "--output=" ) ) {
431 outFile = args[i].substring( 9 );
432 }
433 else if ( args[i].startsWith( "--import=" ) ) {
434 importFile = args[i].substring( 9 );
435 }
436 else if ( args[i].startsWith( "--properties=" ) ) {
437 propFile = args[i].substring( 13 );
438 }
439 else if ( args[i].equals( "--format" ) ) {
440 format = true;
441 }
442 else if ( args[i].startsWith( "--delimiter=" ) ) {
443 delim = args[i].substring( 12 );
444 }
445 else if ( args[i].startsWith( "--config=" ) ) {
446 cfg.configure( args[i].substring( 9 ) );
447 }
448 else if ( args[i].startsWith( "--naming=" ) ) {
449 cfg.setNamingStrategy(
450 ( NamingStrategy ) ReflectHelper.classForName( args[i].substring( 9 ) )
451 .newInstance()
452 );
453 }
454 }
455 else {
456 String filename = args[i];
457 if ( filename.endsWith( ".jar" ) ) {
458 cfg.addJar( new File( filename ) );
459 }
460 else {
461 cfg.addFile( filename );
462 }
463 }
464
465 }
466
467 if ( propFile != null ) {
468 Properties props = new Properties();
469 props.putAll( cfg.getProperties() );
470 props.load( new FileInputStream( propFile ) );
471 cfg.setProperties( props );
472 }
473
474 SchemaExport se = new SchemaExport( cfg )
475 .setHaltOnError( halt )
476 .setOutputFile( outFile )
477 .setImportFile( importFile )
478 .setDelimiter( delim );
479 if ( format ) {
480 se.setFormat( true );
481 }
482 se.execute( script, export, drop, create );
483
484 }
485 catch ( Exception e ) {
486 log.error( "Error creating schema ", e );
487 e.printStackTrace();
488 }
489 }
490
491 /**
492 * Returns a List of all Exceptions which occured during the export.
493 *
494 * @return A List containig the Exceptions occured during the export
495 */
496 public List getExceptions() {
497 return exceptions;
498 }
499 }