Java tutorial
/* * Copyright (C) 2012-2013 CloudJee, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wavemaker.tools.data; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.Dialect; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.springframework.util.ReflectionUtils; import com.wavemaker.common.MessageResource; import com.wavemaker.common.util.CastUtils; import com.wavemaker.common.util.IOUtils; import com.wavemaker.common.util.ObjectUtils; import com.wavemaker.common.util.Tuple; import com.wavemaker.runtime.WMAppContext; import com.wavemaker.runtime.data.DataServiceRuntimeException; import com.wavemaker.runtime.data.util.JDBCUtils; import com.wavemaker.tools.common.ConfigurationException; import com.wavemaker.tools.data.parser.HbmParser; import com.wavemaker.tools.io.Folder; import com.wavemaker.tools.io.Resource; import com.wavemaker.tools.io.ResourceOperation; import com.wavemaker.tools.io.local.LocalFolder; import com.wavemaker.tools.util.ResourceClassLoaderUtils; /** * @author Simon Toens */ public class ExportDB extends BaseDataModelSetup { private static final Log log = LogFactory.getLog(BaseDataModelSetup.class); private static final String HBM_FILES_DIR_SYSTEM_PROPERTY = SYSTEM_PROPERTY_PREFIX + "hbmFilesDir"; private static final String NO_SUITABLE_DRIVER = "No suitable driver found"; private static final String UNKNOWN_DATABASE = "Unknown database"; private Folder hbmFilesDir = null; private boolean exportToDatabase = false; private List<Throwable> errors = null; private boolean verbose = false; private boolean overrideTable = false; private String ddl = null; private Folder classesDir = null; public void setClassesDir(Folder classesDir) { this.classesDir = classesDir; } public void setVerbose(boolean verbose) { this.verbose = verbose; } public void setOverrideTable(boolean val) { this.overrideTable = val; } public List<Throwable> getErrors() { return this.errors; } public void setHbmFilesDir(Folder hbmFilesDir) { this.hbmFilesDir = hbmFilesDir; } public void addProperties(Properties properties) { this.properties.putAll(properties); } public void setExportToDB(boolean exportToDatabase) { this.exportToDatabase = exportToDatabase; } public String getDDL() { return this.ddl; } @Override protected void customRun() { init(); final Configuration cfg = new Configuration(); // cfg.addDirectory(this.hbmFilesDir); this.hbmFilesDir.find().files().performOperation(new ResourceOperation<com.wavemaker.tools.io.File>() { @Override public void perform(com.wavemaker.tools.io.File file) { if (file.getName().endsWith(".hbm.xml")) { cfg.addInputStream(file.getContent().asInputStream()); } } }); Properties connectionProperties = getHibernateConnectionProperties(); cfg.addProperties(connectionProperties); SchemaExport export = null; SchemaUpdate update = null; File ddlFile = null; try { if (this.overrideTable) { Callable<SchemaExport> t = new Callable<SchemaExport>() { @Override public SchemaExport call() { return new SchemaExport(cfg); } }; if (this.classesDir == null) { try { export = t.call(); } catch (Exception e) { ReflectionUtils.rethrowRuntimeException(e); } } else { export = ResourceClassLoaderUtils.runInClassLoaderContext(true, t, this.classesDir); } ddlFile = File.createTempFile("ddl", ".sql"); ddlFile.deleteOnExit(); export.setOutputFile(ddlFile.getAbsolutePath()); export.setDelimiter(";"); export.setFormat(true); String extraddl = prepareForExport(this.exportToDatabase); export.create(this.verbose, this.exportToDatabase); this.errors = CastUtils.cast(export.getExceptions()); this.errors = filterError(this.errors, connectionProperties); this.ddl = IOUtils.read(ddlFile); if (!ObjectUtils.isNullOrEmpty(extraddl)) { this.ddl = extraddl + "\n" + this.ddl; } } else { Callable<SchemaUpdate> t = new Callable<SchemaUpdate>() { @Override public SchemaUpdate call() { return new SchemaUpdate(cfg); } }; if (this.classesDir == null) { try { update = t.call(); } catch (Exception e) { ReflectionUtils.rethrowRuntimeException(e); } } else { update = ResourceClassLoaderUtils.runInClassLoaderContext(t, this.classesDir); } prepareForExport(this.exportToDatabase); Connection conn = JDBCUtils.getConnection(this.connectionUrl.toString(), this.username, this.password, this.driverClassName); Dialect dialect = Dialect.getDialect(connectionProperties); DatabaseMetadata meta = new DatabaseMetadata(conn, dialect); String[] updateSQL = cfg.generateSchemaUpdateScript(dialect, meta); update.execute(this.verbose, this.exportToDatabase); this.errors = CastUtils.cast(update.getExceptions()); StringBuilder sb = new StringBuilder(); for (String line : updateSQL) { sb = sb.append(line); sb = sb.append("\n"); } this.ddl = sb.toString(); } } catch (IOException ex) { throw new DataServiceRuntimeException(ex); } catch (SQLException qex) { throw new DataServiceRuntimeException(qex); } catch (RuntimeException rex) { if (rex.getCause() != null && rex.getCause().getMessage().contains(NO_SUITABLE_DRIVER) && WMAppContext.getInstance().isCloudFoundry()) { String msg = rex.getMessage() + " - " + UNKNOWN_DATABASE; throw new DataServiceRuntimeException(msg); } else { throw new DataServiceRuntimeException(rex); } } finally { try { ddlFile.delete(); } catch (Exception ignore) { } } } // The DDL Hibernate creates includes "alter table" statement on the top, which causes an error message (Table not // found) // to be displayed after the DB is exported properly. Let's hide the error message because it is too misleading. // This is not a perfect solution but we have no other way round until Hibernate fixes this problem. private List<Throwable> filterError(List<Throwable> errors, Properties props) { List<Throwable> rtn = new ArrayList<Throwable>(); String dialect = (String) props.get(".dialect"); String dbType; if (dialect == null) { return rtn; } if (dialect.contains("MySQL")) { dbType = "mysql"; } else if (dialect.contains("HSQL")) { dbType = "hsql"; } else { return rtn; } for (Throwable t : errors) { String msg = t.getMessage(); if (dbType.equals("mysql") && msg.substring(0, 5).equals("Table") && msg.lastIndexOf("doesn't exist") == msg.length() - 13 || dbType.equals("hsql") && msg.substring(0, 16).equals("Table not found:") && msg.contains("in statement [alter table")) { continue; } else { rtn.add(t); } } return rtn; } @Override protected boolean customInit(Collection<String> requiredProperties) { checkHbmFilesDir(requiredProperties); checkRevengNamingStrategy(); return initConnection(requiredProperties, true); } @Override protected void customDispose() { } private String prepareForExport(boolean run) { if (!isMySQL() && !isPostgres()) { return null; } // For export to MySQL or Postgres, we attempt to create the // database. For Postgres, we should also create the schema. // And we should expand this logic to include other supported // database types. Tuple.Two<String, String> t = getMappedSchemaAndCatalog(); String schemaName = t.v1, catalogName = t.v2; String connectionUrl = this.connectionUrl.toString(); String url; String urlDBName; if (WMAppContext.getInstance().isCloudFoundry()) { url = connectionUrl; catalogName = this.dbName; } else { urlDBName = getDatabaseNameFromConnectionUrl(); url = connectionUrl; if (isMySQL() && !ObjectUtils.isNullOrEmpty(schemaName)) { throw new ConfigurationException(MessageResource.UNSET_SCHEMA, "MySQL"); } if (!ObjectUtils.isNullOrEmpty(urlDBName)) { url = connectionUrl.substring(0, connectionUrl.indexOf(urlDBName)); if (isPostgres()) { url += "postgres"; } if (ObjectUtils.isNullOrEmpty(catalogName)) { catalogName = urlDBName; } else if (!ObjectUtils.isNullOrEmpty(catalogName) && !catalogName.equals(urlDBName)) { throw new ConfigurationException(MessageResource.MISMATCH_CATALOG_DBNAME, urlDBName, catalogName); } } else if (ObjectUtils.isNullOrEmpty(catalogName)) { throw new ConfigurationException(MessageResource.CATALOG_SHOULD_BE_SET); } } String ddl; if (isMySQL()) { ddl = "create database if not exists "; } else { ddl = "create database "; } ddl += catalogName; try { if (run) { runDDL(ddl, url); } } catch (RuntimeException ex) { log.warn("Unable to create database " + catalogName + " - it's possible it already exists.", ex); } return ddl; } private void checkHbmFilesDir(Collection<String> requiredProperties) { //It is assumed that, on CF, no properties are set to provide default directory for hbm files. if (!WMAppContext.getInstance().isCloudFoundry()) { if (this.hbmFilesDir == null) { String s = this.properties.getProperty(HBM_FILES_DIR_SYSTEM_PROPERTY); if (s != null) { setHbmFilesDir(new LocalFolder(new File(s))); } requiredProperties.add(HBM_FILES_DIR_SYSTEM_PROPERTY); } } } private String getDatabaseNameFromConnectionUrl() { return JDBCUtils.getMySQLDatabaseName(this.connectionUrl.toString()); } private Tuple.Two<String, String> getMappedSchemaAndCatalog() { for (Resource s : this.hbmFilesDir.list()) { // File f = new File(this.hbmFilesDir, s); if (s instanceof com.wavemaker.tools.io.File) { com.wavemaker.tools.io.File f = (com.wavemaker.tools.io.File) s; HbmParser p = null; try { Reader reader = new InputStreamReader(f.getContent().asInputStream()); p = new HbmParser(reader); // rely on the fact that all mapping files have the same // schema and catalog return Tuple.tuple(p.getEntity().getSchemaName(), p.getEntity().getCatalogName()); } catch (RuntimeException ignore) { } finally { try { p.close(); } catch (RuntimeException ignore) { } } } } return null; } }