| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.openjpa.jdbc.schema; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.security.AccessController; |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.sql.DataSource; |
| |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; |
| import org.apache.openjpa.jdbc.identifier.DBIdentifier; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.SQLExceptions; |
| import org.apache.openjpa.lib.conf.Configurations; |
| import org.apache.openjpa.lib.jdbc.DelegatingDataSource; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.meta.MetaDataSerializer; |
| import org.apache.openjpa.lib.util.Files; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.Options; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.util.InvalidStateException; |
| |
| /** |
| * The SchemaTool is used to manage the database schema. Note that the |
| * tool never adds or drops unique constraints from existing tables, because |
| * JDBC {@link DatabaseMetaData} does not include information on these |
| * constraints. |
| * |
| * @author Abe White |
| * @author Patrick Linskey |
| */ |
| public class SchemaTool { |
| |
| public static final String ACTION_ADD = "add"; |
| public static final String ACTION_DROP = "drop"; |
| public static final String ACTION_DROP_SCHEMA = "dropSchema"; |
| public static final String ACTION_RETAIN = "retain"; |
| public static final String ACTION_REFRESH = "refresh"; |
| public static final String ACTION_BUILD = "build"; |
| public static final String ACTION_REFLECT = "reflect"; |
| public static final String ACTION_CREATEDB = "createDB"; |
| public static final String ACTION_DROPDB = "dropDB"; |
| public static final String ACTION_IMPORT = "import"; |
| public static final String ACTION_EXPORT = "export"; |
| public static final String ACTION_DELETE_TABLE_CONTENTS = "deleteTableContents"; |
| public static final String ACTION_EXECUTE_SCRIPT = "executeScript"; |
| |
| public static final String[] ACTIONS = new String[]{ |
| ACTION_ADD, |
| ACTION_DROP, |
| ACTION_DROP_SCHEMA, |
| ACTION_RETAIN, |
| ACTION_REFRESH, |
| ACTION_BUILD, |
| ACTION_REFLECT, |
| ACTION_CREATEDB, |
| ACTION_DROPDB, |
| ACTION_IMPORT, |
| ACTION_EXPORT, |
| ACTION_DELETE_TABLE_CONTENTS, |
| ACTION_EXECUTE_SCRIPT |
| }; |
| |
| protected static final Localizer _loc = Localizer.forPackage(SchemaTool.class); |
| |
| protected final JDBCConfiguration _conf; |
| protected final DataSource _ds; |
| protected final Log _log; |
| protected final DBDictionary _dict; |
| private final String _action; |
| private boolean _ignoreErrs = false; |
| private boolean _openjpaTables = false; |
| private boolean _dropTables = true; |
| private boolean _dropSeqs = true; |
| private boolean _pks = true; |
| private boolean _fks = true; |
| private boolean _indexes = true; |
| private boolean _seqs = true; |
| private boolean _rollbackBeforeDDL = true; |
| private PrintWriter _writer = null; |
| private SchemaGroup _group = null; |
| private SchemaGroup _db = null; |
| protected boolean _fullDB = false; |
| protected String _sqlTerminator = ";"; |
| protected String _scriptToExecute = null; |
| |
| /** |
| * Default constructor. Tools constructed this way will not have an |
| * action, so the {@link #run()} method will be a no-op. |
| */ |
| public SchemaTool(JDBCConfiguration conf) { |
| this(conf, null); |
| } |
| |
| /** |
| * Construct a tool to perform the given action. |
| */ |
| public SchemaTool(JDBCConfiguration conf, String action) { |
| if (action != null && !Arrays.asList(ACTIONS).contains(action)) { |
| Configurations.configureInstance(this, conf, action, action); |
| } |
| |
| _conf = conf; |
| _action = action; |
| _ds = ACTION_BUILD.equals(action) ? null : conf.getDataSource2(null); |
| _log = conf.getLog(JDBCConfiguration.LOG_SCHEMA); |
| |
| // initialize this up-front; otherwise the dbdictionaryfactory might |
| // try to take a connection to initialize when we've already got one: |
| // bad news if the max pool is 1 |
| _dict = _conf.getDBDictionaryInstance(); |
| } |
| |
| /** |
| * Cleanup DataSource after run()/record() |
| */ |
| public void clear() { |
| if (_ds != null && _ds instanceof DelegatingDataSource) { |
| try { |
| ((DelegatingDataSource)_ds).close(); |
| } catch (Exception e) { |
| // no-op |
| } |
| } |
| } |
| |
| /** |
| * The action supplied on construction. |
| */ |
| public String getAction() { |
| return _action; |
| } |
| |
| /** |
| * If true, SQLExceptions thrown during schema manipulation will be |
| * printed but ignored. |
| */ |
| public boolean getIgnoreErrors() { |
| return _ignoreErrs; |
| } |
| |
| /** |
| * If true, SQLExceptions thrown during schema manipulation will be |
| * printed but ignored. |
| */ |
| public void setIgnoreErrors(boolean ignoreErrs) { |
| _ignoreErrs = ignoreErrs; |
| } |
| |
| /** |
| * Whether to act on special tables used by OpenJPA components |
| * for bookkeeping. |
| */ |
| public boolean getOpenJPATables() { |
| return _openjpaTables; |
| } |
| |
| /** |
| * Whether to act on special tables used by OpenJPA components |
| * for bookkeeping. |
| */ |
| public void setOpenJPATables(boolean openjpaTables) { |
| _openjpaTables = openjpaTables; |
| } |
| |
| /** |
| * If true, tables that appear to be unused will be dropped. Defaults to |
| * true. |
| */ |
| public boolean getDropTables() { |
| return _dropTables; |
| } |
| |
| /** |
| * If true, tables that appear to be unused will be dropped. Defaults to |
| * true. |
| */ |
| public void setDropTables(boolean dropTables) { |
| _dropTables = dropTables; |
| } |
| |
| /** |
| * If true, sequences that appear to be unused will be dropped. Defaults |
| * to true. |
| */ |
| public boolean getDropSequences() { |
| return _dropSeqs; |
| } |
| |
| /** |
| * If true, sequences that appear to be unused will be dropped. Defaults |
| * to true. |
| */ |
| public void setDropSequences(boolean dropSeqs) { |
| _dropSeqs = dropSeqs; |
| if (dropSeqs) |
| setSequences(true); |
| } |
| |
| /** |
| * If true, rollback will be performed before each DDL statement is executed. Defaults to true. |
| */ |
| public boolean getRollbackBeforeDDL() { |
| return _rollbackBeforeDDL; |
| } |
| |
| /** |
| * If true, rollback will be performed before each DDL statement is executed. Defaults to true. |
| */ |
| public void setRollbackBeforeDDL(boolean rollbackBeforeDDL) { |
| _rollbackBeforeDDL = rollbackBeforeDDL; |
| } |
| |
| /** |
| * Whether sequences should be manipulated. Defaults to true. |
| */ |
| public boolean getSequences() { |
| return _seqs; |
| } |
| |
| /** |
| * Whether sequences should be manipulated. Defaults to true. |
| */ |
| public void setSequences(boolean seqs) { |
| _seqs = seqs; |
| } |
| |
| /** |
| * Whether indexes on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public boolean getIndexes() { |
| return _indexes; |
| } |
| |
| /** |
| * Whether indexes on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public void setIndexes(boolean indexes) { |
| _indexes = indexes; |
| } |
| |
| /** |
| * Whether foreign keys on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public boolean getForeignKeys() { |
| return _fks; |
| } |
| |
| /** |
| * Whether foreign keys on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public void setForeignKeys(boolean fks) { |
| _fks = fks; |
| } |
| |
| /** |
| * Whether primary keys on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public boolean getPrimaryKeys() { |
| return _pks; |
| } |
| |
| /** |
| * Whether primary keys on existing tables should be manipulated. |
| * Defaults to true. |
| */ |
| public void setPrimaryKeys(boolean pks) { |
| _pks = pks; |
| } |
| |
| /** |
| * The stream to write to for the creation of SQL scripts. If the |
| * stream is non-null, all SQL will be written to this stream rather than |
| * executed against the database. |
| */ |
| public Writer getWriter() { |
| return _writer; |
| } |
| |
| /** |
| * The stream to write to for the creation of SQL scripts. If the |
| * stream is non-null, all SQL will be written to this stream rather than |
| * executed against the database. |
| */ |
| public void setWriter(Writer writer) { |
| if (writer == null) |
| _writer = null; |
| else if (writer instanceof PrintWriter) |
| _writer = (PrintWriter) writer; |
| else |
| _writer = new PrintWriter(writer); |
| } |
| |
| public void setSQLTerminator(String t) { |
| _sqlTerminator = t; |
| } |
| |
| public void setScriptToExecute(String scriptToExecute) { |
| _scriptToExecute = scriptToExecute; |
| } |
| |
| /** |
| * Return the schema group the tool will act on. |
| */ |
| public SchemaGroup getSchemaGroup() { |
| return _group; |
| } |
| |
| /** |
| * Set the schema group the tool will act on. |
| */ |
| public void setSchemaGroup(SchemaGroup group) { |
| _group = group; |
| } |
| |
| /////////// |
| // Actions |
| /////////// |
| |
| /** |
| * Run the tool action. |
| */ |
| public void run() |
| throws SQLException { |
| if (_action == null) |
| return; |
| |
| if (ACTION_ADD.equals(_action)) |
| add(); |
| else if (ACTION_DROP.equals(_action)) |
| drop(); |
| else if (ACTION_DROP_SCHEMA.equals(_action)) |
| dropSchema(); |
| else if (ACTION_RETAIN.equals(_action)) |
| retain(); |
| else if (ACTION_REFRESH.equals(_action)) |
| refresh(); |
| else if (ACTION_BUILD.equals(_action)) |
| build(); |
| else if (ACTION_CREATEDB.equals(_action)) |
| createDB(); |
| else if (ACTION_DROPDB.equals(_action)) |
| dropDB(); |
| else if (ACTION_DELETE_TABLE_CONTENTS.equals(_action)) |
| deleteTableContents(); |
| else if (ACTION_EXECUTE_SCRIPT.equals(_action)) { |
| executeScript(); |
| } |
| } |
| |
| /** |
| * Adds any components present in the schema repository but absent from |
| * the database. |
| */ |
| protected void add() |
| throws SQLException { |
| add(getDBSchemaGroup(false), assertSchemaGroup()); |
| } |
| |
| /** |
| * Drops all schema components in the schema repository that also exist |
| * in the database. |
| */ |
| protected void drop() |
| throws SQLException { |
| drop(getDBSchemaGroup(false), assertSchemaGroup()); |
| } |
| |
| /** |
| * Drops all schema components in the schema repository. |
| */ |
| protected void dropSchema() |
| throws SQLException { |
| drop(getDBSchemaGroup(false), assertSchemaGroup(), false); |
| } |
| |
| /** |
| * Drops database components that are not mentioned in the schema |
| * repository. |
| */ |
| protected void retain() |
| throws SQLException { |
| retain(getDBSchemaGroup(true), assertSchemaGroup(), |
| getDropTables(), getDropSequences()); |
| } |
| |
| /** |
| * Adds any components present in the schema repository but absent from |
| * the database, and drops unused database components. |
| */ |
| protected void refresh() |
| throws SQLException { |
| SchemaGroup local = assertSchemaGroup(); |
| SchemaGroup db = getDBSchemaGroup(true); |
| retain(db, local, getDropTables(), getDropSequences()); |
| add(db, local); |
| } |
| |
| /** |
| * Re-execute all SQL used for the creation of the current database; |
| * this action is usually used when creating SQL scripts. |
| */ |
| protected void createDB() |
| throws SQLException { |
| SchemaGroup group = new SchemaGroup(); |
| group.addSchema(); |
| add(group, getDBSchemaGroup(true)); |
| } |
| |
| /** |
| * Re-execute all SQL used for the creation of the current database; |
| * this action is usually used when creating SQL scripts. |
| */ |
| protected void build() |
| throws SQLException { |
| SchemaGroup group = new SchemaGroup(); |
| group.addSchema(); |
| buildSchema(group, assertSchemaGroup(), true); |
| } |
| |
| /** |
| * Drop the current database. |
| */ |
| protected void dropDB() |
| throws SQLException { |
| retain(getDBSchemaGroup(true), new SchemaGroup(), true, true); |
| } |
| |
| /** |
| * Issue DELETE statement against all known tables. |
| */ |
| protected void deleteTableContents() |
| throws SQLException { |
| SchemaGroup group = getSchemaGroup(); |
| Schema[] schemas = group.getSchemas(); |
| Collection<Table> tables = new LinkedHashSet<>(); |
| for (Schema schema : schemas) { |
| Table[] ts = schema.getTables(); |
| for (Table t : ts) { |
| tables.add(t); |
| } |
| } |
| Table[] tableArray = tables.toArray(new Table[tables.size()]); |
| Connection conn = _ds.getConnection(); |
| try { |
| String[] sql = _conf.getDBDictionaryInstance() |
| .getDeleteTableContentsSQL(tableArray, conn); |
| if (!executeSQL(sql)) { |
| _log.warn(_loc.get("delete-table-contents")); |
| } |
| } finally { |
| closeConnection(conn); |
| } |
| } |
| |
| protected void executeScript() throws SQLException { |
| if (_scriptToExecute == null) { |
| _log.warn(_loc.get("generating-execute-script-not-defined")); |
| return; |
| } |
| |
| URL url = AccessController.doPrivileged( |
| J2DoPrivHelper.getResourceAction(_conf.getClassResolverInstance(). |
| getClassLoader(SchemaTool.class, null), _scriptToExecute)); |
| |
| if (url == null) { |
| _log.error(_loc.get("generating-execute-script-not-found", _scriptToExecute)); |
| return; |
| } |
| |
| _log.info(_loc.get("generating-execute-script", _scriptToExecute)); |
| BufferedReader reader = null; |
| try { |
| reader = new BufferedReader(new InputStreamReader(url.openStream())); |
| String sql; |
| List<String> script = new ArrayList<>(); |
| while ((sql = reader.readLine()) != null) { |
| sql = sql.trim(); |
| if (sql.startsWith("--") || sql.startsWith("/*") || sql.startsWith("//")) { |
| continue; |
| } |
| |
| int semiColonPosition = sql.indexOf(";"); // ';' can be in string, don't blindly drop it |
| if (sql.endsWith(";")) { |
| sql = sql.substring(0, sql.length() - 1); |
| } |
| if (sql.isEmpty()) { |
| continue; |
| } |
| script.add(sql); |
| } |
| |
| executeSQL(script.toArray(new String[script.size()])); |
| } catch (IOException e) { |
| _log.error(e.getMessage(), e); |
| } finally { |
| try { |
| if (reader != null) { |
| reader.close(); |
| } |
| } catch (IOException e) { |
| _log.error(e.getMessage(), e); |
| } |
| } |
| } |
| |
| /** |
| * Record the changes made to the DB in the current {@link SchemaFactory}. |
| */ |
| public void record() { |
| if (_db != null && _writer == null) |
| _conf.getSchemaFactoryInstance().storeSchema(_db); |
| } |
| |
| /** |
| * Adds all database components in the repository schema that are not |
| * present in the given database schema to the database. |
| */ |
| protected void add(SchemaGroup db, SchemaGroup repos) |
| throws SQLException { |
| |
| buildSchema(db, repos, true); |
| } |
| |
| protected void buildSchema(SchemaGroup db, SchemaGroup repos, boolean considerDatabaseState) throws SQLException { |
| // add sequences |
| Schema[] schemas = repos.getSchemas(); |
| Schema schema; |
| if (_seqs) { |
| Sequence[] seqs; |
| for (Schema value : schemas) { |
| seqs = value.getSequences(); |
| for (Sequence seq : seqs) { |
| if (considerDatabaseState && db.findSequence(value, seq.getQualifiedPath()) != null) { |
| if (_writer == null) { |
| continue; |
| } |
| } |
| |
| if (createSequence(seq)) { |
| schema = db.getSchema(seq.getSchemaIdentifier()); |
| if (schema == null) |
| schema = db.addSchema(seq.getSchemaIdentifier()); |
| schema.importSequence(seq); |
| } |
| else |
| _log.warn(_loc.get("add-seq", seq)); |
| } |
| } |
| } |
| |
| // order is important in this method; start with columns |
| Table[] tabs; |
| Table dbTable = null; |
| Column[] cols; |
| Column col; |
| DBIdentifier defaultSchemaName = DBIdentifier.newSchema(_dict.getDefaultSchemaName()); |
| for (Schema schema2 : schemas) { |
| tabs = schema2.getTables(); |
| for (Table tab : tabs) { |
| cols = tab.getColumns(); |
| if (considerDatabaseState) { |
| dbTable = db.findTable(schema2, tab.getQualifiedPath(), defaultSchemaName); |
| } |
| for (Column column : cols) { |
| if (dbTable != null) { |
| DBIdentifier colName = column.getIdentifier(); |
| col = dbTable.getColumn(colName); |
| if (col == null) { |
| if (addColumn(column)) |
| dbTable.importColumn(column); |
| else |
| _log.warn(_loc.get("add-col", column, |
| tab)); |
| } |
| else if (!column.equalsColumn(_dict, col)) { |
| _log.warn(_loc.get("bad-col", new Object[]{ |
| col, dbTable, col.getDescription(), |
| column.getDescription()})); |
| } |
| } |
| } |
| } |
| } |
| |
| // primary keys |
| if (_pks) { |
| PrimaryKey pk; |
| for (Schema value : schemas) { |
| tabs = value.getTables(); |
| for (Table tab : tabs) { |
| pk = tab.getPrimaryKey(); |
| if (considerDatabaseState) { |
| dbTable = db.findTable(value, tab.getQualifiedPath()); |
| } |
| if (pk != null && !pk.isLogical() && dbTable != null) { |
| if (dbTable.getPrimaryKey() == null |
| && addPrimaryKey(pk)) |
| dbTable.importPrimaryKey(pk); |
| else if (dbTable.getPrimaryKey() == null) |
| _log.warn(_loc.get("add-pk", pk, tab)); |
| else if (!pk.equalsPrimaryKey(dbTable.getPrimaryKey())) |
| _log.warn(_loc.get("bad-pk", |
| dbTable.getPrimaryKey(), dbTable)); |
| } |
| } |
| } |
| } |
| |
| // tables |
| Set<Table> newTables = new HashSet<>(); |
| for (Schema schema1 : schemas) { |
| tabs = schema1.getTables(); |
| for (Table tab : tabs) { |
| if (considerDatabaseState && db.findTable(schema1, tab.getQualifiedPath()) != null) { |
| if (_writer == null) { |
| continue; |
| } |
| } |
| |
| if (createTable(tab)) { |
| newTables.add(tab); |
| schema = db.getSchema(tab.getSchemaIdentifier()); |
| if (schema == null) |
| schema = db.addSchema(tab.getSchemaIdentifier()); |
| schema.importTable(tab); |
| } |
| else |
| _log.warn(_loc.get("add-table", tab)); |
| } |
| } |
| |
| // indexes |
| Index[] idxs; |
| Index idx; |
| for (Schema element : schemas) { |
| tabs = element.getTables(); |
| for (Table tab : tabs) { |
| // create indexes on new tables even if indexes |
| // have been turned off |
| if (!_indexes && !newTables.contains(tab)) |
| continue; |
| |
| idxs = tab.getIndexes(); |
| if (considerDatabaseState) { |
| dbTable = db.findTable(element, tab.getQualifiedPath()); |
| } |
| for (Index index : idxs) { |
| if (dbTable != null) { |
| idx = findIndex(dbTable, index); |
| if (idx == null) { |
| if (createIndex(index, dbTable, tab.getUniques())) |
| dbTable.importIndex(index); |
| else |
| _log.warn(_loc.get("add-index", index, |
| tab)); |
| } |
| else if (!index.equalsIndex(idx)) |
| _log.warn(_loc.get("bad-index", idx, dbTable)); |
| } |
| } |
| } |
| } |
| |
| // Unique Constraints on group of columns |
| Unique[] uniques; |
| for (Schema item : schemas) { |
| tabs = item.getTables(); |
| for (Table tab : tabs) { |
| // create unique constraints only on new tables |
| if (!newTables.contains(tab)) { |
| if (_writer == null) { |
| continue; |
| } |
| } |
| |
| uniques = tab.getUniques(); |
| if (uniques == null || uniques.length == 0) |
| continue; |
| if (considerDatabaseState) { |
| dbTable = db.findTable(tab); |
| } |
| if (dbTable == null) |
| continue; |
| for (Unique unique : uniques) { |
| dbTable.importUnique(unique); |
| } |
| } |
| } |
| |
| // foreign keys |
| ForeignKey[] fks; |
| ForeignKey fk; |
| for (Schema value : schemas) { |
| tabs = value.getTables(); |
| for (Table tab : tabs) { |
| // create foreign keys on new tables even if fks |
| // have been turned off |
| if (!_fks && !newTables.contains(tab)) { |
| if (_writer == null) { |
| continue; |
| } |
| } |
| |
| fks = tab.getForeignKeys(); |
| if (considerDatabaseState) { |
| dbTable = db.findTable(value, tab.getQualifiedPath()); |
| } |
| for (ForeignKey foreignKey : fks) { |
| if (!foreignKey.isLogical() && dbTable != null) { |
| fk = findForeignKey(dbTable, foreignKey); |
| if (fk == null) { |
| if (addForeignKey(foreignKey)) |
| dbTable.importForeignKey(foreignKey); |
| else |
| _log.warn(_loc.get("add-fk", |
| foreignKey, tab)); |
| } |
| else if (!foreignKey.equalsForeignKey(fk)) |
| _log.warn(_loc.get("bad-fk", fk, dbTable)); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Drops all database components that are in the given database schema |
| * but not in the repository schema. |
| */ |
| protected void retain(SchemaGroup db, SchemaGroup repos, boolean tables, |
| boolean sequences) |
| throws SQLException { |
| Schema[] schemas = db.getSchemas(); |
| if (_seqs && sequences) { |
| Sequence[] seqs; |
| for (Schema schema : schemas) { |
| seqs = schema.getSequences(); |
| for (Sequence seq : seqs) { |
| if (!isDroppable(seq)) |
| continue; |
| if (repos.findSequence(seq) == null) { |
| if (dropSequence(seq)) |
| schema.removeSequence(seq); |
| else |
| _log.warn(_loc.get("drop-seq", seq)); |
| } |
| } |
| } |
| } |
| |
| // order is important in this method; start with foreign keys |
| Table[] tabs; |
| Table reposTable; |
| if (_fks) { |
| ForeignKey[] fks; |
| ForeignKey fk; |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| fks = tab.getForeignKeys(); |
| reposTable = repos.findTable(tab); |
| if (!tables && reposTable == null) |
| continue; |
| |
| for (ForeignKey foreignKey : fks) { |
| if (foreignKey.isLogical()) |
| continue; |
| |
| fk = null; |
| if (reposTable != null) |
| fk = findForeignKey(reposTable, foreignKey); |
| if (reposTable == null || fk == null |
| || !foreignKey.equalsForeignKey(fk)) { |
| if (dropForeignKey(foreignKey)) |
| tab.removeForeignKey(foreignKey); |
| else |
| _log.warn(_loc.get("drop-fk", foreignKey, |
| tab)); |
| } |
| } |
| } |
| } |
| } |
| |
| // primary keys |
| if (_pks) { |
| PrimaryKey pk; |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| pk = tab.getPrimaryKey(); |
| if (pk != null && pk.isLogical()) |
| continue; |
| |
| reposTable = repos.findTable(tab); |
| if (pk != null && reposTable != null |
| && (reposTable.getPrimaryKey() == null |
| || !pk.equalsPrimaryKey(reposTable.getPrimaryKey()))) { |
| if (dropPrimaryKey(pk)) |
| tab.removePrimaryKey(); |
| else |
| _log.warn(_loc.get("drop-pk", pk, tab)); |
| } |
| } |
| } |
| } |
| |
| // columns |
| Column[] cols; |
| Column col; |
| Collection<Table> drops = new LinkedList<>(); |
| for (Schema value : schemas) { |
| tabs = value.getTables(); |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| cols = tab.getColumns(); |
| reposTable = repos.findTable(tab); |
| if (reposTable != null) { |
| for (Column column : cols) { |
| col = reposTable.getColumn(column.getIdentifier()); |
| if (col == null || !column.equalsColumn(_dict, col)) { |
| if (tab.getColumns().length == 1) |
| drops.add(tab); |
| else if (dropColumn(column)) |
| tab.removeColumn(column); |
| else |
| _log.warn(_loc.get("drop-col", column, |
| tab)); |
| } |
| } |
| } |
| } |
| } |
| |
| // now tables |
| if (tables) { |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) |
| if (isDroppable(tab) |
| && repos.findTable(tab) == null) |
| drops.add(tab); |
| } |
| } |
| dropTables(drops, db); |
| } |
| |
| protected void drop(SchemaGroup db, SchemaGroup repos) |
| throws SQLException { |
| |
| drop(db, repos, true); |
| } |
| |
| /** |
| * Drops all database components in the given repository schema. |
| */ |
| private void drop(SchemaGroup db, SchemaGroup repos, boolean considerDatabaseState) |
| throws SQLException { |
| // drop sequences |
| Schema[] schemas = repos.getSchemas(); |
| if (_seqs) { |
| Sequence[] seqs; |
| Sequence dbSeq; |
| for (Schema schema : schemas) { |
| seqs = schema.getSequences(); |
| for (Sequence seq : seqs) { |
| if (!isDroppable(seq)) |
| continue; |
| dbSeq = db.findSequence(seq); |
| if (dbSeq != null) { |
| if (dropSequence(seq)) |
| dbSeq.getSchema().removeSequence(dbSeq); |
| else |
| _log.warn(_loc.get("drop-seq", seq)); |
| } |
| else if (_writer != null) { |
| dropSequence(seq); |
| } |
| } |
| } |
| } |
| |
| // calculate tables to drop; we can only drop tables if we're sure |
| // the user listed the entire table definition in the stuff they want |
| // dropped; else they might just want to drop a few columns |
| Collection<Table> drops = new LinkedList<>(); |
| Table[] tabs; |
| Table dbTable; |
| Column[] dbCols; |
| for (Schema value : schemas) { |
| tabs = value.getTables(); |
| tables: |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| |
| if (!considerDatabaseState) { |
| drops.add(tab); |
| continue; |
| } |
| |
| dbTable = db.findTable(tab); |
| if (dbTable == null) { |
| if (_writer != null) { |
| drops.add(tab); |
| } |
| continue; |
| } |
| |
| dbCols = dbTable.getColumns(); |
| for (Column dbCol : dbCols) { |
| if (!dbCol.getIdentifier().getName().equals(_dict.getIdentityColumnName()) && |
| !tab.containsColumn(dbCol)) |
| continue tables; |
| } |
| drops.add(tab); |
| } |
| } |
| |
| // order is important in this method; start with foreign keys mentioned |
| // in the drop schema |
| if (_fks) { |
| ForeignKey[] fks; |
| ForeignKey fk; |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| fks = tab.getForeignKeys(); |
| dbTable = db.findTable(tab); |
| for (ForeignKey foreignKey : fks) { |
| if (foreignKey.isLogical()) |
| continue; |
| |
| fk = null; |
| if (dbTable != null) |
| fk = findForeignKey(dbTable, foreignKey); |
| if (dbTable == null || fk == null) |
| continue; |
| |
| if (dropForeignKey(foreignKey)) |
| if (dbTable != null) |
| dbTable.removeForeignKey(fk); |
| else |
| _log.warn(_loc.get("drop-fk", foreignKey, tab)); |
| } |
| } |
| } |
| |
| // also drop imported foreign keys for tables that will be dropped |
| Table tab; |
| for (Table drop : drops) { |
| tab = drop; |
| dbTable = db.findTable(tab); |
| if (dbTable == null) |
| continue; |
| |
| fks = db.findExportedForeignKeys(dbTable.getPrimaryKey()); |
| for (ForeignKey foreignKey : fks) { |
| if (dropForeignKey(foreignKey)) |
| dbTable.removeForeignKey(foreignKey); |
| else |
| _log.warn(_loc.get("drop-fk", foreignKey, dbTable)); |
| } |
| } |
| } |
| |
| // drop the tables we calculated above |
| dropTables(drops, db); |
| |
| if (considerDatabaseState) { |
| // columns |
| Column[] cols; |
| Column col; |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) { |
| if (!isDroppable(tab)) |
| continue; |
| cols = tab.getColumns(); |
| dbTable = db.findTable(tab); |
| for (Column column : cols) { |
| col = null; |
| if (dbTable != null) |
| col = dbTable.getColumn(column.getIdentifier()); |
| if (dbTable == null || col == null) |
| continue; |
| |
| if (dropColumn(column)) { |
| dbTable.removeColumn(col); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Return true if the table is droppable. |
| */ |
| protected boolean isDroppable(Table table) { |
| return _openjpaTables |
| || (!DBIdentifier.toUpper(table.getIdentifier()).getName().startsWith("OPENJPA_") |
| && !DBIdentifier.toUpper(table.getIdentifier()).getName().startsWith("JDO_")); // legacy |
| } |
| |
| /** |
| * Return true if the sequence is droppable. |
| */ |
| protected boolean isDroppable(Sequence seq) { |
| return _openjpaTables |
| || (!DBIdentifier.toUpper(seq.getIdentifier()).getName().startsWith("OPENJPA_") |
| && !DBIdentifier.toUpper(seq.getIdentifier()).getName().startsWith("JDO_")); // legacy |
| } |
| |
| /** |
| * Find an index in the given table that matches the given one. |
| */ |
| protected Index findIndex(Table dbTable, Index idx) { |
| Index[] idxs = dbTable.getIndexes(); |
| for (Index index : idxs) |
| if (idx.columnsMatch(index.getColumns())) |
| return index; |
| return null; |
| } |
| |
| /** |
| * Find a foreign key in the given table that matches the given one. |
| */ |
| protected ForeignKey findForeignKey(Table dbTable, ForeignKey fk) { |
| if (fk.getConstantColumns().length > 0 |
| || fk.getConstantPrimaryKeyColumns().length > 0) |
| return null; |
| ForeignKey[] fks = dbTable.getForeignKeys(); |
| for (ForeignKey foreignKey : fks) |
| if (fk.columnsMatch(foreignKey.getColumns(), |
| foreignKey.getPrimaryKeyColumns())) |
| return foreignKey; |
| return null; |
| } |
| |
| /** |
| * Remove the given collection of tables from the database schema. Orders |
| * the removals according to foreign key constraints on the tables. |
| */ |
| protected void dropTables(Collection<Table> tables, SchemaGroup change) |
| throws SQLException { |
| if (tables.isEmpty()) |
| return; |
| |
| Table table; |
| Table changeTable; |
| for (Table value : tables) { |
| table = value; |
| if (dropTable(table)) { |
| changeTable = change.findTable(table); |
| if (changeTable != null) |
| changeTable.getSchema().removeTable(changeTable); |
| } |
| else |
| _log.warn(_loc.get("drop-table", table)); |
| } |
| } |
| |
| /** |
| * Add the given table to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean createTable(Table table) |
| throws SQLException { |
| return executeSQL(_dict.getCreateTableSQL(table, _db)); |
| } |
| |
| /** |
| * Drop the given table from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropTable(Table table) |
| throws SQLException { |
| return executeSQL(_dict.getDropTableSQL(table)); |
| } |
| |
| /** |
| * Add the given sequence to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean createSequence(Sequence seq) |
| throws SQLException { |
| return executeSQL(_dict.getCreateSequenceSQL(seq)); |
| } |
| |
| /** |
| * Drop the given sequence from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropSequence(Sequence seq) |
| throws SQLException { |
| return executeSQL(_dict.getDropSequenceSQL(seq)); |
| } |
| |
| /** |
| * Add the given index to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean createIndex(Index idx, Table table) |
| throws SQLException { |
| return createIndex(idx, table, null); |
| } |
| |
| public boolean createIndex(Index idx, Table table, Unique[] uniques) |
| throws SQLException { |
| // Informix will automatically create a unique index for the |
| // primary key, so don't create another index again |
| |
| if (!_dict.needsToCreateIndex(idx,table,uniques)) |
| return false; |
| |
| int max = _dict.maxIndexesPerTable; |
| |
| int len = table.getIndexes().length; |
| if (table.getPrimaryKey() != null) |
| len += table.getPrimaryKey().getColumns().length; |
| |
| if (len >= max) { |
| _log.warn(_loc.get("too-many-indexes", idx, table, max + "")); |
| return false; |
| } |
| |
| return executeSQL(_dict.getCreateIndexSQL(idx)); |
| } |
| |
| /** |
| * Drop the given index from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropIndex(Index idx) |
| throws SQLException { |
| return executeSQL(_dict.getDropIndexSQL(idx)); |
| } |
| |
| /** |
| * Add the given column to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean addColumn(Column col) |
| throws SQLException { |
| return executeSQL(_dict.getAddColumnSQL(col)); |
| } |
| |
| /** |
| * Drop the given column from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropColumn(Column col) |
| throws SQLException { |
| return executeSQL(_dict.getDropColumnSQL(col)); |
| } |
| |
| /** |
| * Add the given primary key to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean addPrimaryKey(PrimaryKey pk) |
| throws SQLException { |
| return executeSQL(_dict.getAddPrimaryKeySQL(pk)); |
| } |
| |
| /** |
| * Drop the given primary key from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropPrimaryKey(PrimaryKey pk) |
| throws SQLException { |
| return executeSQL(_dict.getDropPrimaryKeySQL(pk)); |
| } |
| |
| /** |
| * Add the given foreign key to the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean addForeignKey(ForeignKey fk) |
| throws SQLException { |
| return executeSQL(_dict.getAddForeignKeySQL(fk)); |
| } |
| |
| /** |
| * Drop the given foreign key from the database schema. |
| * |
| * @return true if the operation was successful, false otherwise |
| */ |
| public boolean dropForeignKey(ForeignKey fk) |
| throws SQLException { |
| Connection conn = _ds.getConnection(); |
| try { |
| return executeSQL(_dict.getDropForeignKeySQL(fk,conn)); |
| } finally { |
| closeConnection(conn); |
| } |
| |
| } |
| |
| /** |
| * Return the database schema. |
| */ |
| public SchemaGroup getDBSchemaGroup() { |
| try { |
| return getDBSchemaGroup(true); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| /** |
| * Set the database schema. |
| */ |
| public void setDBSchemaGroup(SchemaGroup db) { |
| _db = db; |
| if (db != null) |
| _fullDB = true; |
| } |
| |
| /** |
| * Return the database schema. |
| * |
| * @param full if false, only the tables named in the set schema |
| * repository will be generated |
| */ |
| protected SchemaGroup getDBSchemaGroup(boolean full) |
| throws SQLException { |
| if (_db == null || (full && !_fullDB)) { |
| SchemaGenerator gen = new SchemaGenerator(_conf); |
| gen.setPrimaryKeys(_pks); |
| gen.setForeignKeys(_fks); |
| gen.setIndexes(_indexes); |
| if (full) |
| gen.generateSchemas(); |
| else { |
| // generate only the tables in the given repository |
| // group; some may not exist yet, which is OK; we just need |
| // to make sure we can detect the changes to the ones that |
| // do exist |
| Collection<DBIdentifier> tables = new LinkedList<>(); |
| SchemaGroup group = assertSchemaGroup(); |
| Schema[] schemas = group.getSchemas(); |
| Table[] tabs; |
| for (Schema schema : schemas) { |
| tabs = schema.getTables(); |
| for (Table tab : tabs) { |
| if (DBIdentifier.isNull(tab.getSchemaIdentifier())) { |
| tables.add(tab.getIdentifier()); |
| } |
| else { |
| DBIdentifier sName = tab.getFullIdentifier(); |
| tables.add(sName); |
| } |
| } |
| } |
| if (!tables.isEmpty()) |
| gen.generateSchemas(tables.toArray(new DBIdentifier[tables.size()])); |
| } |
| _db = gen.getSchemaGroup(); |
| } |
| return _db; |
| } |
| |
| protected SchemaGroup assertSchemaGroup() { |
| SchemaGroup local = getSchemaGroup(); |
| if (local == null) |
| throw new InvalidStateException(_loc.get("tool-norepos")); |
| return local; |
| } |
| |
| ///////////// |
| // Utilities |
| ///////////// |
| |
| /** |
| * Executes the given array of non-selecting SQL statements, correctly |
| * logging the SQL calls and optionally ignoring errors. |
| * |
| * @return true if there was SQL to execute and the calls were |
| * successful, false otherwise |
| */ |
| protected boolean executeSQL(String[] sql) |
| throws SQLException { |
| // if no sql, probably b/c dictionary doesn't support operation |
| if (sql.length == 0) |
| return false; |
| |
| boolean err = false; |
| if (_writer == null) { |
| // this is outside the try-catch because a failure here is |
| // really bad, and should not be ignored. |
| Connection conn = _ds.getConnection(); |
| Statement statement = null; |
| boolean wasAuto = true; |
| try { |
| if (_rollbackBeforeDDL) { |
| wasAuto = conn.getAutoCommit(); |
| if (!wasAuto) { |
| conn.setAutoCommit(true); |
| } |
| } |
| for (String s : sql) { |
| try { |
| if (_rollbackBeforeDDL) { |
| // some connections require that rollback be |
| // called on the connection before any DDL statements |
| // can be run on it, even when autocommit is on. |
| // This is sometimes because the connection does not |
| // allow DDL statements when there are multiple |
| // commands issued on the connection, and the |
| // connection pool may have issued some validation SQL. |
| try { |
| conn.rollback(); |
| } |
| catch (Exception e) { |
| } |
| } |
| |
| statement = conn.createStatement(); |
| statement.executeUpdate(s); |
| |
| // some connections seem to require an explicit |
| // commit for DDL statements, even when autocommit |
| // is on. The DataDirect drivers seem to suffer from |
| // this limitation. |
| try { |
| conn.commit(); |
| } |
| catch (Exception e) { |
| } |
| } |
| catch (SQLException se) { |
| err = true; |
| handleException(se); |
| } |
| finally { |
| if (statement != null) |
| try { |
| statement.close(); |
| } |
| catch (SQLException se) { |
| } |
| } |
| } |
| } |
| finally { |
| if (_rollbackBeforeDDL && !wasAuto) { |
| conn.setAutoCommit(false); |
| } |
| |
| try { |
| closeConnection(conn); |
| } catch (SQLException se) { |
| //X TODO why catch silently? |
| } |
| } |
| } else { |
| for (String s : sql) { |
| _writer.println(s + _sqlTerminator); |
| } |
| _writer.flush(); |
| } |
| |
| return !err; |
| } |
| |
| /** |
| * Handle the given exception, logging it and optionally ignoring it, |
| * depending on the flags this SchemaTool was created with. |
| */ |
| protected void handleException(SQLException sql) |
| throws SQLException { |
| if (!_ignoreErrs) |
| throw sql; |
| _log.warn(sql.getMessage(), sql); |
| } |
| |
| //////// |
| // Main |
| //////// |
| |
| /** |
| * Usage: java org.apache.openjpa.jdbc.schema.SchemaTool [option]* |
| * [-action/-a <add | retain | drop | refresh | createDB | dropDB |
| * | build | reflect | import | export>] |
| * <.schema file or resource>* |
| * Where the following options are recognized. |
| * <ul> |
| * <li><i>-properties/-p <properties file or resource></i>: The |
| * path or resource name of a OpenJPA properties file containing |
| * information such as the license key and connection data as |
| * outlined in {@link JDBCConfiguration}. Optional.</li> |
| * <li><i>-<property name> <property value></i>: All bean |
| * properties of the OpenJPA {@link JDBCConfiguration} can be set by |
| * using their names and supplying a value. For example: |
| * <code>-licenseKey adslfja83r3lkadf</code></li> |
| * <li><i>-ignoreErrors/-i <true/t | false/f></i>: If false, an |
| * exception will will be thrown if the tool encounters any database |
| * exceptions; defaults to <code>false</code>.</li> |
| * <li><i>-file/-f <stdout | output file or resource></i>: Use this |
| * option to write a SQL script for the planned schema modifications, |
| * rather than committing them to the database. This option also |
| * applies to the <code>export</code> and <code>reflect</code> actions.</li> |
| * <li><i>-openjpaTables/-kt <true/t | false/f></i>: Under the |
| * <code>reflect</code> action, whether to reflect on tables with |
| * the name <code>OPENJPA_*</code>. Under other actions, whether to |
| * drop such tables. Defaults to <code>false</code>.</li> |
| * <li><i>-dropTables/-dt <true/t | false/f></i>: Set this option to |
| * true to drop tables that appear to be unused during |
| * <code>retain</code> and <code>refresh</code> actions. Defaults to |
| * <code>true</code>.</li> |
| * <li><i>-dropSequences/-dsq <true/t | false/f></i>: Set this option |
| * to true to drop sequences that appear to be unused during |
| * <code>retain</code> and <code>refresh</code> actions. Defaults to |
| * <code>true</code>.</li> |
| * <li><i>-rollbackBeforeDDL/-rbddl <true/t | false/f></i>: Set this option |
| * to true to send an initail rollback on the connection before any DDL statement |
| * is sent</li> |
| * <li><i>-primaryKeys/-pk <true/t | false/f></i>: Whether primary |
| * keys on existing tables are manipulated. Defaults to true.</li> |
| * <li><i>-foreignKeys/-fk <true/t | false/f></i>: Whether foreign |
| * keys on existing tables are manipulated. Defaults to true.</li> |
| * <li><i>-indexes/-ix <true/t | false/f></i>: Whether indexes |
| * on existing tables are manipulated. Defaults to true.</li> |
| * <li><i>-sequences/-sq <true/t | false/f></i>: Whether to |
| * manipulate sequences. Defaults to true.</li> |
| * <li><i>-record/-r <true/t | false/f></i>: Set this option to |
| * <code>false</code> to prevent writing the schema changes to the |
| * current {@link SchemaFactory}.</li> |
| * </ul> |
| * Actions can be composed in a comma-separated list. The various actions |
| * are as follows. |
| * <ul> |
| * <li><i>add</i>: Bring the schema up-to-date with the latest |
| * changes to the schema XML data by adding tables, columns, |
| * indexes, etc. This action never drops any data. This is the |
| * default action.</li> |
| * <li><i>retain</i>: Keep all schema components in the schema XML, but |
| * drop the rest from the database. This action never adds any data.</li> |
| * <li><i>drop</i>: Drop all the schema components in the schema XML.</li> |
| * <li><i>refresh</i>: Equivalent to retain, then add.</li> |
| * <li><i>createDB</i>: Execute SQL to re-create the current database. |
| * This action is typically used in conjuction with the |
| * <code>file</code> option.</li> |
| * <li><i>build</i>: Execute SQL to build the schema defined in the XML. |
| * Because it doesn't take the current database schema into account, |
| * this action is typically used in conjuction with the |
| * <code>file</code> option.</li> |
| * <li><i>reflect</i>: Reflect on the current database schema. Write the |
| * schema's XML representation to the file specified with the |
| * <code>file</code> option, or to stdout if no file is given.</li> |
| * <li><i>dropDB</i>: Execute SQL to drop the current database. This |
| * action implies <code>dropTables</code>.</li> |
| * <li><i>deleteTableContents</i>: Execute SQL to delete all rows from |
| * all tables that OpenJPA knows about.</li> |
| * <li><i>import</i>: Import the given XML schema definition into the |
| * current {@link SchemaFactory}.</li> |
| * <li><i>export</i>: Export the current {@link SchemaFactory}'s recorded |
| * schema to an XML schema definition file.</li> |
| * </ul> |
| * Examples: |
| * <ul> |
| * <li>Write a script to stdout to re-create the current database |
| * schema:<br /> |
| * <code>java org.apache.openjpa.jdbc.schema.SchemaTool -f stdout |
| * -a createDB</code></li> |
| * <li>Drop the current database schema:<br /> |
| * <code>java org.apache.openjpa.jdbc.schema.SchemaTool |
| * -a dropDB</code></li> |
| * <li>Refresh the schema and delete all records in all tables:<br /> |
| * <code>java org.apache.openjpa.jdbc.schema.SchemaTool |
| * -a refresh,deleteTableContents</code></li> |
| * <li>Create a schema based on an XML schema definition file:<br /> |
| * <code>java org.apache.openjpa.jdbc.schema.SchemaTool |
| * myschema.xml</code></li> |
| * </ul> |
| */ |
| public static void main(String[] args) |
| throws IOException, SQLException { |
| Options opts = new Options(); |
| final String[] arguments = opts.setFromCmdLine(args); |
| boolean ret = Configurations.runAgainstAllAnchors(opts, |
| new Configurations.Runnable() { |
| @Override |
| public boolean run(Options opts) throws Exception { |
| JDBCConfiguration conf = new JDBCConfigurationImpl(); |
| try { |
| return SchemaTool.run(conf, arguments, opts); |
| } finally { |
| conf.close(); |
| } |
| } |
| }); |
| if (!ret) { |
| // START - ALLOW PRINT STATEMENTS |
| System.out.println(_loc.get("tool-usage")); |
| // STOP - ALLOW PRINT STATEMENTS |
| } |
| } |
| |
| /** |
| * Run the tool. Returns false if any invalid options were given. |
| * |
| * @see #main |
| */ |
| public static boolean run(JDBCConfiguration conf, String[] args, |
| Options opts) |
| throws IOException, SQLException { |
| Flags flags = new Flags(); |
| flags.dropTables = opts.removeBooleanProperty |
| ("dropTables", "dt", flags.dropTables); |
| flags.dropSequences = opts.removeBooleanProperty |
| ("dropSequences", "dsq", flags.dropSequences); |
| flags.rollbackBeforeDDL = opts.removeBooleanProperty |
| ("rollbackBeforeDDL", "rbddl", flags.rollbackBeforeDDL); |
| flags.ignoreErrors = opts.removeBooleanProperty |
| ("ignoreErrors", "i", flags.ignoreErrors); |
| flags.openjpaTables = opts.removeBooleanProperty |
| ("openjpaTables", "ot", flags.openjpaTables); |
| flags.primaryKeys = opts.removeBooleanProperty |
| ("primaryKeys", "pk", flags.primaryKeys); |
| flags.foreignKeys = opts.removeBooleanProperty |
| ("foreignKeys", "fks", flags.foreignKeys); |
| flags.indexes = opts.removeBooleanProperty |
| ("indexes", "ix", flags.indexes); |
| flags.sequences = opts.removeBooleanProperty |
| ("sequences", "sq", flags.sequences); |
| flags.record = opts.removeBooleanProperty("record", "r", flags.record); |
| String fileName = opts.removeProperty("file", "f", null); |
| String schemas = opts.removeProperty("s"); |
| if (schemas != null) |
| opts.setProperty("schemas", schemas); |
| |
| String[] actions = opts.removeProperty("action", "a", flags.action) |
| .split(","); |
| |
| // setup a configuration instance with cmd-line info |
| Configurations.populateConfiguration(conf, opts); |
| |
| // create script writer |
| ClassLoader loader = conf.getClassResolverInstance(). |
| getClassLoader(SchemaTool.class, null); |
| flags.writer = Files.getWriter(fileName, loader); |
| |
| boolean returnValue = true; |
| for (String action : actions) { |
| flags.action = action; |
| returnValue &= run(conf, args, flags, loader); |
| } |
| |
| return returnValue; |
| } |
| |
| /** |
| * Run the tool. Return false if invalid options were given. |
| */ |
| public static boolean run(JDBCConfiguration conf, String[] args, |
| Flags flags, ClassLoader loader) |
| throws IOException, SQLException { |
| Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL); |
| if (ACTION_REFLECT.equals(flags.action)) { |
| if (args.length > 0) |
| return false; |
| if (flags.writer == null) |
| flags.writer = new PrintWriter(System.out); |
| |
| SchemaGenerator gen = new SchemaGenerator(conf); |
| gen.setPrimaryKeys(flags.primaryKeys); |
| gen.setIndexes(flags.indexes); |
| gen.setForeignKeys(flags.foreignKeys); |
| gen.setSequences(flags.sequences); |
| gen.setOpenJPATables(flags.openjpaTables); |
| |
| String schemas = conf.getSchemas(); |
| if (StringUtil.isEmpty(schemas)) |
| schemas = "all"; |
| log.info(_loc.get("sch-reflect", schemas)); |
| gen.generateSchemas(); |
| |
| // record the schema |
| log.info(_loc.get("sch-reflect-write")); |
| SchemaSerializer ser = new XMLSchemaSerializer(conf); |
| ser.addAll(gen.getSchemaGroup()); |
| ser.serialize(flags.writer, MetaDataSerializer.PRETTY); |
| return true; |
| } |
| |
| if (args.length == 0 |
| && !ACTION_CREATEDB.equals(flags.action) |
| && !ACTION_DROPDB.equals(flags.action) |
| && !ACTION_EXPORT.equals(flags.action) |
| && !ACTION_DELETE_TABLE_CONTENTS.equals(flags.action)) |
| return false; |
| |
| // parse in the arguments |
| SchemaParser parser = new XMLSchemaParser(conf); |
| parser.setDelayConstraintResolve(true); |
| File file; |
| for (String arg : args) { |
| file = Files.getFile(arg, loader); |
| log.info(_loc.get("tool-running", file)); |
| parser.parse(file); |
| } |
| parser.resolveConstraints(); |
| |
| if (ACTION_IMPORT.equals(flags.action)) { |
| log.info(_loc.get("tool-import-store")); |
| SchemaGroup schema = parser.getSchemaGroup(); |
| conf.getSchemaFactoryInstance().storeSchema(schema); |
| return true; |
| } |
| if (ACTION_EXPORT.equals(flags.action)) { |
| if (flags.writer == null) |
| flags.writer = new PrintWriter(System.out); |
| |
| log.info(_loc.get("tool-export-gen")); |
| SchemaGroup schema = conf.getSchemaFactoryInstance().readSchema(); |
| |
| log.info(_loc.get("tool-export-write")); |
| SchemaSerializer ser = new XMLSchemaSerializer(conf); |
| ser.addAll(schema); |
| ser.serialize(flags.writer, MetaDataSerializer.PRETTY); |
| return true; |
| } |
| |
| SchemaTool tool = new SchemaTool(conf, flags.action); |
| tool.setIgnoreErrors(flags.ignoreErrors); |
| tool.setDropTables(flags.dropTables); |
| tool.setSequences(flags.sequences); // set before dropseqs |
| tool.setDropSequences(flags.dropSequences); |
| tool.setRollbackBeforeDDL(flags.rollbackBeforeDDL); |
| tool.setPrimaryKeys(flags.primaryKeys); |
| tool.setForeignKeys(flags.foreignKeys); |
| tool.setIndexes(flags.indexes); |
| tool.setOpenJPATables(flags.openjpaTables); |
| if (args.length > 0) |
| tool.setSchemaGroup(parser.getSchemaGroup()); |
| if (flags.writer != null) |
| tool.setWriter(flags.writer); |
| |
| log.info(_loc.get("tool-action", flags.action)); |
| try { |
| tool.run(); |
| } finally { |
| if (flags.record) { |
| log.info(_loc.get("tool-record")); |
| tool.record(); |
| } |
| } |
| if (flags.writer != null) |
| flags.writer.flush(); |
| |
| return true; |
| } |
| |
| private void closeConnection(Connection conn) throws SQLException { |
| if (conn != null && !conn.isClosed()) { |
| conn.close(); |
| } |
| } |
| |
| /** |
| * Run flags. |
| */ |
| public static class Flags { |
| |
| public String action = ACTION_ADD; |
| public Writer writer = null; |
| public boolean dropTables = true; |
| public boolean rollbackBeforeDDL = true; |
| public boolean dropSequences = true; |
| public boolean ignoreErrors = false; |
| public boolean openjpaTables = false; |
| public boolean primaryKeys = true; |
| public boolean foreignKeys = true; |
| public boolean indexes = true; |
| public boolean sequences = true; |
| public boolean record = true; |
| } |
| } |