blob: e92907fccac7be969e6254d398ae8073ca05d519 [file] [log] [blame]
/*
* 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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.ApplicationIdTool;
import org.apache.openjpa.enhance.CodeGenerator;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedBlobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedClobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.PrimitiveFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.
RelationCollectionInverseKeyFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationCollectionTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.StateComparisonVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.StringFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.SubclassJoinDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
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.SchemaParser;
import org.apache.openjpa.jdbc.schema.Schemas;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.schema.XMLSchemaParser;
import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.CodeFormat;
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.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.NoneMetaDataFactory;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import serp.bytecode.BCClass;
import serp.bytecode.BCClassLoader;
import serp.bytecode.Project;
/**
* Reverse-maps a schema into class mappings and the associated java
* code. Generates a Java code files for persistent classes and associated
* identity classes and metadata.
*
* @author Abe White
*/
public class ReverseMappingTool
implements MetaDataModes, Cloneable {
/**
* Unmapped table.
*/
public static final int TABLE_NONE = 0;
/**
* Primary table for a new base class.
*/
public static final int TABLE_BASE = 1;
/**
* Secondary table of an existing class. There is exactly one row in
* this table for each row in the primary table.
*/
public static final int TABLE_SECONDARY = 2;
/**
* Secondary table of an existing class. There is zero or one row in
* this table for each row in the primary table.
*/
public static final int TABLE_SECONDARY_OUTER = 3;
/**
* Association table.
*/
public static final int TABLE_ASSOCIATION = 4;
/**
* Subclass table.
*/
public static final int TABLE_SUBCLASS = 5;
public static final String LEVEL_NONE = "none";
public static final String LEVEL_PACKAGE = "package";
public static final String LEVEL_CLASS = "class";
/**
* Access type for generated source, defaults to field-based access.
*/
public static final String ACCESS_TYPE_FIELD = "field";
public static final String ACCESS_TYPE_PROPERTY = "property";
private static Localizer _loc = Localizer.forPackage
(ReverseMappingTool.class);
// map java keywords to related words to use in their place
private static final Map _javaKeywords = new HashMap();
static {
InputStream in = ReverseMappingTool.class.getResourceAsStream
("java-keywords.rsrc");
try {
String[] keywords = StringUtil.split(new BufferedReader
(new InputStreamReader(in)).readLine(), ",", 0);
for (int i = 0; i < keywords.length; i += 2)
_javaKeywords.put(keywords[i], keywords[i + 1]);
} catch (IOException ioe) {
throw new InternalException(ioe);
} finally {
try {
in.close();
} catch (IOException e) {
}
}
}
private final JDBCConfiguration _conf;
private final Log _log;
private final Map _tables = new HashMap();
private final Project _project = new Project();
private final BCClassLoader _loader = AccessController
.doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(_project));
private StrategyInstaller _strat = null;
private String _package = null;
private File _dir = null;
private MappingRepository _repos = null;
private SchemaGroup _schema = null;
private boolean _nullAsObj = false;
private boolean _blobAsObj = false;
private boolean _useGenericColl = false;
private Properties _typeMap = null;
private boolean _useFK = false;
private boolean _useSchema = false;
private boolean _pkOnJoin = false;
private boolean _datastore = false;
private boolean _builtin = true;
private boolean _inner = false;
private String _idSuffix = "Id";
private boolean _inverse = true;
private boolean _detachable = false;
private boolean _genAnnotations = false;
private String _accessType = ACCESS_TYPE_FIELD;
private CodeFormat _format = null;
private ReverseCustomizer _custom = null;
private String _discStrat = null;
private String _versStrat = null;
private boolean _useSchemaElement = true;
// we have to track field names that were created but then abandoned by
// the customizer so that we don't attempt to use them again; doing so can
// mess up certain customizers (bug 881)
private Set _abandonedFieldNames = null;
// generated annotations, key = metadata, val = list of annotations
private Map _annos = null;
/**
* Constructor. Supply configuration.
*/
public ReverseMappingTool(JDBCConfiguration conf) {
_conf = conf;
_log = conf.getLog(OpenJPAConfiguration.LOG_METADATA);
}
/**
* Return the internal strategy installer.
*/
private StrategyInstaller getStrategyInstaller() {
if (_strat == null)
_strat = new ReverseStrategyInstaller(getRepository());
return _strat;
}
/**
* Return the configuration provided on construction.
*/
public JDBCConfiguration getConfiguration() {
return _conf;
}
/**
* Return the log to write to.
*/
public Log getLog() {
return _log;
}
/**
* Return the default package for the generated classes, or null if unset.
*/
public String getPackageName() {
return _package;
}
/**
* Set the default package for the generated classes; use null to
* indicate no package.
*/
public void setPackageName(String packageName) {
_package = StringUtil.trimToNull(packageName);
}
/**
* The file to output the generated code to, or null for
* the current directory. If the directory matches the package, it will
* be used. Otherwise, the package structure will be created under
* this directory.
*/
public File getDirectory() {
return _dir;
}
/**
* The file to output the generated code to, or null for
* the current directory. If the directory matches the package, it will
* be used. Otherwise, the package structure will be created under
* this directory.
*/
public void setDirectory(File dir) {
_dir = dir;
}
/**
* Return true if the schema name will be included in the generated
* class name for each table. Defaults to false.
*/
public boolean getUseSchemaName() {
return _useSchema;
}
/**
* Set whether the schema name will be included in the generated
* class name for each table. Defaults to false.
*/
public void setUseSchemaName(boolean useSchema) {
_useSchema = useSchema;
}
/**
* Return whether the foreign key name will be used to generate
* relation names. Defaults to false.
*/
public boolean getUseForeignKeyName() {
return _useFK;
}
/**
* Set whether the foreign key name will be used to generate
* relation names. Defaults to false.
*/
public void setUseForeignKeyName(boolean useFK) {
_useFK = useFK;
}
/**
* Return whether even nullable columns will be mapped to wrappers
* rather than primitives. Defaults to false.
*/
public boolean getNullableAsObject() {
return _nullAsObj;
}
/**
* Set whether even nullable columns will be mapped to wrappers
* rather than primitives. Defaults to false.
*/
public void setNullableAsObject(boolean nullAsObj) {
_nullAsObj = nullAsObj;
}
/**
* Whether to reverse-map blob columns as containing serialized Java
* objects, rather than the default of using a byte[] field.
*/
public boolean getBlobAsObject() {
return _blobAsObj;
}
/**
* Whether to reverse-map blob columns as containing serialized Java
* objects, rather than the default of using a byte[] field.
*/
public void setBlobAsObject(boolean blobAsObj) {
_blobAsObj = blobAsObj;
}
/**
* Whether to use generic collections on one-to-many and many-to-many
* relations instead of untyped collections.
*/
public boolean getUseGenericCollections() {
return _useGenericColl;
}
/**
* Whether to use generic collections on one-to-many and many-to-many
* relations instead of untyped collections.
*/
public void setUseGenericCollections(boolean useGenericCollections) {
_useGenericColl = useGenericCollections;
}
/**
* Map of JDBC-name to Java-type-name entries that allows customization
* of reverse mapping columns to field types.
*/
public Properties getTypeMap() {
return _typeMap;
}
/**
* Map of JDBC-name to Java-type-name entries that allows customization
* of reverse mapping columns to field types.
*/
public void setTypeMap(Properties typeMap) {
_typeMap = typeMap;
}
/**
* Return true if join tables are allowed to have primary keys, false
* if all primary key tables will be mapped as persistent classes.
* Defaults to false.
*/
public boolean getPrimaryKeyOnJoin() {
return _pkOnJoin;
}
/**
* Set to true if join tables are allowed to have primary keys, false
* if all primary key tables will be mapped as persistent classes.
* Defaults to false.
*/
public void setPrimaryKeyOnJoin(boolean pkOnJoin) {
_pkOnJoin = pkOnJoin;
}
/**
* Whether to use datastore identity when possible. Defaults to false.
*/
public boolean getUseDataStoreIdentity() {
return _datastore;
}
/**
* Whether to use datastore identity when possible. Defaults to false.
*/
public void setUseDataStoreIdentity(boolean datastore) {
_datastore = datastore;
}
/**
* Whether to use built in identity classes when possible. Defaults to true.
*/
public boolean getUseBuiltinIdentityClass() {
return _builtin;
}
/**
* Whether to use built in identity classes when possible. Defaults to true.
*/
public void setUseBuiltinIdentityClass(boolean builtin) {
_builtin = builtin;
}
/**
* Whether or not to generate inner classes when creating application
* identity classes.
*/
public boolean getInnerIdentityClasses() {
return _inner;
}
/**
* Whether or not to generate inner classes when creating application
* identity classes.
*/
public void setInnerIdentityClasses(boolean inner) {
_inner = inner;
}
/**
* Suffix used to create application identity class name from a class name,
* or in the case of inner identity classes, the inner class name.
*/
public String getIdentityClassSuffix() {
return _idSuffix;
}
/**
* Suffix used to create application identity class name from a class name,
* or in the case of inner identity classes, the inner class name.
*/
public void setIdentityClassSuffix(String suffix) {
_idSuffix = suffix;
}
/**
* Whether to generate inverse 1-many/1-1 relations for all many-1/1-1
* relations. Defaults to true.
*/
public boolean getInverseRelations() {
return _inverse;
}
/**
* Whether to generate inverse 1-many/1-1 relations for all many-1/1-1
* relations. Defaults to true.
*/
public void setInverseRelations(boolean inverse) {
_inverse = inverse;
}
/**
* Whether to make generated classes detachable. Defaults to false.
*/
public boolean getDetachable() {
return _detachable;
}
/**
* Whether to make generated classes detachable. Defaults to false.
*/
public void setDetachable(boolean detachable) {
_detachable = detachable;
}
/**
* Default discriminator strategy for base class mappings.
*/
public String getDiscriminatorStrategy() {
return _discStrat;
}
/**
* Default discriminator strategy for base class mappings.
*/
public void setDiscriminatorStrategy(String discStrat) {
_discStrat = discStrat;
}
/**
* Default version strategy for base class mappings.
*/
public String getVersionStrategy() {
return _versStrat;
}
/**
* Default version strategy for base class mappings.
*/
public void setVersionStrategy(String versionStrat) {
_versStrat = versionStrat;
}
/**
* Whether to generate annotations along with generated code. Defaults
* to false.
*/
public boolean getGenerateAnnotations() {
return _genAnnotations;
}
/**
* Whether to generate annotations along with generated code. Defaults
* to false.
*/
public void setGenerateAnnotations(boolean genAnnotations) {
_genAnnotations = genAnnotations;
}
/**
* Whether to use field or property-based access on generated code.
* Defaults to field-based access.
*/
public String getAccessType() {
return _accessType;
}
/**
* Whether to use field or property-based access on generated code.
* Defaults to field-based access.
*/
public void setAccessType(String accessType) {
this._accessType = ACCESS_TYPE_PROPERTY.equalsIgnoreCase(accessType) ?
ACCESS_TYPE_PROPERTY : ACCESS_TYPE_FIELD;
}
/**
* The code formatter for the generated Java code.
*/
public CodeFormat getCodeFormat() {
return _format;
}
/**
* Set the code formatter for the generated Java code.
*/
public void setCodeFormat(CodeFormat format) {
_format = format;
}
/**
* Return the customizer in use, or null if none.
*/
public ReverseCustomizer getCustomizer() {
return _custom;
}
/**
* Set the customizer. The configuration on the customizer, if any,
* should already be set.
*/
public void setCustomizer(ReverseCustomizer customizer) {
if (customizer != null)
customizer.setTool(this);
_custom = customizer;
}
/**
* Returns whether or not the schema name will be included in the @Table
* annotation within the generated class for each table, as well as the
* corresponding XML mapping files. The initialized value is true (in order
* to preserve backwards compatibility).
*/
public boolean getUseSchemaElement() {
return _useSchemaElement;
}
/**
* Sets whether or not the schema name will be included in the @Table
* annotation within the generated class for each table, as well as the
* corresponding XML mapping files.
*/
public void setUseSchemaElement(boolean useSchemaElement) {
_useSchemaElement = useSchemaElement;
}
/**
* Return the mapping repository used to hold generated mappings. You
* can also use the repository to seed the schema group to generate
* classes from.
*/
public MappingRepository getRepository() {
if (_repos == null) {
// create empty repository
_repos = _conf.newMappingRepositoryInstance();
_repos.setMetaDataFactory(NoneMetaDataFactory.getInstance());
_repos.setMappingDefaults(NoneMappingDefaults.getInstance());
_repos.setResolve(MODE_NONE);
_repos.setValidate(MetaDataRepository.VALIDATE_NONE);
}
return _repos;
}
/**
* Set the repository to use.
*/
public void setRepository(MappingRepository repos) {
_repos = repos;
}
/**
* Return the schema group to reverse map. If none has been set, the
* schema will be generated from the database.
*/
public SchemaGroup getSchemaGroup() {
if (_schema == null) {
SchemaGenerator gen = new SchemaGenerator(_conf);
try {
gen.generateSchemas();
} catch (SQLException se) {
throw SQLExceptions.getStore(se,
_conf.getDBDictionaryInstance());
}
_schema = gen.getSchemaGroup();
}
return _schema;
}
/**
* Set the schema to reverse map.
*/
public void setSchemaGroup(SchemaGroup schema) {
_schema = schema;
}
/**
* Return the generated mappings.
*/
public ClassMapping[] getMappings() {
return getRepository().getMappings();
}
/**
* Generate mappings and class code for the current schema group.
*/
public void run() {
// map base classes first
Schema[] schemas = getSchemaGroup().getSchemas();
Table[] tables;
for (Schema schema2 : schemas) {
tables = schema2.getTables();
for (Table table : tables)
if (isBaseTable(table))
mapBaseClass(table);
}
// map vertical subclasses
Set subs = null;
for (Schema schema1 : schemas) {
tables = schema1.getTables();
for (Table table : tables) {
if (!_tables.containsKey(table)
&& getSecondaryType(table, false) == TABLE_SUBCLASS) {
if (subs == null)
subs = new HashSet();
subs.add(table);
}
}
}
if (subs != null)
mapSubclasses(subs);
// map fields in the primary tables of the classes
ClassMapping cls;
for (Object o1 : _tables.values()) {
cls = (ClassMapping) o1;
mapColumns(cls, cls.getTable(), null, false);
}
// map association tables, join tables, and secondary tables
for (Schema element : schemas) {
tables = element.getTables();
for (Table table : tables)
if (!_tables.containsKey(table))
mapTable(table, getSecondaryType(table, false));
}
// map discriminators and versions, make sure identity type is correct,
// set simple field column java types, and ref schema components so
// we can tell what is unmapped
FieldMapping[] fields;
for (Object item : _tables.values()) {
cls = (ClassMapping) item;
cls.refSchemaComponents();
if (cls.getDiscriminator().getStrategy() == null)
getStrategyInstaller().installStrategy
(cls.getDiscriminator());
cls.getDiscriminator().refSchemaComponents();
if (cls.getVersion().getStrategy() == null)
getStrategyInstaller().installStrategy(cls.getVersion());
cls.getVersion().refSchemaComponents();
// double-check identity type; if it was set for builtin identity
// it might have to switch to std application identity if pk field
// not compatible
if (cls.getPCSuperclass() == null
&& cls.getIdentityType() == ClassMetaData.ID_APPLICATION) {
if (cls.getPrimaryKeyFields().length == 0)
throw new MetaDataException(_loc.get("no-pk-fields", cls));
if (cls.getObjectIdType() == null
|| (cls.isOpenJPAIdentity() && !isBuiltinIdentity(cls)))
setObjectIdType(cls);
}
else if (cls.getIdentityType() == ClassMetaData.ID_DATASTORE)
cls.getPrimaryKeyColumns()[0].setJavaType(JavaTypes.LONG);
// set java types for simple fields;
fields = cls.getDeclaredFieldMappings();
for (FieldMapping field : fields) {
field.refSchemaComponents();
setColumnJavaType(field);
setColumnJavaType(field.getElementMapping());
}
}
// set the java types of foreign key columns; we couldn't do this
// earlier because we rely on the linked-to columns to do it
for (Object value : _tables.values()) {
cls = (ClassMapping) value;
setForeignKeyJavaType(cls.getJoinForeignKey());
fields = cls.getDeclaredFieldMappings();
for (FieldMapping field : fields) {
setForeignKeyJavaType(field.getJoinForeignKey());
setForeignKeyJavaType(field.getForeignKey());
setForeignKeyJavaType(field.getElementMapping().
getForeignKey());
}
}
// allow customizer to map unmapped tables, and warn about anything
// that ends up unmapped
Column[] cols;
Collection unmappedCols = new ArrayList(5);
for (Schema schema : schemas) {
tables = schema.getTables();
for (Table table : tables) {
unmappedCols.clear();
cols = table.getColumns();
for (Column col : cols)
if (col.getRefCount() == 0)
unmappedCols.add(col);
if (unmappedCols.size() == cols.length) {
if (_custom == null || !_custom.unmappedTable(table))
_log.info(_loc.get("unmap-table", table));
}
else if (unmappedCols.size() > 0)
_log.info(_loc.get("unmap-cols", table, unmappedCols));
}
}
if (_custom != null)
_custom.close();
// resolve mappings
for (Object o : _tables.values()) {
((ClassMapping) o).resolve(MODE_META | MODE_MAPPING);
}
}
/**
* Map the table of the given type.
*/
private void mapTable(Table table, int type) {
switch (type) {
case TABLE_SECONDARY:
case TABLE_SECONDARY_OUTER:
mapSecondaryTable(table, type != TABLE_SECONDARY);
break;
case TABLE_ASSOCIATION:
mapAssociationTable(table);
break;
}
}
/**
* Return true if the given class is compatible with builtin identity.
*/
private static boolean isBuiltinIdentity(ClassMapping cls) {
FieldMapping[] fields = cls.getPrimaryKeyFieldMappings();
if (fields.length != 1)
return false;
switch (fields[0].getDeclaredTypeCode()) {
case JavaTypes.BYTE:
case JavaTypes.CHAR:
case JavaTypes.INT:
case JavaTypes.LONG:
case JavaTypes.SHORT:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.STRING:
case JavaTypes.OID:
return true;
}
return false;
}
/**
* Set the Java type of the column for the given value.
*/
private static void setColumnJavaType(ValueMapping vm) {
Column[] cols = vm.getColumns();
if (cols.length == 1)
cols[0].setJavaType(vm.getDeclaredTypeCode());
}
/**
* Set the Java type of the foreign key columns.
*/
private static void setForeignKeyJavaType(ForeignKey fk) {
if (fk == null)
return;
Column[] cols = fk.getColumns();
Column[] pks = fk.getPrimaryKeyColumns();
for (int i = 0; i < cols.length; i++)
if (cols[i].getJavaType() == JavaTypes.OBJECT)
cols[i].setJavaType(pks[i].getJavaType());
}
/**
* Uses {@link CodeGenerator}s to write the Java code for the generated
* mappings to the proper packages.
*
* @return a list of {@link File} instances that were written
*/
public List recordCode() throws IOException {
return recordCode(null);
}
/**
* Write the code for the tool.
*
* @param output if null, then perform the write directly
* to the filesystem; otherwise, populate the
* specified map with keys as the generated
* {@link ClassMapping} and values as a
* {@link String} that contains the generated code
* @return a list of {@link File} instances that were written
*/
public List recordCode(Map<Class<?>, String> output)
throws IOException {
List written = new LinkedList();
ClassMapping[] mappings = getMappings();
ReverseCodeGenerator gen;
for (ClassMapping mapping : mappings) {
if (_log.isInfoEnabled())
_log.info(_loc.get("class-code", mapping));
ApplicationIdTool aid = newApplicationIdTool(mapping);
if (getGenerateAnnotations())
gen = new AnnotatedCodeGenerator(mapping, aid);
else
gen = new ReverseCodeGenerator(mapping, aid);
gen.generateCode();
if (output == null) {
gen.writeCode();
written.add(gen.getFile());
if (aid != null && !aid.isInnerClass())
aid.record();
}
else {
StringWriter writer = new StringWriter();
gen.writeCode(writer);
output.put(mapping.getDescribedType(), writer.toString());
if (aid != null && !aid.isInnerClass()) {
writer = new StringWriter();
aid.setWriter(writer);
aid.record();
output.put(mapping.getObjectIdType(),
writer.toString());
}
}
}
return written;
}
/**
* Write the generated metadata to the proper packages.
*
* @return the set of metadata {@link File}s that were written
*/
public Collection recordMetaData(boolean perClass)
throws IOException {
return recordMetaData(perClass, null);
}
/**
* Write the code for the tool.
*
* @param output if null, then perform the write directly
* to the filesystem; otherwise, populate the
* specified map with keys as the generated
* {@link ClassMapping} and values as a
* {@link String} that contains the generated code
* @return the set of metadata {@link File}s that were written
*/
public Collection recordMetaData(boolean perClass, Map output)
throws IOException {
// pretend mappings are all resolved
ClassMapping[] mappings = getMappings();
for (ClassMapping classMapping : mappings) {
classMapping.setResolve(MODE_META | MODE_MAPPING, true);
classMapping.setUseSchemaElement(getUseSchemaElement());
}
// store in user's configured IO
MetaDataFactory mdf = _conf.newMetaDataFactoryInstance();
mdf.setRepository(getRepository());
mdf.setStoreDirectory(_dir);
if (perClass)
mdf.setStoreMode(MetaDataFactory.STORE_PER_CLASS);
mdf.store(mappings, new QueryMetaData[0], new SequenceMetaData[0],
MODE_META | MODE_MAPPING, output);
Set files = new TreeSet();
for (ClassMapping mapping : mappings)
if (mapping.getSourceFile() != null)
files.add(mapping.getSourceFile());
return files;
}
public void buildAnnotations() {
Map output = new HashMap();
// pretend mappings are all resolved
ClassMapping[] mappings = getMappings();
for (ClassMapping mapping : mappings) {
mapping.setResolve(MODE_META | MODE_MAPPING, true);
mapping.setUseSchemaElement(getUseSchemaElement());
}
// store in user's configured IO
MetaDataFactory mdf = _conf.newMetaDataFactoryInstance();
mdf.setRepository(getRepository());
mdf.setStoreDirectory(_dir);
mdf.store(mappings, new QueryMetaData[0], new SequenceMetaData[0],
MODE_META | MODE_MAPPING | MODE_ANN_MAPPING, output);
_annos = output;
}
/**
* Returns a list of stringified annotations for specified meta.
*/
public List getAnnotationsForMeta(Object meta) {
if (null == _annos)
return null;
return (List) _annos.get(meta);
}
/**
* Generate and write the application identity code.
*/
private ApplicationIdTool newApplicationIdTool(ClassMapping mapping) {
ApplicationIdTool tool;
if (mapping.getIdentityType() == ClassMetaData.ID_APPLICATION
&& !mapping.isOpenJPAIdentity()
&& mapping.getPCSuperclass() == null) {
tool = new ApplicationIdTool(_conf, mapping.getDescribedType(),
mapping);
tool.setDirectory(_dir);
tool.setCodeFormat(_format);
if (!tool.run())
return null;
return tool;
}
return null;
}
//////////////////////////////////
// Methods for customizers to use
//////////////////////////////////
/**
* Return the class mapping for the given table, or null if none.
*/
public ClassMapping getClassMapping(Table table) {
return (ClassMapping) _tables.get(table);
}
/**
* Create a new class to be mapped to a table. The class will start out
* with a default application identity class set.
*/
public ClassMapping newClassMapping(Class cls, Table table) {
ClassMapping mapping = (ClassMapping) getRepository().addMetaData(cls);
Class sup = mapping.getDescribedType().getSuperclass();
if (sup == Object.class)
setObjectIdType(mapping);
else
mapping.setPCSuperclass(sup);
mapping.setTable(table);
if (_detachable)
mapping.setDetachable(true);
_tables.put(table, mapping);
return mapping;
}
/**
* Set the given class' objectid-class.
*/
private void setObjectIdType(ClassMapping cls) {
String name = cls.getDescribedType().getName();
if (_inner)
name += "$";
name += _idSuffix;
cls.setObjectIdType(generateClass(name, null), false);
}
/**
* Generate a new class with the given name. If a non-null parent class
* is given, it will be set as the superclass.
*/
public Class generateClass(String name, Class parent) {
BCClass bc = _project.loadClass(name, null);
if (parent != null)
bc.setSuperclass(parent);
bc.addDefaultConstructor();
try {
return Class.forName(name, false, _loader);
} catch (ClassNotFoundException cnfe) {
throw new InternalException(cnfe.toString(), cnfe);
}
}
/**
* Return whether the given foreign key is unique.
*/
public boolean isUnique(ForeignKey fk) {
PrimaryKey pk = fk.getTable().getPrimaryKey();
if (pk != null && pk.columnsMatch(fk.getColumns()))
return true;
Index[] idx = fk.getTable().getIndexes();
for (Index index : idx)
if (index.isUnique() && index.columnsMatch(fk.getColumns()))
return true;
Unique[] unq = fk.getTable().getUniques();
for (Unique unique : unq)
if (unique.columnsMatch(fk.getColumns()))
return true;
return false;
}
/**
* If the given table has a single unique foreign key or a foreign
* key that matches the primary key, return it. Else return null.
*/
public ForeignKey getUniqueForeignKey(Table table) {
ForeignKey[] fks = table.getForeignKeys();
PrimaryKey pk = table.getPrimaryKey();
ForeignKey unq = null;
int count = 0;
for (ForeignKey fk : fks) {
if (pk != null && pk.columnsMatch(fk.getColumns()))
return fk;
if (!isUnique(fk))
continue;
count++;
if (unq == null)
unq = fk;
}
return (count == 1) ? unq : null;
}
/**
* Add existing unique constraints and indexes to the given field's join.
*/
public void addJoinConstraints(FieldMapping field) {
ForeignKey fk = field.getJoinForeignKey();
if (fk == null)
return;
Index idx = findIndex(fk.getColumns());
if (idx != null)
field.setJoinIndex(idx);
Unique unq = findUnique(fk.getColumns());
if (unq != null)
field.setJoinUnique(unq);
}
/**
* Add existing unique constraints and indexes to the given value.
*/
public void addConstraints(ValueMapping vm) {
Column[] cols = (vm.getForeignKey() != null)
? vm.getForeignKey().getColumns() : vm.getColumns();
Index idx = findIndex(cols);
if (idx != null)
vm.setValueIndex(idx);
Unique unq = findUnique(cols);
if (unq != null)
vm.setValueUnique(unq);
}
/**
* Return the index with the given columns.
*/
private Index findIndex(Column[] cols) {
if (cols == null || cols.length == 0)
return null;
Table table = cols[0].getTable();
Index[] idxs = table.getIndexes();
for (Index idx : idxs)
if (idx.columnsMatch(cols))
return idx;
return null;
}
/**
* Return the unique constriant with the given columns.
*/
private Unique findUnique(Column[] cols) {
if (cols == null || cols.length == 0)
return null;
Table table = cols[0].getTable();
Unique[] unqs = table.getUniques();
for (Unique unq : unqs)
if (unq.columnsMatch(cols))
return unq;
return null;
}
/////////////
// Utilities
/////////////
/**
* Return whether the given table is a base class table.
*/
public boolean isBaseTable(Table table) {
if (table.getPrimaryKey() == null)
return false;
int type = getSecondaryType(table, true);
if (type != -1)
return type == TABLE_BASE;
if (_custom != null)
return _custom.getTableType(table, TABLE_BASE) == TABLE_BASE;
return true;
}
/**
* Calculate the type of the secondary given table.
*/
private int getSecondaryType(Table table, boolean maybeBase) {
int type;
ForeignKey[] fks = table.getForeignKeys();
if (fks.length == 2
&& (table.getPrimaryKey() == null || _pkOnJoin)
&& fks[0].getColumns().length + fks[1].getColumns().length
== table.getColumns().length
&& (!isUnique(fks[0]) || !isUnique(fks[1])))
type = TABLE_ASSOCIATION;
else if (maybeBase && table.getPrimaryKey() != null)
type = -1;
else if (getUniqueForeignKey(table) != null)
type = TABLE_SECONDARY;
else if (fks.length == 1)
type = TABLE_NONE;
else
type = -1;
if (_custom != null && type != -1)
type = _custom.getTableType(table, type);
return type;
}
/**
* Attempt to create a base class from the given table.
*/
private void mapBaseClass(Table table) {
ClassMapping cls = newClassMapping(table, null);
if (cls == null)
return;
// check for datastore identity and builtin identity; for now
// we assume that any non-datastore single primary key column will use
// builtin identity; if we discover that the primary key field is
// not compatible with builtin identity later, then we'll assign
// an application identity class
Column[] pks = table.getPrimaryKey().getColumns();
cls.setPrimaryKeyColumns(pks);
if (pks.length == 1 && _datastore
&& pks[0].isCompatible(Types.BIGINT, null, 0, 0)) {
cls.setObjectIdType(null, false);
cls.setIdentityType(ClassMetaData.ID_DATASTORE);
} else if (pks.length == 1 && _builtin)
cls.setObjectIdType(null, false);
cls.setStrategy(new FullClassStrategy(), null);
if (_custom != null)
_custom.customize(cls);
}
/**
* Attempt to create a vertical subclasses from the given tables.
*/
private void mapSubclasses(Set tables) {
// loop through tables until either all are mapped or none link to
// a mapped base class table
ClassMapping base, sub;
Table table = null;
ForeignKey fk = null;
while (!tables.isEmpty()) {
// find a table with a foreign key linking to a mapped table
base = null;
for (Iterator itr = tables.iterator(); itr.hasNext();) {
table = (Table) itr.next();
fk = getUniqueForeignKey(table);
if (fk == null && table.getForeignKeys().length == 1)
fk = table.getForeignKeys()[0];
else if (fk == null)
itr.remove();
else {
base = (ClassMapping) _tables.get(fk.getPrimaryKeyTable());
if (base != null) {
itr.remove();
break;
}
}
}
// if no tables link to a base table, nothing left to do
if (base == null)
return;
sub = newClassMapping(table, base.getDescribedType());
sub.setJoinForeignKey(fk);
sub.setPrimaryKeyColumns(fk.getColumns());
sub.setIdentityType(base.getIdentityType());
sub.setStrategy(new VerticalClassStrategy(), null);
if (_custom != null)
_custom.customize(sub);
}
}
/**
* Attempt to reverse map the given table as an association table.
*/
private void mapAssociationTable(Table table) {
ForeignKey[] fks = table.getForeignKeys();
if (fks.length != 2)
return;
ClassMapping cls1 = (ClassMapping) _tables.get
(fks[0].getPrimaryKeyTable());
ClassMapping cls2 = (ClassMapping) _tables.get
(fks[1].getPrimaryKeyTable());
if (cls1 == null || cls2 == null)
return;
// add a relation from each class to the other through the
// association table
String name = getRelationName(cls2.getDescribedType(), true, fks[1],
false, cls1);
FieldMapping field1 = newFieldMapping(name, Set.class, null, fks[1],
cls1);
if (field1 != null) {
field1.setJoinForeignKey(fks[0]);
addJoinConstraints(field1);
ValueMapping vm = field1.getElementMapping();
vm.setDeclaredType(cls2.getDescribedType());
vm.setForeignKey(fks[1]);
addConstraints(vm);
field1.setStrategy(new RelationCollectionTableFieldStrategy(),
null);
if (_custom != null)
_custom.customize(field1);
}
name = getRelationName(cls1.getDescribedType(), true, fks[0],
false, cls2);
FieldMapping field2 = newFieldMapping(name, Set.class, null, fks[0],
cls2);
if (field2 == null)
return;
field2.setJoinForeignKey(fks[1]);
addJoinConstraints(field2);
ValueMapping vm = field2.getElementMapping();
vm.setDeclaredType(cls1.getDescribedType());
vm.setForeignKey(fks[0]);
addConstraints(vm);
if (field1 != null && field1.getMappedBy() == null)
field2.setMappedBy(field1.getName());
field2.setStrategy(new RelationCollectionTableFieldStrategy(), null);
if (_custom != null)
_custom.customize(field2);
}
/**
* Attempt to reverse map the given table as a secondary table.
*/
private void mapSecondaryTable(Table table, boolean outer) {
ForeignKey fk = getUniqueForeignKey(table);
if (fk == null && table.getForeignKeys().length == 1)
fk = table.getForeignKeys()[0];
else if (fk == null)
return;
ClassMapping cls = (ClassMapping) _tables.get(fk.getPrimaryKeyTable());
if (cls == null)
return;
mapColumns(cls, table, fk, outer);
}
/**
* Map the columns of the given table to fields of the given type.
*/
private void mapColumns(ClassMapping cls, Table table, ForeignKey join,
boolean outer) {
// first map foreign keys to relations
ForeignKey[] fks = table.getForeignKeys();
for (ForeignKey fk : fks)
if (fk != join && fk != cls.getJoinForeignKey())
mapForeignKey(cls, fk, join, outer);
// map any columns not controlled by foreign keys; also force primary
// key cols to get mapped to simple fields even if the columns are
// also foreign key columns
PrimaryKey pk = (join != null) ? null : table.getPrimaryKey();
boolean pkcol;
Column[] cols = table.getColumns();
for (Column col : cols) {
pkcol = pk != null && pk.containsColumn(col);
if (pkcol && cls.getIdentityType() == ClassMetaData.ID_DATASTORE)
continue;
if ((cls.getPCSuperclass() == null && pkcol)
|| !isForeignKeyColumn(col))
mapColumn(cls, col, join, outer);
}
}
/**
* Whether the given column appears in any foreign keys.
*/
private static boolean isForeignKeyColumn(Column col) {
ForeignKey[] fks = col.getTable().getForeignKeys();
for (ForeignKey fk : fks)
if (fk.containsColumn(col))
return true;
return false;
}
/**
* Map a foreign key to a relation.
*/
private void mapForeignKey(ClassMapping cls, ForeignKey fk,
ForeignKey join, boolean outer) {
ClassMapping rel = (ClassMapping) _tables.get(fk.getPrimaryKeyTable());
if (rel == null)
return;
String name = getRelationName(rel.getDescribedType(), false, fk,
false, cls);
FieldMapping field1 = newFieldMapping(name, rel.getDescribedType(),
null, fk, cls);
if (field1 != null) {
field1.setJoinForeignKey(join);
field1.setJoinOuter(outer);
addJoinConstraints(field1);
field1.setForeignKey(fk);
addConstraints(field1);
field1.setStrategy(new RelationFieldStrategy(), null);
if (_custom != null)
_custom.customize(field1);
}
if (!_inverse || join != null)
return;
// create inverse relation
boolean unq = isUnique(fk);
name = getRelationName(cls.getDescribedType(), !unq, fk, true, rel);
Class type = (unq) ? cls.getDescribedType() : Set.class;
FieldMapping field2 = newFieldMapping(name, type, null, fk, rel);
if (field2 == null)
return;
if (field1 != null)
field2.setMappedBy(field1.getName());
if (unq) {
field2.setForeignKey(fk);
field2.setJoinDirection(ValueMapping.JOIN_INVERSE);
field2.setStrategy(new RelationFieldStrategy(), null);
} else {
ValueMapping vm = field2.getElementMapping();
vm.setDeclaredType(cls.getDescribedType());
vm.setForeignKey(fk);
vm.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE);
field2.setStrategy(new RelationCollectionInverseKeyFieldStrategy(),
null);
}
if (_custom != null)
_custom.customize(field2);
}
/**
* Map a column to a simple field.
*/
private void mapColumn(ClassMapping cls, Column col, ForeignKey join,
boolean outer) {
String name = getFieldName(col.getName(), cls);
Class type = getFieldType(col, false);
FieldMapping field = newFieldMapping(name, type, col, null, cls);
field.setSerialized(type == Object.class);
field.setJoinForeignKey(join);
field.setJoinOuter(outer);
addJoinConstraints(field);
field.setColumns(new Column[]{ col });
addConstraints(field);
if (col.isPrimaryKey()
&& cls.getIdentityType() != ClassMetaData.ID_DATASTORE)
field.setPrimaryKey(true);
FieldStrategy strat;
if (type.isPrimitive())
strat = new PrimitiveFieldStrategy();
else if (col.getType() == Types.CLOB
&& _conf.getDBDictionaryInstance().maxEmbeddedClobSize != -1)
strat = new MaxEmbeddedClobFieldStrategy();
else if (col.isLob()
&& _conf.getDBDictionaryInstance().maxEmbeddedBlobSize != -1)
strat = new MaxEmbeddedBlobFieldStrategy();
else if (type == String.class)
strat = new StringFieldStrategy();
else
strat = new HandlerFieldStrategy();
field.setStrategy(strat, null);
if (_custom != null)
_custom.customize(field);
}
/**
* Create a class mapping for the given table, or return null if
* customizer rejects.
*/
private ClassMapping newClassMapping(Table table, Class parent) {
String name = getClassName(table);
if (_custom != null)
name = _custom.getClassName(table, name);
if (name == null)
return null;
return newClassMapping(generateClass(name, parent), table);
}
/**
* Create a field mapping for the given info, or return null if
* customizer rejects.
*/
public FieldMapping newFieldMapping(String name, Class type, Column col,
ForeignKey fk, ClassMapping dec) {
if (_custom != null) {
Column[] cols = (fk == null) ? new Column[]{ col }
: fk.getColumns();
String newName = _custom.getFieldName(dec, cols, fk, name);
if (newName == null || !newName.equals(name)) {
if (_abandonedFieldNames == null)
_abandonedFieldNames = new HashSet();
_abandonedFieldNames.add(dec.getDescribedType().getName()
+ "." + name);
if (newName == null)
return null;
name = newName;
}
}
FieldMapping field = dec.addDeclaredFieldMapping(name, type);
field.setExplicit(true);
return field;
}
/**
* Return a Java identifier-formatted name for the given table
* name, using the default package.
*/
private String getClassName(Table table) {
StringBuilder buf = new StringBuilder();
if (getPackageName() != null)
buf.append(getPackageName()).append(".");
String[] subs;
String name = replaceInvalidCharacters(table.getSchemaName());
if (_useSchema && name != null) {
if (allUpperCase(name))
name = name.toLowerCase();
subs = StringUtil.split(name, "_", 0);
for (String sub : subs) {
buf.append(StringUtil.capitalize(sub));
}
}
name = replaceInvalidCharacters(table.getName());
if (allUpperCase(name))
name = name.toLowerCase();
subs = StringUtil.split(name, "_", 0);
for (int i = 0; i < subs.length; i++) {
// make sure the name can't conflict with generated id class names;
// if the name would end in 'Id', make it end in 'Ident'
if (i == subs.length - 1 && subs[i].equalsIgnoreCase("id"))
subs[i] = "ident";
buf.append(StringUtil.capitalize(subs[i]));
}
return buf.toString();
}
/**
* Return a default Java identifier-formatted name for the given
* column/table name.
*/
public String getFieldName(String name, ClassMapping dec) {
name = replaceInvalidCharacters(name);
if (allUpperCase(name))
name = name.toLowerCase();
else
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
StringBuilder buf = new StringBuilder();
String[] subs = StringUtil.split(name, "_", 0);
for (int i = 0; i < subs.length; i++) {
if (i > 0)
subs[i] = StringUtil.capitalize(subs[i]);
buf.append(subs[i]);
}
return getUniqueName(buf.toString(), dec);
}
/**
* Return a default java identifier-formatted field relation name
* for the given class name.
*/
private String getRelationName(Class fieldType, boolean coll,
ForeignKey fk, boolean inverse, ClassMapping dec) {
if (_useFK && fk.getName() != null) {
String name = getFieldName(fk.getName(), dec);
if (inverse && coll)
name = name + "Inverses";
else if (inverse)
name = name + "Inverse";
return getUniqueName(name, dec);
}
// get just the class name, w/o package
String name = fieldType.getName();
name = name.substring(name.lastIndexOf('.') + 1);
// make the first character lowercase and pluralize if a collection
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (coll && !name.endsWith("s"))
name += "s";
return getUniqueName(name, dec);
}
/**
* Return true if the given string is all uppercase letters.
*/
private static boolean allUpperCase(String str) {
for (int i = 0; i < str.length(); i++) {
if (Character.isLetter(str.charAt(i))
&& !Character.isUpperCase(str.charAt(i)))
return false;
}
return true;
}
/**
* Replace characters not allowed in Java names with an underscore;
* package-private for testing.
*/
static String replaceInvalidCharacters(String str) {
if (StringUtil.isEmpty(str))
return str;
StringBuilder buf = new StringBuilder(str);
char c;
for (int i = 0; i < buf.length(); i++) {
c = buf.charAt(i);
if (c == '$' || !Character.isJavaIdentifierPart(str.charAt(i)))
buf.setCharAt(i, '_');
}
// strip leading and trailing underscores
int start = 0;
while (start < buf.length() && buf.charAt(start) == '_')
start++;
int end = buf.length() - 1;
while (end >= 0 && buf.charAt(end) == '_')
end--;
// possible that all chars in name are invalid
if (start > end)
return "x";
return buf.substring(start, end + 1);
}
/**
* Modifies the given name as necessary to ensure that it isn't already
* taken by a field in the given parent.
*/
private String getUniqueName(String name, ClassMapping dec) {
// make sure the name isn't a keyword
if (_javaKeywords.containsKey(name))
name = (String) _javaKeywords.get(name);
// this is the same algorithm used in DBDictionary to get unique names
String prefix = dec.getDescribedType().getName() + ".";
for (int version = 2, chars = 1;
dec.getDeclaredField(name) != null
|| (_abandonedFieldNames != null
&& _abandonedFieldNames.contains(prefix + name)); version++) {
if (version > 2)
name = name.substring(0, name.length() - chars);
if (version >= Math.pow(10, chars))
chars++;
name = name + version;
}
return name;
}
/**
* Return the default field type for the given column.
*/
public Class getFieldType(Column col, boolean forceObject) {
// check the custom type map to see if we've overridden the
// default type to create for a raw SQL type name
Class type = null;
if (_typeMap != null) {
// first try "TYPE(SIZE,DECIMALS)", then "TYPE(SIZE), then "TYPE"
String[] propNames = new String[]{
col.getTypeName() + "(" + col.getSize() + ","
+ col.getDecimalDigits() + ")",
col.getTypeName() + "(" + col.getSize() + ")",
col.getTypeName()
};
String typeName = null;
String typeSpec = null;
int nameIdx = 0;
for (; typeSpec == null && nameIdx < propNames.length; nameIdx++) {
if (propNames[nameIdx] == null)
continue;
typeSpec = StringUtil.trimToNull(_typeMap.getProperty
(propNames[nameIdx]));
if (typeSpec != null)
typeName = propNames[nameIdx];
}
if (typeSpec != null)
_log.info(_loc.get("reverse-type", typeName, typeSpec));
else
_log.trace(_loc.get("no-reverse-type",
propNames[propNames.length - 1]));
if (typeSpec != null)
type = ClassUtil.toClass(typeSpec, _conf.
getClassResolverInstance().getClassLoader
(ReverseMappingTool.class, null));
}
if (type == null)
type = Schemas.getJavaType(col.getType(), col.getSize(),
col.getDecimalDigits());
if (type == Object.class) {
if (!_blobAsObj)
return byte[].class;
return type;
}
// treat chars specially; if the dictionary has storeCharsAsNumbers
// set, then we can't reverse map into a char; use a string and tell
// the user why we are doing so
if (type == char.class
&& _conf.getDBDictionaryInstance().storeCharsAsNumbers) {
type = String.class;
if (_log.isWarnEnabled())
_log.warn(_loc.get("cant-use-char", col.getFullName()));
}
if (!type.isPrimitive())
return type;
if (!forceObject && (col.isNotNull() || !_nullAsObj))
return type;
// convert the type into the appropriate wrapper class
switch (type.getName().charAt(0)) {
case'b':
if (type == boolean.class)
return Boolean.class;
return Byte.class;
case'c':
return Character.class;
case'd':
return Double.class;
case'f':
return Float.class;
case'i':
return Integer.class;
case'l':
return Long.class;
case's':
return Short.class;
default:
throw new InternalException();
}
}
/**
* Return a new tool with the same settings as this one. Used in workbench.
*/
@Override
public Object clone() {
ReverseMappingTool tool = new ReverseMappingTool(_conf);
tool.setSchemaGroup(getSchemaGroup());
tool.setPackageName(getPackageName());
tool.setDirectory(getDirectory());
tool.setUseSchemaName(getUseSchemaName());
tool.setUseForeignKeyName(getUseForeignKeyName());
tool.setNullableAsObject(getNullableAsObject());
tool.setBlobAsObject(getBlobAsObject());
tool.setUseGenericCollections(getUseGenericCollections());
tool.setPrimaryKeyOnJoin(getPrimaryKeyOnJoin());
tool.setUseDataStoreIdentity(getUseDataStoreIdentity());
tool.setUseBuiltinIdentityClass(getUseBuiltinIdentityClass());
tool.setInnerIdentityClasses(getInnerIdentityClasses());
tool.setIdentityClassSuffix(getIdentityClassSuffix());
tool.setInverseRelations(getInverseRelations());
tool.setDetachable(getDetachable());
tool.setGenerateAnnotations(getGenerateAnnotations());
tool.setCustomizer(getCustomizer());
tool.setCodeFormat(getCodeFormat());
tool.setUseSchemaElement(getUseSchemaElement());
return tool;
}
protected static ReverseMappingTool newInstance(JDBCConfiguration conf) {
return new ReverseMappingTool(conf);
}
////////
// Main
////////
/**
* Usage: java org.apache.openjpa.jdbc.meta.ReverseMappingTool
* [option]* &lt;.schema file or resource&gt;*
* Where the following options are recognized.
* <ul>
* <li><i>-properties/-p &lt;properties file or resource&gt;</i>: The
* path or resource name of a OpenJPA properties file containing
* information such as the license key data as outlined in
* {@link OpenJPAConfiguration}. Optional.</li>
* <li><i>-&lt;property name&gt; &lt;property value&gt;</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>-schemas/-s &lt;schemas and tables&gt;</i>: Comma-separated
* list of schemas and tables to reverse-map.</li>
* <li><i>-package/-pkg &lt;package name&gt;</i>: The name of the package
* for all generated classes. Defaults to no package.</li>
* <li><i>-directory/-d &lt;output directory&gt;</i>: The directory where
* all generated code should be placed. Defaults to the current
* directory.</li>
* <li><i>-useSchemaName/-sn &lt;true/t | false/f&gt;</i>: Set this flag to
* true to include the schema name as part of the generated class name
* for each table.</li>
* <li><i>-useSchemaElement/-se &lt;true/t | false/f&gt;</i>: Set this
* flag to false to exclude the schema name from the @Table annotation
* in the generated class for each table. If set to false, the schema
* name will also be removed from the corresponding XML mapping files
* (orm.xml) that are generated by the tool. The initialized value is
* true (in order to preserve backwards compatibility). </li>
* <li><i>-useForeignKeyName/-fkn &lt;true/t | false/f&gt;</i>: Set this
* flag to true to use the foreign key name to generate fields
* representing relations between classes.</li>
* <li><i>-nullableAsObject/-no &lt;true/t | false/f&gt;</i>: Set to true
* to make all nullable columns map to object types; columns that would
* normally map to a primitive will map to the appropriate wrapper
* type instead.</li>
* <li><i>-blobAsObject/-bo &lt;true/t | false/f&gt;</i>: Set to true
* to make all binary columns map to Object rather than byte[].</li>
* <li><i>-useGenericCollections/-gc &lt;true/t | false/f&gt;</i>: Set to
* true to use generic collections on OneToMany and ManyToMany relations
* (requires JDK 1.5 or higher).</li>
* <li><i>-typeMap/-typ &lt;types&gt;</i>: Default mapping of SQL type
* names to Java classes.</li>
* <li><i>-primaryKeyOnJoin/-pkj &lt;true/t | false/f&gt;</i>: Set to true
* to allow primary keys on join tables.</li>
* <li><i>-useDatastoreIdentity/-ds &lt;true/t | false/f&gt;</i>: Set to
* true to use datastore identity where possible.</li>
* <li><i>-useBuiltinIdentityClass/-bic &lt;true/t | false/f&gt;</i>: Set
* to false to never use OpenJPA's builtin application identity
* classes.</li>
* <li><i>-innerIdentityClasses/-inn &lt;true/t | false/f&gt;</i>: Set to
* true to generate the application identity classes as inner classes.</li>
* <li><i>-identityClassSuffix/-is &lt;suffix&gt;</i>: Suffix to append
* to class names to create identity class name, or for inner identity
* classes, the inner class name.</li>
* <li><i>-inverseRelations/-ir &lt;true/t | false/f&gt;</i>: Set to
* false to prevent the creation of inverse 1-many/1-1 relations for
* each encountered many-1/1-1 relation.</li>
* <li><i>-detachable/-det &lt;true/t | false/f&gt;</i>: Set to
* true to make generated classes detachable.</li>
* <li><i>-discriminatorStrategy/-ds &lt;strategy&gt;</i>: The default
* discriminator strategy to place on base classes.</li>
* <li><i>-versionStrategy/-vs &lt;strategy&gt;</i>: The default
* version strategy to place on base classes.</li>
* <li><i>-metadata/-md &lt;class | package | none&gt;</i>: Specify the
* level the metadata should be generated at. Defaults to generating a
* single package-level metadata file.</li>
* <li><i>-annotations/-ann &lt;true/t | false/f&gt;</i>: Set to true to
* generate JPA annotations in generated code.</li>
* <li><i>-accessType/-access &lt;field | property&gt;</i>: Change access
* type for generated annotations. Defaults to field access.</li>
* <li><i>-customizerClass/-cc &lt;class name&gt;</i>: The full class
* name of a {@link ReverseCustomizer} implementation to use to
* customize the reverse mapping process. Optional.</li>
* <li><i>-customizerProperties/-cp &lt;properties file or resource&gt;</i>
* : The path or resource name of a properties file that will be
* passed to the reverse customizer on initialization. Optional.</li>
* <li><i>-customizer/-c.&lt;property name&gt; &lt; property value&gt;</i>
* : Arguments like this will be used to configure the bean
* properties of the {@link ReverseCustomizer}.</li>
* <li><i>-codeFormat/-cf.&lt;property name&gt; &lt; property value&gt;</i>
* : Arguments like this will be used to configure the bean
* properties of the internal {@link CodeFormat}.</li>
* </ul>
* Each schema given as an argument will be reverse-mapped into
* persistent classes and associated metadata. If no arguments are given,
* the database schemas defined by the system configuration will be
* reverse-mapped.
*/
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 ReverseMappingTool.run(conf, arguments, opts);
} finally {
conf.close();
}
}
});
if (!ret) {
// START - ALLOW PRINT STATEMENTS
System.out.println(_loc.get("revtool-usage"));
// STOP - ALLOW PRINT STATEMENTS
}
}
/**
* Run the tool. Returns false if invalid options were given.
*
* @see #main
*/
public static boolean run(JDBCConfiguration conf, String[] args, Options opts) throws IOException, SQLException {
return run(conf, args, opts, null);
}
/**
* Run the tool and write to the optionally provided map. Returns false if invalid options were given.
*
* @see #main
*/
public static boolean run(JDBCConfiguration conf, String[] args, Options opts, Map<Class<?>, String> output)
throws IOException, SQLException {
// flags
Flags flags = new Flags();
flags.packageName = opts.removeProperty
("package", "pkg", flags.packageName);
flags.directory = Files.getFile
(opts.removeProperty("directory", "d", null), null);
flags.useSchemaName = opts.removeBooleanProperty
("useSchemaName", "sn", flags.useSchemaName);
flags.useForeignKeyName = opts.removeBooleanProperty
("useForeignKeyName", "fkn", flags.useForeignKeyName);
flags.nullableAsObject = opts.removeBooleanProperty
("nullableAsObject", "no", flags.nullableAsObject);
flags.blobAsObject = opts.removeBooleanProperty
("blobAsObject", "bo", flags.blobAsObject);
flags.useGenericCollections = opts.removeBooleanProperty
("useGenericCollections", "gc", flags.useGenericCollections);
flags.primaryKeyOnJoin = opts.removeBooleanProperty
("primaryKeyOnJoin", "pkj", flags.primaryKeyOnJoin);
flags.useDataStoreIdentity = opts.removeBooleanProperty
("useDatastoreIdentity", "ds", flags.useDataStoreIdentity);
flags.useBuiltinIdentityClass = opts.removeBooleanProperty
("useBuiltinIdentityClass", "bic", flags.useBuiltinIdentityClass);
flags.innerIdentityClasses = opts.removeBooleanProperty
("innerIdentityClasses", "inn", flags.innerIdentityClasses);
flags.identityClassSuffix = opts.removeProperty
("identityClassSuffix", "is", flags.identityClassSuffix);
flags.inverseRelations = opts.removeBooleanProperty
("inverseRelations", "ir", flags.inverseRelations);
flags.detachable = opts.removeBooleanProperty
("detachable", "det", flags.detachable);
flags.discriminatorStrategy = opts.removeProperty
("discriminatorStrategy", "ds", flags.discriminatorStrategy);
flags.versionStrategy = opts.removeProperty
("versionStrategy", "vs", flags.versionStrategy);
flags.metaDataLevel = opts.removeProperty
("metadata", "md", flags.metaDataLevel);
flags.generateAnnotations = opts.removeBooleanProperty
("annotations", "ann", flags.generateAnnotations);
flags.accessType = opts.removeProperty
("accessType", "access", flags.accessType);
flags.useSchemaElement = opts.removeBooleanProperty
("useSchemaElement", "se", flags.useSchemaElement);
String typeMap = opts.removeProperty("typeMap", "typ", null);
if (typeMap != null)
flags.typeMap = Configurations.parseProperties(typeMap);
// remap the -s shortcut to the "schemas" property name so that it
// gets set into the configuration
if (opts.containsKey("s"))
opts.put("schemas", opts.get("s"));
// customizer
String customCls = opts.removeProperty("customizerClass", "cc",
PropertiesReverseCustomizer.class.getName());
File customFile = Files.getFile
(opts.removeProperty("customizerProperties", "cp", null), null);
Properties customProps = new Properties();
if (customFile != null && AccessController.doPrivileged(
J2DoPrivHelper.existsAction(customFile))) {
FileInputStream fis = null;
try {
fis = AccessController.doPrivileged(
J2DoPrivHelper.newFileInputStreamAction(customFile));
} catch (PrivilegedActionException pae) {
throw (FileNotFoundException) pae.getException();
}
customProps.load(fis);
}
// separate the properties for the customizer and code format
Options customOpts = new Options();
Options formatOpts = new Options();
Map.Entry entry;
String key;
for (Iterator itr = opts.entrySet().iterator(); itr.hasNext();) {
entry = (Map.Entry) itr.next();
key = (String) entry.getKey();
if (key.startsWith("customizer.")) {
customOpts.put(key.substring(11), entry.getValue());
itr.remove();
} else if (key.startsWith("c.")) {
customOpts.put(key.substring(2), entry.getValue());
itr.remove();
} else if (key.startsWith("codeFormat.")) {
formatOpts.put(key.substring(11), entry.getValue());
itr.remove();
} else if (key.startsWith("cf.")) {
formatOpts.put(key.substring(3), entry.getValue());
itr.remove();
}
}
// code format
if (!formatOpts.isEmpty()) {
flags.format = new CodeFormat();
formatOpts.setInto(flags.format);
}
// setup a configuration instance with cmd-line info
Configurations.populateConfiguration(conf, opts);
ClassLoader loader = conf.getClassResolverInstance().
getClassLoader(ReverseMappingTool.class, null);
// customizer
flags.customizer = (ReverseCustomizer) Configurations.
newInstance(customCls, loader);
if (flags.customizer != null) {
Configurations.configureInstance(flags.customizer, conf,
customOpts);
flags.customizer.setConfiguration(customProps);
}
run(conf, args, flags, loader, output);
return true;
}
/**
* Run the tool.
*/
public static void run(JDBCConfiguration conf, String[] args, Flags flags, ClassLoader loader) throws IOException,
SQLException {
run(conf, args, flags, loader, null);
}
/**
* Run the tool and write to the optionally provided map
*/
public static void run(JDBCConfiguration conf, String[] args, Flags flags, ClassLoader loader,
Map<Class<?>, String> output) throws IOException, SQLException {
// parse the schema to reverse-map
Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL);
SchemaGroup schema;
if (args.length == 0) {
log.info(_loc.get("revtool-running"));
SchemaGenerator gen = new SchemaGenerator(conf);
gen.generateSchemas();
schema = gen.getSchemaGroup();
} else {
SchemaParser parser = new XMLSchemaParser(conf);
File file;
for (String arg : args) {
file = Files.getFile(arg, loader);
log.info(_loc.get("revtool-running-file", file));
parser.parse(file);
}
schema = parser.getSchemaGroup();
}
// flags
ReverseMappingTool tool = newInstance(conf);
tool.setSchemaGroup(schema);
tool.setPackageName(flags.packageName);
tool.setDirectory(flags.directory);
tool.setUseSchemaName(flags.useSchemaName);
tool.setUseForeignKeyName(flags.useForeignKeyName);
tool.setNullableAsObject(flags.nullableAsObject);
tool.setBlobAsObject(flags.blobAsObject);
tool.setUseGenericCollections(flags.useGenericCollections);
tool.setTypeMap(flags.typeMap);
tool.setPrimaryKeyOnJoin(flags.primaryKeyOnJoin);
tool.setUseDataStoreIdentity(flags.useDataStoreIdentity);
tool.setUseBuiltinIdentityClass(flags.useBuiltinIdentityClass);
tool.setInnerIdentityClasses(flags.innerIdentityClasses);
tool.setIdentityClassSuffix(flags.identityClassSuffix);
tool.setInverseRelations(flags.inverseRelations);
tool.setDetachable(flags.detachable);
tool.setGenerateAnnotations(flags.generateAnnotations);
tool.setAccessType(flags.accessType);
tool.setCustomizer(flags.customizer);
tool.setCodeFormat(flags.format);
tool.setUseSchemaElement(flags.useSchemaElement);
// run
log.info(_loc.get("revtool-map"));
tool.run();
if (flags.generateAnnotations) {
log.info(_loc.get("revtool-gen-annos"));
tool.buildAnnotations();
}
log.info(_loc.get("revtool-write-code"));
tool.recordCode(output);
if (!LEVEL_NONE.equals(flags.metaDataLevel)) {
log.info(_loc.get("revtool-write-metadata"));
tool.recordMetaData(LEVEL_CLASS.equals(flags.metaDataLevel));
}
}
/**
* Holder for run flags.
*/
public static class Flags {
public String packageName = null;
public File directory = null;
public boolean useSchemaName = false;
public boolean useForeignKeyName = false;
public boolean nullableAsObject = false;
public boolean blobAsObject = false;
public boolean useGenericCollections = false;
public Properties typeMap = null;
public boolean primaryKeyOnJoin = false;
public boolean useDataStoreIdentity = false;
public boolean useBuiltinIdentityClass = true;
public boolean innerIdentityClasses = false;
public String identityClassSuffix = "Id";
public boolean inverseRelations = true;
public boolean detachable = false;
public boolean generateAnnotations = false;
public String accessType = ACCESS_TYPE_FIELD;
public String metaDataLevel = LEVEL_PACKAGE;
public String discriminatorStrategy = null;
public String versionStrategy = null;
public ReverseCustomizer customizer = null;
public CodeFormat format = null;
public boolean useSchemaElement = true;
}
/**
* Used to install discriminator and version strategies on
* reverse-mapped classes.
*/
private class ReverseStrategyInstaller
extends StrategyInstaller {
private static final long serialVersionUID = 1L;
public ReverseStrategyInstaller(MappingRepository repos) {
super(repos);
}
@Override
public void installStrategy(ClassMapping cls) {
throw new InternalException();
}
@Override
public void installStrategy(FieldMapping field) {
throw new InternalException();
}
@Override
public void installStrategy(Version version) {
ClassMapping cls = version.getClassMapping();
if (cls.getPCSuperclass() != null)
version.setStrategy(new SuperclassVersionStrategy(), null);
else if (_versStrat != null) {
VersionStrategy strat = repos.instantiateVersionStrategy
(_versStrat, version);
version.setStrategy(strat, null);
} else
version.setStrategy(new StateComparisonVersionStrategy(),
null);
}
@Override
public void installStrategy(Discriminator discrim) {
ClassMapping cls = discrim.getClassMapping();
if (cls.getPCSuperclass() != null) {
discrim.setStrategy(new SuperclassDiscriminatorStrategy(),
null);
} else if (!hasSubclasses(cls)) {
discrim.setStrategy(NoneDiscriminatorStrategy.getInstance(),
null);
} else if (_discStrat != null) {
DiscriminatorStrategy strat = repos.
instantiateDiscriminatorStrategy(_discStrat, discrim);
discrim.setStrategy(strat, null);
} else
discrim.setStrategy(new SubclassJoinDiscriminatorStrategy(),
null);
}
/**
* Return whether the given class has any mapped persistent subclasses.
*/
private boolean hasSubclasses(ClassMapping cls) {
ClassMetaData[] metas = repos.getMetaDatas();
for (ClassMetaData meta : metas)
if (meta.getPCSuperclass() == cls.getDescribedType())
return true;
return false;
}
}
/**
* Extension of the {@link CodeGenerator} to allow users to customize
* the formatting of their generated classes.
*/
private class ReverseCodeGenerator
extends CodeGenerator {
protected final ClassMapping _mapping;
protected final ApplicationIdTool _appid;
public ReverseCodeGenerator(ClassMapping mapping,
ApplicationIdTool aid) {
super(mapping);
super.setDirectory(_dir);
super.setCodeFormat(_format);
_mapping = mapping;
if (aid != null && aid.isInnerClass())
_appid = aid;
else
_appid = null;
}
/**
* If there is an inner application identity class, then
* add it to the bottom of the class code.
*/
@Override
protected void closeClassBrace(CodeFormat code) {
if (_appid != null) {
code.afterSection();
code.append(_appid.getCode());
code.endl();
}
super.closeClassBrace(code);
}
/**
* Add the list of imports for any inner app id classes
*
*/
@Override
public Set getImportPackages() {
Set pkgs = super.getImportPackages();
if (_appid != null)
pkgs.addAll(_appid.getImportPackages());
return pkgs;
}
@Override
protected String getClassCode() {
return (_custom == null) ? null : _custom.getClassCode(_mapping);
}
@Override
protected String getInitialValue(FieldMetaData field) {
if (_custom == null)
return null;
return _custom.getInitialValue((FieldMapping) field);
}
@Override
protected String getDeclaration(FieldMetaData field) {
if (_custom == null)
return null;
return _custom.getDeclaration((FieldMapping) field);
}
@Override
protected String getFieldCode(FieldMetaData field) {
if (_custom == null)
return null;
return _custom.getFieldCode((FieldMapping) field);
}
@Override
protected boolean useGenericCollections() {
return _useGenericColl;
}
}
private class AnnotatedCodeGenerator
extends ReverseCodeGenerator {
public AnnotatedCodeGenerator (ClassMapping mapping,
ApplicationIdTool aid) {
super (mapping, aid);
}
@Override
public Set getImportPackages() {
Set pkgs = super.getImportPackages();
pkgs.add("javax.persistence");
return pkgs;
}
@Override
protected List getClassAnnotations() {
return getAnnotationsForMeta(_mapping);
}
@Override
protected List getFieldAnnotations(FieldMetaData field) {
return getAnnotationsForMeta(field);
}
@Override
protected boolean usePropertyBasedAccess () {
return ACCESS_TYPE_PROPERTY.equals(_accessType);
}
}
}