| /* |
| * 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.meta; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.sql.SQLException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| 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.kernel.JDBCSeq; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.DynamicSchemaFactory; |
| import org.apache.openjpa.jdbc.schema.LazySchemaFactory; |
| import org.apache.openjpa.jdbc.schema.Schema; |
| import org.apache.openjpa.jdbc.schema.SchemaGenerator; |
| import org.apache.openjpa.jdbc.schema.SchemaGroup; |
| import org.apache.openjpa.jdbc.schema.SchemaSerializer; |
| import org.apache.openjpa.jdbc.schema.SchemaTool; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.jdbc.schema.XMLSchemaSerializer; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.kernel.Seq; |
| import org.apache.openjpa.lib.conf.Configurations; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.meta.ClassArgParser; |
| import org.apache.openjpa.lib.meta.MetaDataSerializer; |
| import org.apache.openjpa.lib.meta.SourceTracker; |
| 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.Services; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.MetaDataFactory; |
| import org.apache.openjpa.meta.MetaDataModes; |
| import org.apache.openjpa.meta.MetaDataRepository; |
| import org.apache.openjpa.meta.QueryMetaData; |
| import org.apache.openjpa.meta.SequenceMetaData; |
| import org.apache.openjpa.meta.ValueStrategies; |
| import org.apache.openjpa.util.GeneralException; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.MetaDataException; |
| |
| /** |
| * Tool for manipulating class mappings and associated schema. |
| * |
| * @author Abe White |
| */ |
| public class MappingTool |
| implements MetaDataModes { |
| |
| public static final String SCHEMA_ACTION_NONE = "none"; |
| |
| public static final String ACTION_ADD = "add"; |
| public static final String ACTION_REFRESH = "refresh"; |
| public static final String ACTION_BUILD_SCHEMA = "buildSchema"; |
| public static final String ACTION_DROP = "drop"; |
| public static final String ACTION_DROP_SCHEMA = "dropSchema"; |
| public static final String ACTION_VALIDATE = "validate"; |
| public static final String ACTION_EXPORT = "export"; |
| public static final String ACTION_IMPORT = "import"; |
| public static final String ACTION_SCRIPT_CREATE = "scriptCreate"; |
| public static final String ACTION_SCRIPT_DROP = "scriptDrop"; |
| public static final String ACTION_SCRIPT_LOAD = "scriptLoad"; |
| |
| public static final String[] ACTIONS = new String[]{ |
| ACTION_ADD, |
| ACTION_REFRESH, |
| ACTION_BUILD_SCHEMA, |
| ACTION_DROP, |
| ACTION_DROP_SCHEMA, |
| ACTION_VALIDATE, |
| ACTION_EXPORT, |
| ACTION_IMPORT, |
| ACTION_SCRIPT_CREATE, |
| ACTION_SCRIPT_DROP, |
| ACTION_SCRIPT_LOAD, |
| }; |
| |
| private static final Localizer _loc = Localizer.forPackage(MappingTool.class); |
| |
| private final JDBCConfiguration _conf; |
| private final Log _log; |
| private final String _action; |
| private final boolean _meta; |
| private final int _mode; |
| private final ClassLoader _loader; |
| private final DBDictionary _dict; |
| |
| private MappingRepository _repos = null; |
| private SchemaGroup _schema = null; |
| private SchemaTool _schemaTool = null; |
| private String _schemaActions = SchemaTool.ACTION_ADD; |
| private boolean _readSchema = false; |
| private boolean _pks = false; |
| private boolean _fks = false; |
| private boolean _indexes = false; |
| private boolean _seqs = true; |
| private boolean _dropUnused = true; |
| private boolean _ignoreErrors = false; |
| private boolean _rollbackBeforeDDL = false; |
| private File _file = null; |
| private Writer _mappingWriter = null; |
| private Writer _schemaWriter = null; |
| |
| // buffer metadatas to be dropped |
| private Set<Class<?>> _dropCls = null; |
| private Set<ClassMapping> _dropMap = null; |
| private boolean _flush = false; |
| private boolean _flushSchema = false; |
| |
| /** |
| * Constructor. Supply configuration and action. |
| */ |
| public MappingTool(JDBCConfiguration conf, String action, boolean meta) { |
| this(conf, action, meta, null); |
| } |
| |
| /** |
| * Constructor. Supply configuration and action. |
| */ |
| public MappingTool(JDBCConfiguration conf, String action, boolean meta, ClassLoader loader) { |
| _conf = conf; |
| _log = conf.getLog(OpenJPAConfiguration.LOG_METADATA); |
| _meta = meta; |
| |
| if (action == null) |
| _action = ACTION_REFRESH; |
| else if (!Arrays.asList(ACTIONS).contains(action)) |
| throw new IllegalArgumentException("action == " + action); |
| else |
| _action = action; |
| |
| if (meta && ACTION_ADD.equals(_action)) |
| _mode = MODE_META; |
| else if (meta && ACTION_DROP.equals(_action)) |
| _mode = MODE_META | MODE_MAPPING | MODE_QUERY; |
| else |
| _mode = MODE_MAPPING; |
| |
| _loader = loader; |
| |
| _dict = _conf.getDBDictionaryInstance(); |
| } |
| |
| /** |
| * The action supplied on construction. |
| */ |
| public String getAction() { |
| return _action; |
| } |
| |
| /** |
| * Whether the action works on metadata as well as mappings. |
| */ |
| public boolean isMetaDataAction() { |
| return _meta; |
| } |
| |
| /** |
| * The schema modification policy, or <code>none</code>. See the |
| * ACTION constants in {@link SchemaTool}. May be a comma-separated |
| * list of values. Defaults to {@link SchemaTool#ACTION_ADD}. |
| */ |
| public String getSchemaAction() { |
| return _schemaActions; |
| } |
| |
| /** |
| * The schema modification policy, or <code>none</code>. See the |
| * ACTION constants in {@link SchemaTool}. May be a comma-separated |
| * list of values. Defaults to {@link SchemaTool#ACTION_ADD}. |
| */ |
| public void setSchemaAction(String schemaAction) { |
| _schemaActions = schemaAction; |
| } |
| |
| /** |
| * Set to true to read the entire schema before mapping. |
| * Leaving this option false saves time, but is dangerous when adding |
| * new mappings, because without full knowledge of the existing schema the |
| * mapping tool might create tables or indexes that conflict with |
| * existing components. |
| */ |
| public boolean getReadSchema() { |
| return _readSchema; |
| } |
| |
| /** |
| * Set to true to read the entire schema before mapping. |
| * Leaving this option false saves time, but is dangerous when adding |
| * new mappings, because without full knowledge of the existing schema the |
| * mapping tool might create tables or indexes that conflict with |
| * existing components. |
| */ |
| public void setReadSchema(boolean readSchema) { |
| _readSchema = readSchema; |
| } |
| |
| /** |
| * Whether to manipulate sequences. Defaults to true. |
| */ |
| public boolean getSequences() { |
| return _seqs; |
| } |
| |
| /** |
| * Whether to manipulate sequences. Defaults to true. |
| */ |
| public void setSequences(boolean seqs) { |
| _seqs = seqs; |
| } |
| |
| /** |
| * Whether indexes on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public boolean getIndexes() { |
| return _indexes; |
| } |
| |
| /** |
| * Whether indexes on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public void setIndexes(boolean indexes) { |
| _indexes = indexes; |
| } |
| |
| /** |
| * Whether foreign keys on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public boolean getForeignKeys() { |
| return _fks; |
| } |
| |
| /** |
| * Whether foreign keys on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public void setForeignKeys(boolean fks) { |
| _fks = fks; |
| } |
| |
| /** |
| * Whether primary keys on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public boolean getPrimaryKeys() { |
| return _pks; |
| } |
| |
| /** |
| * Whether primary keys on existing tables should be manipulated. |
| * Defaults to false. |
| */ |
| public void setPrimaryKeys(boolean pks) { |
| _pks = pks; |
| } |
| |
| /** |
| * Whether schema components that are unused by any mapping will be |
| * dropped from this tool's {@link SchemaGroup}, and, depending on |
| * the schema action, from the database. Defaults to true. |
| */ |
| public boolean getDropUnusedComponents() { |
| return _dropUnused; |
| } |
| |
| /** |
| * Whether schema components that are unused by any mapping will be |
| * dropped from this tool's {@link SchemaGroup}, and, depending on |
| * the schema action, from the database. Defaults to true. |
| */ |
| public void setDropUnusedComponents(boolean dropUnused) { |
| _dropUnused = dropUnused; |
| } |
| |
| /** |
| * Whether and SQL errors should cause a failure or just issue a warning. |
| */ |
| public void setIgnoreErrors(boolean ignoreErrors) { |
| _ignoreErrors = ignoreErrors; |
| } |
| |
| /** |
| * Whether and SQL errors should cause a failure or just issue a warning. |
| */ |
| public boolean getIgnoreErrors() { |
| return _ignoreErrors; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Return the schema tool to use for schema modification. |
| */ |
| private SchemaTool newSchemaTool(String action) { |
| if (SCHEMA_ACTION_NONE.equals(action)) |
| action = null; |
| SchemaTool tool = new SchemaTool(_conf, action); |
| tool.setIgnoreErrors(getIgnoreErrors()); |
| tool.setPrimaryKeys(getPrimaryKeys()); |
| tool.setForeignKeys(getForeignKeys()); |
| tool.setIndexes(getIndexes()); |
| tool.setSequences(getSequences()); |
| tool.setRollbackBeforeDDL(getRollbackBeforeDDL()); |
| return tool; |
| } |
| |
| /** |
| * Set the schema tool to use for schema modification. |
| */ |
| public void setSchemaTool(SchemaTool tool) { |
| _schemaTool = tool; |
| } |
| |
| /** |
| * The stream to export the planned schema to as an XML document. |
| * If non-null, then the database schema will not be altered. |
| */ |
| public Writer getSchemaWriter() { |
| return _schemaWriter; |
| } |
| |
| /** |
| * The stream to export the planned schema to as an XML document. |
| * If non-null, then the database schema will not be altered. |
| */ |
| public void setSchemaWriter(Writer schemaWriter) { |
| _schemaWriter = schemaWriter; |
| } |
| |
| /** |
| * The stream to export the planned mappings to as an XML document. |
| * If non-null, then the mapping repository will not be altered. |
| */ |
| public Writer getMappingWriter() { |
| return _mappingWriter; |
| } |
| |
| /** |
| * The stream to export the planned mappings to as an XML document. |
| * If non-null, then the mapping repository will not be altered. |
| */ |
| public void setMappingWriter(Writer mappingWriter) { |
| _mappingWriter = mappingWriter; |
| } |
| |
| /** |
| * If adding metadata, the metadata file to add to. |
| */ |
| public File getMetaDataFile() { |
| return _file; |
| } |
| |
| /** |
| * If adding metadata, the metadata file to add to. |
| */ |
| public void setMetaDataFile(File file) { |
| _file = file; |
| } |
| |
| /** |
| * Return the repository to use to access mapping information. |
| * Defaults to a new {@link MappingRepository}. |
| */ |
| public MappingRepository getRepository() { |
| if (_repos == null) { |
| _repos = _conf.newMappingRepositoryInstance(); |
| _repos.setSchemaGroup(getSchemaGroup()); |
| _repos.setValidate(MetaDataRepository.VALIDATE_UNENHANCED, false); |
| } |
| return _repos; |
| } |
| |
| /** |
| * Set the repository to use to access mapping information. |
| */ |
| public void setRepository(MappingRepository repos) { |
| _repos = repos; |
| } |
| |
| /** |
| * Return the schema group to use in mapping. If none has been set, the |
| * schema will be generated from the database. |
| */ |
| public SchemaGroup getSchemaGroup() { |
| if (_schema == null) { |
| if (_action.indexOf(ACTION_BUILD_SCHEMA) != -1) { |
| DynamicSchemaFactory factory = new DynamicSchemaFactory(); |
| factory.setConfiguration(_conf); |
| _schema = factory; |
| } else if (_readSchema |
| || contains(_schemaActions,SchemaTool.ACTION_RETAIN) |
| || contains(_schemaActions,SchemaTool.ACTION_REFRESH)) { |
| _schema = (SchemaGroup) newSchemaTool(null).getDBSchemaGroup(). |
| clone(); |
| } else { |
| // with this we'll just read tables as different mappings |
| // look for them |
| LazySchemaFactory factory = new LazySchemaFactory(); |
| factory.setConfiguration(_conf); |
| factory.setPrimaryKeys(getPrimaryKeys()); |
| factory.setForeignKeys(getForeignKeys()); |
| factory.setIndexes(getIndexes()); |
| _schema = factory; |
| } |
| |
| if (_schema.getSchemas().length == 0) |
| _schema.addSchema(); |
| } |
| return _schema; |
| } |
| |
| /** |
| * Set the schema to use in mapping. |
| */ |
| public void setSchemaGroup(SchemaGroup schema) { |
| _schema = schema; |
| } |
| |
| /** |
| * Reset the internal repository. This is called automatically after |
| * every {@link #record}. |
| */ |
| public void clear() { |
| _repos = null; |
| _schema = null; |
| _schemaTool = null; |
| _flush = false; |
| _flushSchema = false; |
| if (_dropCls != null) |
| _dropCls.clear(); |
| if (_dropMap != null) |
| _dropMap.clear(); |
| } |
| |
| /** |
| * Records the changes that have been made to both the mappings and the |
| * associated schema, and clears the tool for further use. This also |
| * involves clearing the internal mapping repository. |
| */ |
| public void record() { |
| record(null); |
| } |
| |
| public void record(MappingTool.Flags flags) { |
| MappingRepository repos = getRepository(); |
| MetaDataFactory io = repos.getMetaDataFactory(); |
| ClassMapping[] mappings; |
| |
| if (!ACTION_DROP.equals(_action)) |
| mappings = repos.getMappings(); |
| else if (_dropMap != null) |
| mappings = (ClassMapping[]) _dropMap.toArray |
| (new ClassMapping[_dropMap.size()]); |
| else |
| mappings = new ClassMapping[0]; |
| |
| try { |
| if (_dropCls != null && !_dropCls.isEmpty()) { |
| Class<?>[] cls = (Class[]) _dropCls.toArray |
| (new Class[_dropCls.size()]); |
| if (!io.drop(cls, _mode, null)) |
| _log.warn(_loc.get("bad-drop", _dropCls)); |
| } |
| |
| if (_flushSchema) { |
| // drop portions of the known schema that no mapping uses, and |
| // add sequences used for value generation |
| if (_dropUnused) |
| dropUnusedSchemaComponents(mappings); |
| addSequenceComponents(mappings); |
| |
| // now run the schematool as long as we're doing some schema |
| // action and the user doesn't just want an xml output |
| String[] schemaActions = _schemaActions.split(","); |
| for (String schemaAction : schemaActions) { |
| if (!SCHEMA_ACTION_NONE.equals(schemaAction) |
| && (_schemaWriter == null || (_schemaTool != null |
| && _schemaTool.getWriter() != null))) { |
| |
| SchemaTool tool; |
| if (schemaAction.equals(ACTION_SCRIPT_CREATE) || |
| schemaAction.equals(ACTION_SCRIPT_DROP) || |
| schemaAction.equals(ACTION_SCRIPT_LOAD)) { |
| tool = newSchemaTool(SchemaTool.ACTION_EXECUTE_SCRIPT); |
| } |
| else { |
| tool = newSchemaTool(schemaAction); |
| } |
| |
| if (schemaAction.equals(ACTION_ADD) && _conf.getCreateScriptTarget() != null) { |
| tool.setWriter(new PrintWriter(_conf.getCreateScriptTarget())); |
| tool.setIndexes(true); |
| tool.setForeignKeys(true); |
| tool.setSequences(true); |
| } |
| |
| if (schemaAction.equals(ACTION_DROP) && _conf.getDropScriptTarget() != null) { |
| tool.setWriter(new PrintWriter(_conf.getDropScriptTarget())); |
| } |
| |
| // configure the tool with additional settings |
| if (flags != null) { |
| tool.setDropTables(flags.dropTables); |
| tool.setRollbackBeforeDDL(flags.rollbackBeforeDDL); |
| tool.setDropSequences(flags.dropSequences); |
| tool.setWriter(flags.sqlWriter); |
| tool.setOpenJPATables(flags.openjpaTables); |
| tool.setSQLTerminator(flags.sqlTerminator); |
| } |
| |
| switch (schemaAction) { |
| case ACTION_SCRIPT_CREATE: |
| tool.setScriptToExecute(_conf.getCreateScriptSource()); |
| break; |
| case ACTION_SCRIPT_DROP: |
| tool.setScriptToExecute(_conf.getDropScriptSource()); |
| break; |
| case ACTION_SCRIPT_LOAD: |
| tool.setScriptToExecute(_conf.getLoadScriptSource()); |
| break; |
| } |
| |
| tool.setSchemaGroup(getSchemaGroup()); |
| tool.run(); |
| tool.record(); |
| tool.clear(); |
| } |
| } |
| |
| // xml output of schema? |
| if (_schemaWriter != null) { |
| // serialize the planned schema to the stream |
| SchemaSerializer ser = new XMLSchemaSerializer(_conf); |
| ser.addAll(getSchemaGroup()); |
| ser.serialize(_schemaWriter, MetaDataSerializer.PRETTY); |
| _schemaWriter.flush(); |
| } |
| } |
| if (!_flush) |
| return; |
| |
| QueryMetaData[] queries = repos.getQueryMetaDatas(); |
| SequenceMetaData[] seqs = repos.getSequenceMetaDatas(); |
| Map<File, String> output = null; |
| |
| // if we're outputting to stream, set all metas to same file so |
| // they get placed in single string |
| if (_mappingWriter != null) { |
| output = new HashMap<>(); |
| File tmp = new File("openjpatmp"); |
| for (ClassMapping mapping : mappings) { |
| mapping.setSource(tmp, SourceTracker.SRC_OTHER, "openjpatmp"); |
| } |
| for (QueryMetaData query : queries) { |
| query.setSource(tmp, query.getSourceScope(), SourceTracker.SRC_OTHER, "openjpatmp"); |
| } |
| for (SequenceMetaData seq : seqs) |
| seq.setSource(tmp, seq.getSourceScope(), |
| SourceTracker.SRC_OTHER); |
| } |
| |
| // store |
| if (!io.store(mappings, queries, seqs, _mode, output)) |
| throw new MetaDataException(_loc.get("bad-store")); |
| |
| // write to stream |
| if (_mappingWriter != null) { |
| PrintWriter out = new PrintWriter(_mappingWriter); |
| for (String s : output.values()) { |
| out.println(s); |
| } |
| out.flush(); |
| } |
| } |
| catch (RuntimeException re) { |
| throw re; |
| } catch (Exception e) { |
| throw new GeneralException(e); |
| } finally { |
| clear(); |
| } |
| } |
| |
| /** |
| * Drops schema components that appear to be unused from the local |
| * copy of the schema group. |
| */ |
| private void dropUnusedSchemaComponents(ClassMapping[] mappings) { |
| FieldMapping[] fields; |
| for (ClassMapping mapping : mappings) { |
| mapping.refSchemaComponents(); |
| mapping.getDiscriminator().refSchemaComponents(); |
| mapping.getVersion().refSchemaComponents(); |
| fields = mapping.getDefinedFieldMappings(); |
| for (FieldMapping field : fields) { |
| field.refSchemaComponents(); |
| } |
| } |
| |
| // also allow the dbdictionary to ref any schema components that |
| // it adds apart from mappings |
| SchemaGroup group = getSchemaGroup(); |
| Schema[] schemas = group.getSchemas(); |
| Table[] tables; |
| for (Schema schema : schemas) { |
| tables = schema.getTables(); |
| for (Table table : tables) { |
| _dict.refSchemaComponents(table); |
| } |
| } |
| |
| group.removeUnusedComponents(); |
| } |
| |
| /** |
| * Add tables used by sequences to the given schema. |
| */ |
| private void addSequenceComponents(ClassMapping[] mappings) { |
| SchemaGroup group = getSchemaGroup(); |
| for (ClassMapping mapping : mappings) { |
| addSequenceComponents(mapping, group); |
| } |
| } |
| |
| /** |
| * Add tables used by sequences to the given schema. |
| */ |
| private void addSequenceComponents(ClassMapping mapping, |
| SchemaGroup group) { |
| SequenceMetaData smd = mapping.getIdentitySequenceMetaData(); |
| Seq seq = null; |
| if (smd != null) |
| seq = smd.getInstance(null); |
| else if (mapping.getIdentityStrategy() == ValueStrategies.NATIVE |
| || (mapping.getIdentityStrategy() == ValueStrategies.NONE |
| && mapping.getIdentityType() == ClassMetaData.ID_DATASTORE)) |
| seq = _conf.getSequenceInstance(); |
| |
| if (seq instanceof JDBCSeq) |
| ((JDBCSeq) seq).addSchema(mapping, group); |
| |
| FieldMapping[] fmds; |
| if (mapping.getEmbeddingMetaData() == null) |
| fmds = mapping.getDefinedFieldMappings(); |
| else |
| fmds = mapping.getFieldMappings(); |
| for (FieldMapping fmd : fmds) { |
| smd = fmd.getValueSequenceMetaData(); |
| if (smd != null) { |
| seq = smd.getInstance(_loader); |
| if (seq instanceof JDBCSeq) |
| ((JDBCSeq) seq).addSchema(mapping, group); |
| } |
| else if (fmd.getEmbeddedMapping() != null) |
| addSequenceComponents(fmd.getEmbeddedMapping(), group); |
| } |
| } |
| |
| /////////// |
| // Actions |
| /////////// |
| |
| /** |
| * Run the configured action on the given instance. |
| */ |
| public void run(Class<?> cls) { |
| if (ACTION_ADD.equals(_action)) { |
| if (_meta) |
| addMeta(cls); |
| else |
| add(cls); |
| } else if (ACTION_REFRESH.equals(_action)) |
| refresh(cls); |
| else if (ACTION_BUILD_SCHEMA.equals(_action)) |
| buildSchema(cls); |
| else if (ACTION_DROP.equals(_action)) |
| drop(cls); |
| else if (ACTION_VALIDATE.equals(_action)) |
| validate(cls); |
| } |
| |
| /** |
| * Add the mapping for the given instance. |
| */ |
| private void add(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| MappingRepository repos = getRepository(); |
| repos.setStrategyInstaller(new MappingStrategyInstaller(repos)); |
| if (getMapping(repos, cls, true) != null) { |
| _flush = true; |
| _flushSchema = true; |
| } |
| } |
| |
| /** |
| * Return the mapping for the given type, or null if the type is |
| * persistence-aware. |
| */ |
| private static ClassMapping getMapping(MappingRepository repos, Class<?> cls, |
| boolean validate) { |
| // this will parse all possible metadata rsrcs looking for cls, so |
| // will detect if p-aware |
| ClassMapping mapping = repos.getMapping(cls, null, false); |
| if (mapping != null) |
| return mapping; |
| if (!validate || cls.isInterface() || repos.skipMetadata(cls) |
| || repos.getPersistenceAware(cls) != null) |
| return null; |
| throw new MetaDataException(_loc.get("no-meta", cls, cls.getClassLoader())); |
| } |
| |
| /** |
| * Create a metadata for the given instance. |
| */ |
| private void addMeta(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| _flush = true; |
| MappingRepository repos = getRepository(); |
| repos.setResolve(MODE_MAPPING, false); |
| MetaDataFactory factory = repos.getMetaDataFactory(); |
| factory.getDefaults().setIgnoreNonPersistent(false); |
| factory.setStoreMode(MetaDataFactory.STORE_VERBOSE); |
| |
| ClassMetaData meta = repos.addMetaData(cls); |
| FieldMetaData[] fmds = meta.getDeclaredFields(); |
| for (FieldMetaData fmd : fmds) { |
| if (fmd.getDeclaredTypeCode() == JavaTypes.OBJECT |
| && fmd.getDeclaredType() != Object.class) |
| fmd.setDeclaredTypeCode(JavaTypes.PC); |
| } |
| meta.setSource(_file, meta.getSourceType(), _file == null ? "": _file.getPath() ); |
| meta.setResolve(MODE_META, true); |
| } |
| |
| /** |
| * Refresh or add the mapping for the given instance. |
| */ |
| private void refresh(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| MappingRepository repos = getRepository(); |
| repos.setStrategyInstaller(new RefreshStrategyInstaller(repos)); |
| if (getMapping(repos, cls, true) != null) { |
| _flush = true; |
| _flushSchema = true; |
| } |
| } |
| |
| /** |
| * Validate the mappings for the given class and its fields. |
| */ |
| private void validate(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| MappingRepository repos = getRepository(); |
| repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos)); |
| if (getMapping(repos, cls, true) != null) |
| _flushSchema = !contains(_schemaActions,SCHEMA_ACTION_NONE) |
| && !contains(_schemaActions,SchemaTool.ACTION_ADD); |
| } |
| |
| /** |
| * Create the schema using the mapping for the given instance. |
| */ |
| private void buildSchema(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| MappingRepository repos = getRepository(); |
| repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos)); |
| if (getMapping(repos, cls, true) == null) |
| return; |
| |
| // set any logical pks to non-logical so they get flushed |
| _flushSchema = true; |
| Schema[] schemas = _schema.getSchemas(); |
| Table[] tables; |
| Column[] cols; |
| for (Schema schema : schemas) { |
| tables = schema.getTables(); |
| for (Table table : tables) { |
| if (table.getPrimaryKey() == null) |
| continue; |
| |
| table.getPrimaryKey().setLogical(false); |
| cols = table.getPrimaryKey().getColumns(); |
| for (Column col : cols) { |
| col.setNotNull(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Drop mapping for given class. |
| */ |
| private void drop(Class<?> cls) { |
| if (cls == null) |
| return; |
| |
| if (_dropCls == null) |
| _dropCls = new HashSet<>(); |
| _dropCls.add(cls); |
| if (!contains(_schemaActions,SchemaTool.ACTION_DROP)) |
| return; |
| |
| MappingRepository repos = getRepository(); |
| repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos)); |
| ClassMapping mapping = null; |
| try { |
| mapping = repos.getMapping(cls, null, false); |
| } catch (Exception e) { |
| } |
| |
| if (mapping != null) { |
| _flushSchema = true; |
| if (_dropMap == null) |
| _dropMap = new HashSet<>(); |
| _dropMap.add(mapping); |
| } else |
| _log.warn(_loc.get("no-drop-meta", cls)); |
| } |
| |
| //////// |
| // Main |
| //////// |
| |
| /** |
| * Usage: java org.apache.openjpa.jdbc.meta.MappingTool [option]* |
| * [-action/-a <refresh | add | buildSchema | drop | validate | import |
| * | export>] <class name | .java file | .class file | .jdo file>* |
| * 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 as outlined in {@link OpenJPAConfiguration}. 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>-file/-f <stdout | output file or resource></i>: Use |
| * this option to write the planned mappings to an XML document rather |
| * than store them in the repository. This option also specifies the |
| * metadata file to write to if using the <code>add</code> action with |
| * the <code>-meta true</code> flag, or the file to dump to if using |
| * the <code>export</code> action.</li> |
| * <li><i>-schemaAction/-sa <schema action | none></i>: The |
| * {@link SchemaTool} defines the actions possible. The actions will |
| * apply to all schema components used by the mappings involved. |
| * Unless you are running the mapping tool on all of your persistent |
| * types at once, be careful running schema actions that can drop data. |
| * It is possible to accidentally drop schema components that are |
| * used by classes you aren't currently running the tool over. The |
| * action defaults to <code>add</code>.</li> |
| * <li><i>-schemaFile/-sf <stdout | output file or resource></i>: Use |
| * this option to write the planned schema to an XML document rather |
| * than modify the data store.</li> |
| * <li><i>-sqlFile/-sql <stdout | output file or resource></i>: Use |
| * this option to write the planned schema changes as a SQL |
| * script rather than modifying the data store.</li> |
| * <li><i>-sqlEncode/-se <encoding></i>: Use |
| * this option with the <code>-sqlFile</code> flag to write the SQL script |
| * in a different Java character set encoding than the default JVM locale, |
| * such as <code>UTF-8</code>.</li> |
| * <li><i>-dropTables/-dt <true/t | false/f></i>: Corresponds to the |
| * same-named option in the {@link SchemaTool}.</li> |
| * <li><i>-dropSequences/-dsq <true/t | false/f></i>: Corresponds |
| * to the same-named option in the {@link SchemaTool}.</li> |
| * <li><i>-openjpaTables/-kt <true/t | false/f></i>: Corresponds to |
| * the same-named option in the {@link SchemaTool}.</li> |
| * <li><i>-ignoreErrors/-i <true/t | false/f></i>: Corresponds to the |
| * same-named option in the {@link SchemaTool}.</li> |
| * <li><i>-readSchema/-rs <true/t | false/f></i>: Set this to true |
| * to read the entire existing schema (even when false the parts of |
| * the schema used by classes the tool is run on will still be read). |
| * Turning on schema reading can ensure that no naming conflicts will |
| * occur, but it can take a long time.</li> |
| * <li><i>-primaryKeys/-pk <true/t | false/f></i>: Whether primary |
| * keys on existing tables are manipulated. Defaults to false.</li> |
| * <li><i>-foreignKeys/-fk <true/t | false/f></i>: Whether foreign |
| * keys on existing tables are manipulated. Defaults to false.</li> |
| * <li><i>-indexes/-ix <true/t | false/f></i>: Whether indexes on |
| * existing tables are manipulated. Defaults to false.</li> |
| * <li><i>-sequences/-sq <true/t | false/f></i>: Whether sequences |
| * are manipulated. Defaults to true.</li> |
| * <li><i>-schemas/-s <schema and table names></i>: A list of schemas |
| * and/or tables to read. Corresponds to the |
| * same-named option in the {@link SchemaGenerator}. This option |
| * is ignored if <code>readSchema</code> is false.</li> |
| * <li><i>-meta/-m <true/t | false/f></i>: Whether the given action |
| * applies to metadata as well as mappings.</li> |
| * </ul> |
| * The various actions are as follows. |
| * <ul> |
| * <li><i>refresh</i>: Bring the mapping information up-to-date |
| * with the class definitions. OpenJPA will attempt to use any provided |
| * mapping information, and fill in missing information. If the |
| * provided information conflicts with the class definition, the |
| * conflicting information will be discarded and the class/field will |
| * be re-mapped to new columns/tables. This is the default action.</li> |
| * <li><i>add</i>: If used with the <code>-meta</code> option, adds new |
| * default metadata for the given class(es). Otherwise, brings the |
| * mapping information up-to-date with the class |
| * definitions. OpenJPA will attempt to use any provided mapping |
| * information, and fill in missing information. OpenJPA will fail if |
| * the provided information conflicts with the class definition.</li> |
| * <li><i>buildSchema</i>: Create the schema matching the existing |
| * mappings for the given class(es). Any invalid mapping information |
| * will cause an exception.</li> |
| * <li><i>drop</i>: Delete mappings for the given classes. If used with |
| * the <code>-meta</code> option, also deletes metadata.</li> |
| * <li><i>validate</i>: Validate the given mappings. The mapping |
| * repository and schema will not be affected.</li> |
| * <li><i>import</i>: Import mappings from an XML document and store |
| * them as the current system mappings.</li> |
| * <li><i>export</i>: Dump the current mappings for the given classes to |
| * an XML document specified by the <code>file</code> option.</li> |
| * If used with the <code>-meta</code> option, the metadata will be |
| * included in the export. |
| * </ul> |
| * Each class supplied as an argument must have valid metadata. If |
| * no class arguments are given, the tool runs on all metadata files in |
| * the CLASSPATH. |
| * Examples: |
| * <ul> |
| * <li>Refresh the mappings for given package, without dropping any |
| * schema components:<br /> |
| * <code>java org.apache.openjpa.jdbc.meta.MappingTool |
| * mypackage.jdo</code></li> |
| * <li>Refresh the mappings for all persistent classes in the classpath, |
| * dropping any unused columns and even tables:<br /> |
| * <code>java org.apache.openjpa.jdbc.meta.MappingTool -sa refresh |
| * -dt true</code></li> |
| * <li>Make sure the mappings you've created by hand match the object |
| * model and schema:<br /> |
| * <code>java org.apache.openjpa.jbdc.meta.MappingTool |
| * -a validate Person.java</code></li> |
| * <li>Remove the recorded mapping for a given class:<br /> |
| * <code>java org.apache.openjpa.jbdc.meta.MappingTool |
| * -a drop Person.java</code></li> |
| * <li>Record the current mappings in an XML file:<br /> |
| * <code>java org.apache.openjpa.jdbc.meta.MappingTool |
| * -f mypackage.orm -a export mypackage.jdo</code></li> |
| * </ul> |
| */ |
| public static void main(String[] arguments) |
| throws IOException, SQLException { |
| Options opts = new Options(); |
| final String[] args = opts.setFromCmdLine(arguments); |
| boolean ret = Configurations.runAgainstAllAnchors(opts, |
| new Configurations.Runnable() { |
| @Override |
| public boolean run(Options opts) throws IOException, SQLException { |
| JDBCConfiguration conf = new JDBCConfigurationImpl(); |
| try { |
| return MappingTool.run(conf, args, opts, null); |
| } finally { |
| conf.close(); |
| } |
| } |
| }); |
| if (!ret) { |
| // START - ALLOW PRINT STATEMENTS |
| System.err.println(_loc.get("tool-usage")); |
| // STOP - ALLOW PRINT STATEMENTS |
| } |
| } |
| |
| /** |
| * Run the tool. Returns false if invalid options are given. |
| * |
| * @see #main |
| */ |
| public static boolean run(JDBCConfiguration conf, String[] args, |
| Options opts, ClassLoader loader) |
| throws IOException, SQLException { |
| // flags |
| Flags flags = new Flags(); |
| flags.action = opts.removeProperty("action", "a", flags.action); |
| flags.schemaAction = opts.removeProperty("schemaAction", "sa", |
| flags.schemaAction); |
| flags.dropTables = opts.removeBooleanProperty |
| ("dropTables", "dt", flags.dropTables); |
| flags.rollbackBeforeDDL = opts.removeBooleanProperty |
| ("rollbackBeforeDDL", "rbddl", flags.rollbackBeforeDDL); |
| flags.openjpaTables = opts.removeBooleanProperty |
| ("openjpaTables", "ot", flags.openjpaTables); |
| flags.dropSequences = opts.removeBooleanProperty |
| ("dropSequences", "dsq", flags.dropSequences); |
| flags.readSchema = opts.removeBooleanProperty |
| ("readSchema", "rs", flags.readSchema); |
| flags.primaryKeys = opts.removeBooleanProperty |
| ("primaryKeys", "pk", flags.primaryKeys); |
| flags.indexes = opts.removeBooleanProperty("indexes", "ix", |
| flags.indexes); |
| flags.foreignKeys = opts.removeBooleanProperty("foreignKeys", "fk", |
| flags.foreignKeys); |
| flags.sequences = opts.removeBooleanProperty("sequences", "sq", |
| flags.sequences); |
| flags.ignoreErrors = opts.removeBooleanProperty |
| ("ignoreErrors", "i", flags.ignoreErrors); |
| flags.meta = opts.removeBooleanProperty("meta", "m", flags.meta); |
| String fileName = opts.removeProperty("file", "f", null); |
| String schemaFileName = opts.removeProperty("schemaFile", "sf", null); |
| String sqlFileName = opts.removeProperty("sqlFile", "sql", null); |
| String sqlEncoding = opts.removeProperty("sqlEncode", "se", null); |
| String sqlTerminator = opts.removeProperty("sqlTerminator", "st", ";"); |
| String schemas = opts.removeProperty("s"); |
| if (schemas != null) |
| opts.setProperty("schemas", schemas); |
| |
| Configurations.populateConfiguration(conf, opts); |
| if (flags.meta && ACTION_ADD.equals(flags.action)) |
| flags.metaDataFile = Files.getFile(fileName, loader); |
| else |
| flags.mappingWriter = Files.getWriter(fileName, loader); |
| flags.schemaWriter = Files.getWriter(schemaFileName, loader); |
| if (sqlEncoding != null) |
| flags.sqlWriter = Files.getWriter(sqlFileName, loader, sqlEncoding); |
| else |
| flags.sqlWriter = Files.getWriter(sqlFileName, loader); |
| flags.sqlTerminator = sqlTerminator; |
| return run(conf, args, flags, loader); |
| } |
| |
| /** |
| * Run the tool. Return false if an invalid option was given. |
| */ |
| public static boolean run(JDBCConfiguration conf, String[] args, |
| Flags flags, ClassLoader loader) |
| throws IOException, SQLException { |
| // default action based on whether the mapping defaults fills in |
| // missing info |
| if (flags.action == null) { |
| if (conf.getMappingDefaultsInstance().defaultMissingInfo()) |
| flags.action = ACTION_BUILD_SCHEMA; |
| else |
| flags.action = ACTION_REFRESH; |
| } |
| |
| // collect the classes to act on |
| Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL); |
| Collection<Class<?>> classes = null; |
| if (args.length == 0) { |
| if (ACTION_IMPORT.equals(flags.action)) |
| return false; |
| log.info(_loc.get("running-all-classes")); |
| classes = conf.getMappingRepositoryInstance(). |
| loadPersistentTypes(true, loader); |
| } else { |
| classes = new HashSet<>(); |
| ClassArgParser classParser = conf.getMetaDataRepositoryInstance(). |
| getMetaDataFactory().newClassArgParser(); |
| classParser.setClassLoader(loader); |
| Class<?>[] parsed; |
| for (String arg : args) { |
| parsed = classParser.parseTypes(arg); |
| classes.addAll(Arrays.asList(parsed)); |
| } |
| } |
| |
| Class<?>[] act = (Class[]) classes.toArray(new Class[classes.size()]); |
| if (ACTION_EXPORT.equals(flags.action)) { |
| // run exports until the first export succeeds |
| ImportExport[] instances = newImportExports(); |
| for (ImportExport instance : instances) { |
| if (instance.exportMappings(conf, act, flags.meta, log, |
| flags.mappingWriter)) |
| return true; |
| } |
| return false; |
| } |
| if (ACTION_IMPORT.equals(flags.action)) { |
| // run exports until the first export succeeds |
| ImportExport[] instances = newImportExports(); |
| for (ImportExport instance : instances) { |
| if (instance.importMappings(conf, act, args, flags.meta, |
| log, loader)) |
| return true; |
| } |
| return false; |
| } |
| |
| MappingTool tool; |
| try { |
| tool = new MappingTool(conf, flags.action, flags.meta, loader); |
| } catch (IllegalArgumentException iae) { |
| return false; |
| } |
| |
| // setup the tool |
| tool.setIgnoreErrors(flags.ignoreErrors); |
| tool.setMetaDataFile(flags.metaDataFile); |
| tool.setMappingWriter(flags.mappingWriter); |
| tool.setSchemaAction(flags.schemaAction); |
| tool.setSchemaWriter(flags.schemaWriter); |
| tool.setReadSchema(flags.readSchema |
| && !ACTION_VALIDATE.equals(flags.action)); |
| tool.setPrimaryKeys(flags.primaryKeys); |
| tool.setForeignKeys(flags.foreignKeys); |
| tool.setIndexes(flags.indexes); |
| tool.setSequences(flags.sequences || flags.dropSequences); |
| tool.setRollbackBeforeDDL(flags.rollbackBeforeDDL); |
| |
| // and run the action |
| for (int i = 0; i < act.length; i++) { |
| log.info(_loc.get("tool-running", act[i], flags.action)); |
| if (i == 0 && flags.readSchema) |
| log.info(_loc.get("tool-time")); |
| tool.run(act[i]); |
| } |
| log.info(_loc.get("tool-record")); |
| tool.record(flags); |
| return true; |
| } |
| |
| /** |
| * Create an {@link ImportExport} instance. |
| */ |
| private static ImportExport[] newImportExports() { |
| try { |
| Class<?>[] types = Services.getImplementorClasses(ImportExport.class); |
| ImportExport[] instances = new ImportExport[types.length]; |
| for (int i = 0; i < types.length; i++) |
| instances[i] = (ImportExport) AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction(types[i])); |
| return instances; |
| } catch (Throwable t) { |
| if (t instanceof PrivilegedActionException) |
| t = ((PrivilegedActionException) t).getException(); |
| throw new InternalException(_loc.get("importexport-instantiate"),t); |
| } |
| } |
| |
| private static boolean contains(String list, String key) { |
| return (list == null) ? false : list.indexOf(key) != -1; |
| } |
| |
| /** |
| * Run flags. |
| */ |
| public static class Flags { |
| |
| public String action = null; |
| public boolean meta = false; |
| public String schemaAction = SchemaTool.ACTION_ADD; |
| public File metaDataFile = null; |
| public Writer mappingWriter = null; |
| public Writer schemaWriter = null; |
| public Writer sqlWriter = null; |
| public boolean ignoreErrors = false; |
| public boolean readSchema = false; |
| public boolean dropTables = false; |
| public boolean rollbackBeforeDDL = false; |
| public boolean openjpaTables = false; |
| public boolean dropSequences = false; |
| public boolean sequences = true; |
| public boolean primaryKeys = false; |
| public boolean foreignKeys = false; |
| public boolean indexes = false; |
| public String sqlTerminator = ";"; |
| } |
| |
| /** |
| * Helper used to import and export mapping data. |
| */ |
| public interface ImportExport { |
| |
| /** |
| * Import mappings for the given classes based on the given arguments. |
| */ |
| boolean importMappings(JDBCConfiguration conf, Class<?>[] act, |
| String[] args, boolean meta, Log log, ClassLoader loader) |
| throws IOException; |
| |
| /** |
| * Export mappings for the given classes based on the given arguments. |
| */ |
| boolean exportMappings(JDBCConfiguration conf, Class<?>[] act, |
| boolean meta, Log log, Writer writer) |
| throws IOException; |
| } |
| } |