Java tutorial
/* * This file is part of Transitime.org * * Transitime.org is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License (GPL) as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * Transitime.org 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Transitime.org . If not, see <http://www.gnu.org/licenses/>. */ package org.transitime.applications; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.sql.Types; import java.util.ArrayList; import java.util.List; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.MySQLDialect; import org.hibernate.tool.hbm2ddl.SchemaExport; import com.google.common.reflect.ClassPath; /** * For generating SQL schema files based on classes to be stored in database * that were annotated for Hibernate. This is much nicer than trying to figure * out what the schema really should be by hand. This code was copied from * http:/ * /jandrewthompson.blogspot.nl/2009/10/how-to-generate-ddl-scripts-from.html * <p> * Note that unfortunately there does not appear to be a way to specify the * order of the columns in the resulting create table SQL statements. Though * this has been asked for, it appears to still be a limitation of Hibernate. * The default ordering appears to be first the @Id columns in reverse * alphabetical order, and then the non @Id columns in alphabetical order. Yes, * quite peculiar. * <p> * Since the resulting automatically generated files have unneeded drop commands * these are filtered out. This way the resulting sql is smaller and easier to * understand. * * @author john.thompson, Skibu Smith, and Sean Crudden * */ public class SchemaGenerator { private final Configuration cfg; private final String packageName; private final String outputDirectory; private static final Logger logger = LoggerFactory.getLogger(SchemaGenerator.class); /** * MySQL handles fractional seconds differently from PostGRES and other * DBs. Need to use "datetime(3)" for fractional seconds whereas with * PostGRES can use the default "timestamp" type. In order to handle * this properly in the generated ddl schema files need to not use * @Column(columnDefinition="datetime(3)") in the Java class that defines * the db object. Instead need to use this special ImprovedMySQLDialect * as the Dialect. */ public static class ImprovedMySQLDialect extends MySQLDialect { public ImprovedMySQLDialect() { super(); // Specify special SQL type for MySQL for timestamps so that get // fractions seconds. registerColumnType(Types.TIMESTAMP, "datetime(3)"); } } @SuppressWarnings("unchecked") public SchemaGenerator(String packageName, String outputDirectory) throws Exception { this.cfg = new Configuration(); this.cfg.setProperty("hibernate.hbm2ddl.auto", "create"); for (Class<Object> clazz : getClasses(packageName)) { this.cfg.addAnnotatedClass(clazz); } this.packageName = packageName; this.outputDirectory = outputDirectory; } /** * Gets rid of the unwanted drop table commands. These aren't needed because * the resulting script is intended only for creating a database, not for * deleting all the data and recreating the tables. * * @param outputFilename */ private void trimCruftFromFile(String outputFilename) { // Need to write to a temp file because if try to read and write // to same file things get quite confused. String tmpFileName = outputFilename + "_tmp"; BufferedReader reader = null; BufferedWriter writer = null; try { FileInputStream fis = new FileInputStream(outputFilename); reader = new BufferedReader(new InputStreamReader(fis)); FileOutputStream fos = new FileOutputStream(tmpFileName); writer = new BufferedWriter(new OutputStreamWriter(fos)); String line; while ((line = reader.readLine()) != null) { // Filter out "drop table" commands if (line.contains("drop table")) { // Read in following blank line line = reader.readLine(); // Continue to next line since filtering out drop table commands continue; } // Filter out "drop sequence" oracle commands if (line.contains("drop sequence")) { // Read in following blank line line = reader.readLine(); // Continue to next line since filtering out drop commands continue; } // Filter out the alter table commands where dropping a key or // a constraint if (line.contains("alter table")) { String nextLine = reader.readLine(); if (nextLine.contains("drop")) { // Need to continue reading until process a blank line while (reader.readLine().length() != 0) ; // Continue to next line since filtering out drop commands continue; } else { // It is an "alter table" command but not a "drop". // Therefore need to keep this command. Since read in // two lines need to handle this specially and then // continue writer.write(line); writer.write("\n"); writer.write(nextLine); writer.write("\n"); continue; } } // Line not being filtered so write it to the file writer.write(line); writer.write("\n"); } } catch (IOException e) { System.err.println("Could not trim cruft from file " + outputFilename + " . " + e.getMessage()); } finally { try { if (reader != null) reader.close(); if (writer != null) writer.close(); } catch (IOException e) { } } // Move the temp file to the original name try { Files.copy(new File(tmpFileName).toPath(), new File(outputFilename).toPath(), StandardCopyOption.REPLACE_EXISTING); Files.delete(new File(tmpFileName).toPath()); } catch (IOException e) { System.err.println("Could not rename file " + tmpFileName + " to " + outputFilename); } } /** * Method that actually creates the file. * * @param dbDialect to use */ private void generate(Dialect dialect) { cfg.setProperty("hibernate.dialect", dialect.getDialectClass()); SchemaExport export = new SchemaExport(cfg); export.setDelimiter(";"); // Determine file name. Use "ddl_" plus dialect name such as mysql or // oracle plus the package name with "_" replacing "." such as // org_transitime_db_structs . String packeNameSuffix = packageName.replace(".", "_"); String outputFilename = (outputDirectory != null ? outputDirectory + "/" : "") + "ddl_" + dialect.name().toLowerCase() + "_" + packeNameSuffix + ".sql"; export.setOutputFile(outputFilename); // Export, but only to an SQL file. Don't actually modify the database System.out.println("Writing file " + outputFilename); export.execute(true, false, false, false); // Get rid of unneeded SQL for dropping tables and keys and such trimCruftFromFile(outputFilename); } /** * Utility method used to fetch Class list based on a package name. * * @param packageName * (should be the package containing your annotated beans. */ @SuppressWarnings("rawtypes") private List<Class> getClasses(String packageName) throws Exception { logger.debug("Start: Classes in " + packageName); List<Class> classes = new ArrayList<Class>(); final ClassLoader loader = Thread.currentThread().getContextClassLoader(); for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) { if (info.getName().startsWith(packageName)) { final Class<?> clazz = info.load(); logger.debug(info.getName()); classes.add(clazz); } } logger.debug("End: Classes in " + packageName); return classes; } /** * Holds the class names of hibernate dialects for easy reference. */ private static enum Dialect { ORACLE("org.hibernate.dialect.Oracle10gDialect"), // Note that using special ImprovedMySqlDialect MYSQL("org.transitime.applications.SchemaGenerator$ImprovedMySQLDialect"), POSTGRES( "org.hibernate.dialect.PostgreSQLDialect"), HSQL("org.hibernate.dialect.HSQLDialect"); private String dialectClass; private Dialect(String dialectClass) { this.dialectClass = dialectClass; } public String getDialectClass() { return dialectClass; } } /** * Param args args[0] is the package name for the Hibernate annotated * classes whose schema is to be exported such as * "org.transitime.db.structs". args[1] is optional output directory where * the resulting files are to go. If the optional output directory is not * specified then schema files written to local directory. * <p> * The resulting files have the name "ddl_" plus dialect name such as mysql * or oracle plus the first two components of the package name such as * org_transitime. */ public static void main(String[] args) throws Exception { // Handle the command line options CommandLineParser parser = new BasicParser(); Options options = new Options(); Option hibernatePackagePathOption = new Option("p", "hibernatePackagePath", true, "This is the path to the package containing the " + "hibernate annotated java classes"); Option outputDirectoryOption = new Option("o", "outputDirectory", true, "This is the directory to output the sql"); hibernatePackagePathOption.setRequired(true); outputDirectoryOption.setRequired(true); options.addOption(outputDirectoryOption); options.addOption(hibernatePackagePathOption); try { CommandLine cmd = parser.parse(options, args); if (cmd.hasOption("p") && cmd.hasOption("o")) { String packageName = cmd.getOptionValue("p"); String outputDirectory = cmd.getOptionValue("o"); // Note: need to use separate SchemaGenerator objects for each // dialect because for some reason they otherwise interfere // with each other. SchemaGenerator gen = new SchemaGenerator(packageName, outputDirectory); gen.generate(Dialect.POSTGRES); gen = new SchemaGenerator(packageName, outputDirectory); gen.generate(Dialect.ORACLE); gen = new SchemaGenerator(packageName, outputDirectory); gen.generate(Dialect.MYSQL); } else { // Necessary command line options were not set HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("java -jar generateDatabaseSchema.jar", options); System.exit(-1); } } catch (ParseException pe) { logger.error(pe.getMessage()); HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("java -jar generateDatabaseSchema.jar", options); System.exit(-1); } } }