OPENJPA-239 Patch to support the generation of annotation mappings using the reverse mapping tool

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@552358 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Marc Prud'hommeaux 2007-07-01 19:37:04 +00:00
parent 0b356dfc0d
commit 85b2e766ca
13 changed files with 2864 additions and 30 deletions

View File

@ -122,6 +122,14 @@ public class ReverseMappingToolTask
flags.blobAsObject = blobAsObject;
}
/**
* Set whether to use generic collections on one-to-many and many-to-many
* relations instead of untyped collections.
*/
public void setUseGenericCollections(boolean useGenericCollections) {
flags.useGenericCollections = useGenericCollections;
}
/**
* Set the SQL type map overrides.
*/
@ -201,6 +209,22 @@ public class ReverseMappingToolTask
flags.metaDataLevel = level.getValue();
}
/**
* Whether to generate annotations along with generated code. Defaults
* to false.
*/
public void setGenerateAnnotations(boolean genAnnotations) {
flags.generateAnnotations = genAnnotations;
}
/**
* Whether to use field or property-based access on generated code.
* Defaults to field-based access.
*/
public void setAccessType(AccessType accessType) {
flags.accessType = accessType.getValue();
}
/**
* Set a customizer class to use.
*/
@ -255,6 +279,18 @@ public class ReverseMappingToolTask
return new String[]{
"package",
"class",
"none"
};
}
}
public static class AccessType
extends EnumeratedAttribute {
public String[] getValues() {
return new String[]{
"field",
"property"
};
}
}

View File

@ -136,9 +136,16 @@ public class ReverseMappingTool
*/
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);
@ -176,6 +183,7 @@ public class ReverseMappingTool
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;
@ -186,6 +194,8 @@ public class ReverseMappingTool
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;
@ -196,6 +206,9 @@ public class ReverseMappingTool
// 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.
*/
@ -326,6 +339,22 @@ public class ReverseMappingTool
_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.
@ -478,6 +507,39 @@ public class ReverseMappingTool
_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.
*/
@ -784,7 +846,11 @@ public class ReverseMappingTool
_log.info(_loc.get("class-code", mappings[i]));
ApplicationIdTool aid = newApplicationIdTool(mappings[i]);
gen = new ReverseCodeGenerator(mappings[i], aid);
if (getGenerateAnnotations())
gen = new AnnotatedCodeGenerator(mappings[i], aid);
else
gen = new ReverseCodeGenerator(mappings[i], aid);
gen.generateCode();
if (output == null) {
@ -852,6 +918,31 @@ public class ReverseMappingTool
return files;
}
public void buildAnnotations() {
Map output = new HashMap();
// pretend mappings are all resolved
ClassMapping[] mappings = getMappings();
for (int i = 0; i < mappings.length; i++)
mappings[i].setResolve(MODE_META | MODE_MAPPING, true);
// 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.
*/
protected List getAnnotationsForMeta(Object meta) {
if (null == _annos)
return null;
return (List) _annos.get(meta);
}
/**
* Generate and write the application identity code.
*/
@ -1619,6 +1710,7 @@ public class ReverseMappingTool
tool.setUseForeignKeyName(getUseForeignKeyName());
tool.setNullableAsObject(getNullableAsObject());
tool.setBlobAsObject(getBlobAsObject());
tool.setUseGenericCollections(getUseGenericCollections());
tool.setPrimaryKeyOnJoin(getPrimaryKeyOnJoin());
tool.setUseDataStoreIdentity(getUseDataStoreIdentity());
tool.setUseBuiltinIdentityClass(getUseBuiltinIdentityClass());
@ -1626,6 +1718,7 @@ public class ReverseMappingTool
tool.setIdentityClassSuffix(getIdentityClassSuffix());
tool.setInverseRelations(getInverseRelations());
tool.setDetachable(getDetachable());
tool.setGenerateAnnotations(getGenerateAnnotations());
tool.setCustomizer(getCustomizer());
tool.setCodeFormat(getCodeFormat());
return tool;
@ -1667,6 +1760,9 @@ public class ReverseMappingTool
* 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
@ -1690,9 +1786,13 @@ public class ReverseMappingTool
* 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&gt;</i>: Specify the level the
* metadata should be generated at. Defaults to generating a
* <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>
@ -1749,6 +1849,8 @@ public class ReverseMappingTool
("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
@ -1768,7 +1870,11 @@ public class ReverseMappingTool
flags.versionStrategy = opts.removeProperty
("versionStrategy", "vs", flags.versionStrategy);
flags.metaDataLevel = opts.removeProperty
("metadata", "md", flags.metaDataLevel);
("metadata", "md", flags.metaDataLevel);
flags.generateAnnotations = opts.removeBooleanProperty
("annotations", "ann", flags.generateAnnotations);
flags.accessType = opts.removeProperty
("accessType", "access", flags.accessType);
String typeMap = opts.removeProperty("typeMap", "typ", null);
if (typeMap != null)
@ -1869,6 +1975,7 @@ public class ReverseMappingTool
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);
@ -1877,16 +1984,24 @@ public class ReverseMappingTool
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);
// 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();
log.info(_loc.get("revtool-write-metadata"));
tool.recordMetaData(LEVEL_CLASS.equals(flags.metaDataLevel));
if (!LEVEL_NONE.equals(flags.metaDataLevel)) {
log.info(_loc.get("revtool-write-metadata"));
tool.recordMetaData(LEVEL_CLASS.equals(flags.metaDataLevel));
}
}
/**
@ -1900,6 +2015,7 @@ public class ReverseMappingTool
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;
@ -1908,6 +2024,8 @@ public class ReverseMappingTool
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;
@ -1983,8 +2101,8 @@ public class ReverseMappingTool
private class ReverseCodeGenerator
extends CodeGenerator {
private final ClassMapping _mapping;
private final ApplicationIdTool _appid;
protected final ClassMapping _mapping;
protected final ApplicationIdTool _appid;
public ReverseCodeGenerator(ClassMapping mapping,
ApplicationIdTool aid) {
@ -2046,5 +2164,37 @@ public class ReverseMappingTool
return null;
return _custom.getFieldCode((FieldMapping) field);
}
protected boolean useGenericCollections() {
return _useGenericColl;
}
}
private class AnnotatedCodeGenerator
extends ReverseCodeGenerator {
public AnnotatedCodeGenerator (ClassMapping mapping,
ApplicationIdTool aid) {
super (mapping, aid);
}
public Set getImportPackages() {
Set pkgs = super.getImportPackages();
pkgs.add("javax.persistence");
return pkgs;
}
protected List getClassAnnotations() {
return getAnnotationsForMeta(_mapping);
}
protected List getFieldAnnotations(FieldMetaData field) {
return getAnnotationsForMeta(field);
}
protected boolean usePropertyBasedAccess () {
return ACCESS_TYPE_PROPERTY.equals(_accessType);
}
}
}

View File

@ -359,6 +359,7 @@ revtool-map: Calculating reverse mappings.
revtool-write-code: Writing generated class source code.
revtool-write-appid: Writing generated application identity classes.
revtool-write-metadata: Writing generated metadata.
revtool-gen-annos: Generating annotations.
revtool-usage: Usage: java org.apache.openjpa.jdbc.meta.ReverseMappingTool\n\
\t[-properties/-p <properties file or resource>]\n\
\t[-<property name> <property value>]*\n\
@ -369,6 +370,7 @@ revtool-usage: Usage: java org.apache.openjpa.jdbc.meta.ReverseMappingTool\n\
\t[-useForeignKeyName/-fkn <true/t | false/f>]\n\
\t[-nullableAsObject/-no <true/t | false/f>]\n\
\t[-blobAsObject/-bo <true/t | false/f>]\n\
\t[-useGenericCollections/-gc <true/t | false/f>]\n\
\t[-typeMap/-type <types>]\n\
\t[-primaryKeyOnJoin/-pkj <true/t | false/f>]\n\
\t[-useDatastoreIdentity/-ds <true/t | false/f>]\n\
@ -379,7 +381,9 @@ revtool-usage: Usage: java org.apache.openjpa.jdbc.meta.ReverseMappingTool\n\
\t[-detachable/-det <true/t | false/f>]\n\
\t[-discriminatorStrategy/-ds <strategy>]\n\
\t[-versionStrategy/-vs <strategy>]\n\
\t[-metadata/-md <package | class>]\n\
\t[-metadata/-md <package | class | none>]\n\
\t[-annotations/-ann <true/t | false/f>]\n\
\t[-accessType/-access <field | property>]\n\
\t[-customizerClass/-cc <full class name>]\n\
\t[-customizerProperties/-cp <properties file or resource>]\n\
\t[-customizer/-c.<property name> <property value>]*\n\

View File

@ -24,8 +24,10 @@ import java.io.Writer;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.util.CodeFormat;
import org.apache.openjpa.lib.util.Files;
@ -307,27 +309,47 @@ public class CodeGenerator {
propertyName = propertyName.substring(1);
String fieldType = Strings.getClassName(fmd.getDeclaredType());
String keyType = null;
String elementType = null;
String paramType = "";
if (useGenericCollections()) {
if (fmd.getDeclaredTypeCode() == JavaTypes.COLLECTION) {
Class elmCls = fmd.getElement().getDeclaredType();
elementType = Strings.getClassName(elmCls);
paramType = decs.getParametrizedType(
new String[] {elementType});
} else if (fmd.getDeclaredTypeCode() == JavaTypes.MAP) {
Class keyCls = fmd.getKey().getDeclaredType();
Class elmCls = fmd.getElement().getDeclaredType();
keyType = Strings.getClassName(keyCls);
elementType = Strings.getClassName(elmCls);
paramType = decs.getParametrizedType(
new String[] {keyType, elementType});
}
}
String fieldValue = getInitialValue(fmd);
if (fieldValue == null) {
if ("Set".equals(fieldType))
fieldValue = "new HashSet" + decs.getParens();
fieldValue = "new HashSet" + paramType + decs.getParens();
else if ("TreeSet".equals(fieldType))
fieldValue = "new TreeSet" + decs.getParens();
fieldValue = "new TreeSet" + paramType + decs.getParens();
else if ("Collection".equals(fieldType))
fieldValue = "new ArrayList" + decs.getParens();
fieldValue = "new ArrayList" + paramType + decs.getParens();
else if ("Map".equals(fieldType))
fieldValue = "new HashMap" + decs.getParens();
fieldValue = "new HashMap" + paramType + decs.getParens();
else if ("TreeMap".equals(fieldType))
fieldValue = "new TreeMap" + decs.getParens();
else if (fmd.getDeclaredTypeCode() == JavaTypes.COLLECTION
|| fmd.getDeclaredTypeCode() == JavaTypes.MAP)
fieldValue = "new " + fieldType + decs.getParens();
fieldValue = "new TreeMap" + paramType + decs.getParens();
else if (fmd.getDeclaredTypeCode() == JavaTypes.COLLECTION ||
fmd.getDeclaredTypeCode() == JavaTypes.MAP)
fieldValue = "new " + fieldType + paramType + decs.getParens();
else
fieldValue = "";
}
if (fieldValue.length() > 0)
fieldValue = " = " + fieldValue;
boolean fieldAccess = !usePropertyBasedAccess();
String custom = getDeclaration(fmd);
if (decs.length() > 0)
decs.endl();
@ -339,12 +361,18 @@ public class CodeGenerator {
templ.setParameter("capFieldName", capFieldName);
templ.setParameter("propertyName", propertyName);
templ.setParameter("fieldType", fieldType);
templ.setParameter("keyType", keyType);
templ.setParameter("elementType", elementType);
templ.setParameter("fieldValue", fieldValue);
decs.append(templ.toString());
} else {
if (fieldAccess)
writeAnnotations(decs, getFieldAnnotations(fmd), 1);
decs.tab().append("private ").append(fieldType).
append(" ").append(fieldName).append(fieldValue).
append(";");
append(paramType).append(" ").append(fieldName).
append(fieldValue).append(";");
if (fieldAccess)
decs.endl();
}
custom = getFieldCode(fmd);
@ -357,11 +385,16 @@ public class CodeGenerator {
templ.setParameter("capFieldName", capFieldName);
templ.setParameter("propertyName", propertyName);
templ.setParameter("fieldType", fieldType);
templ.setParameter("keyType", keyType);
templ.setParameter("elementType", elementType);
templ.setParameter("fieldValue", fieldValue);
code.append(templ.toString());
} else {
// getter
code.tab().append("public ").append(fieldType).append(" ");
if (!fieldAccess)
writeAnnotations(code, getFieldAnnotations(fmd), 1);
code.tab().append("public ").append(fieldType).append(paramType).
append(" ");
if ("boolean".equalsIgnoreCase(fieldType))
code.append("is");
else
@ -374,8 +407,8 @@ public class CodeGenerator {
// setter
code.tab().append("public void set").append(capFieldName);
code.openParen(true).append(fieldType).append(" ").
append(propertyName).closeParen();
code.openParen(true).append(fieldType).append(paramType).
append(" ").append(propertyName).closeParen();
code.openBrace(2).endl();
code.tab(2);
if (propertyName.equals(fieldName))
@ -403,6 +436,7 @@ public class CodeGenerator {
append(" * ").append(getClass().getName()).endl().
append(" */").endl();
writeAnnotations(code, getClassAnnotations(), 0);
code.append("public class ").append(className);
if (extendsName.length() > 0)
code.extendsDec(1).append(" ").append(extendsName);
@ -426,6 +460,21 @@ public class CodeGenerator {
return code.toString();
}
/**
* Appends the given list of annotations to code buffer.
*/
private void writeAnnotations (CodeFormat code, List ann,
int tabLevel) {
if (ann == null || ann.size() == 0)
return;
for (Iterator i = ann.iterator(); i.hasNext();) {
if (tabLevel > 0)
code.tab(tabLevel);
String s = (String) i.next();
code.append(s).endl();
}
}
/**
* Append the opening code-level brace to the code; this can be
* overridden to add code to the top of the class.
@ -503,6 +552,9 @@ public class CodeGenerator {
* <li>${capFieldName}: The capitalized field name.</li>
* <li>${propertyName}: The field name without leading '_', if any.</li>
* <li>${fieldType}: The field's type name.</li>
* <li>${keyType}: Key type name for maps, null otherwise.</li>
* <li>${elementType}: Element type name for collections, null otherwise.
* </li>
* <li>${fieldValue}: The field's initial value, in the form
* " = &lt;value&gt;", or empty string if none.</li>
* </ul> Returns null by default.
@ -521,6 +573,9 @@ public class CodeGenerator {
* <li>${capFieldName}: The capitalized field name.</li>
* <li>${propertyName}: The field name without leading '_', if any.</li>
* <li>${fieldType}: The field's type name.</li>
* <li>${keyType}: Key type name for maps, null otherwise.</li>
* <li>${elementType}: Element type name for collections, null otherwise.
* </li>
* <li>${fieldValue}: The field's initial value, in the form
* "= &lt;value&gt;", or empty string if none.</li>
* </ul>
@ -529,4 +584,37 @@ public class CodeGenerator {
{
return null;
}
/**
* Whether to use property-based access on generated code.
* Defaults to false (field-based).
*/
protected boolean usePropertyBasedAccess () {
return false;
}
/**
* Return class-level annotations. Returns null by default.
*/
protected List getClassAnnotations() {
return null;
}
/**
* Return field-level annotations. Returns null by default.
*/
protected List getFieldAnnotations(FieldMetaData field) {
return null;
}
/**
* Whether to use generic collections on one-to-many and many-to-many
* relations instead of untyped collections.
*
* Override in descendants to change default behavior.
*/
protected boolean useGenericCollections() {
return false;
}
}

View File

@ -192,7 +192,10 @@ public abstract class AbstractCFMetaDataFactory
Parser parser;
if (mode != MODE_QUERY) {
int sermode = (isMappingOnlyFactory()) ? mode : mode | MODE_META;
ser = newSerializer();
if ((mode & MODE_ANN_MAPPING) != 0)
ser = newAnnotationSerializer();
else
ser = newSerializer();
ser.setMode(sermode);
if (metaFiles != null) {
parser = newParser(false);
@ -231,7 +234,10 @@ public abstract class AbstractCFMetaDataFactory
for (int i = 0; !qFiles && i < queries.length; i++)
qFiles = queries[i].getSourceMode() == MODE_QUERY;
if (qFiles) {
ser = newSerializer();
if ((mode & MODE_ANN_MAPPING) != 0)
ser = newAnnotationSerializer();
else
ser = newSerializer();
ser.setMode(MODE_QUERY);
if (queryFiles != null) {
parser = newParser(false);
@ -543,6 +549,11 @@ public abstract class AbstractCFMetaDataFactory
*/
protected abstract Serializer newSerializer();
/**
* Create a new annotation metadata serializer.
*/
protected abstract Serializer newAnnotationSerializer();
/**
* Return the metadata that defines the given query, if any.
*

View File

@ -32,4 +32,5 @@ public interface MetaDataModes {
public static final int MODE_MAPPING = 2;
public static final int MODE_QUERY = 4;
public static final int MODE_MAPPING_INIT = 8;
public static final int MODE_ANN_MAPPING = 16;
}

View File

@ -308,6 +308,22 @@ public final class CodeFormat implements Cloneable {
return tabs.toString();
}
/**
* Returns parametrized type string for given type(s).
*/
public String getParametrizedType(String[] typenames) {
StringBuffer buf = new StringBuffer ();
buf.append("<");
for (int i = 0; i < typenames.length; i++) {
if (i > 0)
buf.append(", ");
buf.append(typenames[i]);
}
buf.append(">");
return buf.toString();
}
/**
* Return the field name for given suggested name, possibly adding
* leading underscore.

View File

@ -0,0 +1,793 @@
/*
* 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.persistence.jdbc;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataSerializer;
import org.apache.openjpa.persistence.PersistenceStrategy;
import org.apache.openjpa.persistence.AnnotationBuilder;
import org.apache.openjpa.jdbc.meta.QueryResultMapping;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.DiscriminatorMappingInfo;
import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.SequenceMapping;
import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
import org.apache.openjpa.jdbc.meta.strats.FlatClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.EnumValueHandler;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.schema.*;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.commons.lang.StringUtils;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.sql.Types;
import java.lang.annotation.Annotation;
import serp.util.Strings;
import javax.persistence.TemporalType;
import javax.persistence.EnumType;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.SecondaryTable;
import javax.persistence.Inheritance;
import javax.persistence.DiscriminatorValue;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.Enumerated;
import javax.persistence.UniqueConstraint;
import javax.persistence.TableGenerator;
import javax.persistence.JoinColumns;
import javax.persistence.JoinColumn;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.ColumnResult;
//@todo: javadocs
/**
* Serializes persistence mappings as annotations.
*
* @since 1.0.0
* @author Steve Kim
* @author Gokhan Ergul
* @nojavadoc
*/
public class AnnotationPersistenceMappingSerializer
extends AnnotationPersistenceMetaDataSerializer {
private static final int TYPE_RESULTMAP = TYPE_QUERY + 1;
private List<QueryResultMapping> _results = null;
private boolean _sync = false;
private Map<QueryResultMapping, List<AnnotationBuilder>> _rsmAnnos = null;
/**
* Constructor. Supply configuration.
*/
public AnnotationPersistenceMappingSerializer(JDBCConfiguration conf) {
super(conf);
}
/**
* Whether to automatically synchronize mapping info with data available
* from mapped components before serialization. Defaults to false.
*/
public boolean getSyncMappingInfo() {
return _sync;
}
/**
* Whether to automatically synchronize mapping info with data available
* from mapped components before serialization. Defaults to false.
*/
public void setSyncMappingInfo(boolean sync) {
_sync = sync;
}
/**
* Adds the given result set mapping to local cache.
*/
public void addQueryResultMapping(QueryResultMapping meta) {
if (_results == null)
_results = new ArrayList<QueryResultMapping>();
_results.add(meta);
}
/**
* Removes given result set mapping from the local cache.
*/
public boolean removeQueryResultMapping(QueryResultMapping meta) {
return _results != null && _results.remove(meta);
}
@Override
public void addAll(MetaDataRepository repos) {
super.addAll(repos);
for (QueryResultMapping res : ((MappingRepository) repos)
.getQueryResultMappings())
addQueryResultMapping(res);
}
@Override
public boolean removeAll(MetaDataRepository repos) {
boolean removed = super.removeAll(repos);
for (QueryResultMapping res : ((MappingRepository) repos)
.getQueryResultMappings())
removed |= removeQueryResultMapping(res);
return removed;
}
@Override
public void clear() {
super.clear();
if (_results != null)
_results.clear();
}
/**
* Add an annotation builder to list of builders for the specified
* class metadata.
*/
protected void addAnnotation(AnnotationBuilder ab, QueryResultMapping meta) {
if (_rsmAnnos == null)
_rsmAnnos = new HashMap<QueryResultMapping, List<AnnotationBuilder>>();
List<AnnotationBuilder> list = _rsmAnnos.get(meta);
if (list == null) {
list = new ArrayList<AnnotationBuilder>();
_rsmAnnos.put(meta, list);
}
list.add(ab);
}
/**
* Creates an an annotation builder for the specified class metadata
* and adds it to list of builders.
*/
protected AnnotationBuilder addAnnotation(
Class<? extends Annotation> annType, QueryResultMapping meta) {
AnnotationBuilder ab = newAnnotationBuilder(annType);
if (meta == null)
return ab;
addAnnotation(ab, meta);
return ab;
}
@Override
protected void serializeClass(ClassMetaData meta) {
if (_sync && isMappingMode() && meta instanceof ClassMapping) {
// sync if resolved and mapped
ClassMapping cls = (ClassMapping) meta;
if ((cls.getResolve() & MetaDataModes.MODE_MAPPING) != 0 &&
cls.isMapped()) {
cls.syncMappingInfo();
cls.getDiscriminator().syncMappingInfo();
cls.getVersion().syncMappingInfo();
FieldMapping[] fields;
if (cls.getEmbeddingMetaData() == null)
fields = cls.getDefinedFieldMappings();
else
fields = cls.getFieldMappings();
for (FieldMapping f : fields)
f.syncMappingInfo();
}
}
super.serializeClass(meta);
}
@Override
protected void serializeClassMappingContent(ClassMetaData mapping) {
ClassMapping cls = (ClassMapping) mapping;
ClassMappingInfo info = cls.getMappingInfo();
AnnotationBuilder abTable = addAnnotation(Table.class, mapping);
serializeTable(info.getTableName(), Strings
.getClassName(mapping.getDescribedType()), null,
info.getUniques(), abTable);
serializeColumns(info, ColType.PK_JOIN, null, abTable, cls);
for (String second : info.getSecondaryTableNames()) {
AnnotationBuilder abSecTable =
addAnnotation(SecondaryTable.class, mapping);
serializeTable(second, null, info, null, abSecTable);
}
}
@Override
protected void serializeInheritanceContent(ClassMetaData mapping) {
ClassMapping cls = (ClassMapping) mapping;
ClassMappingInfo info = cls.getMappingInfo();
DiscriminatorMappingInfo dinfo = cls.getDiscriminator()
.getMappingInfo();
String strat = info.getHierarchyStrategy();
if (null == strat)
return;
String itypecls = Strings.getClassName(InheritanceType.class);
AnnotationBuilder abInheritance =
addAnnotation(Inheritance.class, mapping);
if (FlatClassStrategy.ALIAS.equals(strat))
abInheritance.add("strategy", itypecls + ".SINGLE_TABLE");
else if (VerticalClassStrategy.ALIAS.equals(strat))
abInheritance.add("strategy", itypecls + ".JOINED");
else if (FullClassStrategy.ALIAS.equals(strat))
abInheritance.add("strategy", itypecls + ".TABLE_PER_CLASS");
if (dinfo.getValue() != null) {
AnnotationBuilder abDiscVal =
addAnnotation(DiscriminatorValue.class, mapping);
abDiscVal.add(null, dinfo.getValue());
}
AnnotationBuilder abDiscCol =
addAnnotation(DiscriminatorColumn.class, mapping);
serializeColumns(dinfo, ColType.DISC, null, abDiscCol, null);
}
/**
* Serialize table optionally listing primary-key-joins stored
* in the given {@link org.apache.openjpa.jdbc.meta.ClassMappingInfo}.
*/
private void serializeTable(String table, String defaultName,
ClassMappingInfo secondaryInfo, Unique[] uniques,
AnnotationBuilder ab) {
List<Column> cols = null;
if (secondaryInfo != null)
cols = (List<Column>) secondaryInfo.getSecondaryTableJoinColumns
(table);
boolean print = (cols != null && cols.size() > 0) ||
(uniques != null && uniques.length > 0);
if (table != null
&& (defaultName == null || !defaultName.equals(table))) {
print = true;
int index = table.indexOf('.');
if (index < 0)
ab.add("name", table);
else {
ab.add("schema", table.substring(0, index));
ab.add("name", table.substring(index + 1));
}
}
if (print) {
if (cols != null) {
for (Column col : cols)
serializeColumn(col, ColType.PK_JOIN,
null, false, ab, null);
}
if (uniques != null) {
for (Unique unique: uniques) {
AnnotationBuilder abUniqueConst =
newAnnotationBuilder(UniqueConstraint.class);
serializeUniqueConstraint(unique, abUniqueConst);
ab.add("uniqueConstraints", abUniqueConst);
}
}
}
}
@Override
protected boolean serializeAttributeOverride(FieldMetaData fmd,
FieldMetaData orig) {
if (orig == null || fmd == orig)
return false;
FieldMapping field = (FieldMapping) fmd;
FieldMapping field2 = (FieldMapping) orig;
if (field.getMappingInfo().hasSchemaComponents()
|| field2.getMappingInfo().hasSchemaComponents())
return true;
ValueMappingInfo info = field.getValueInfo();
List<Column> cols = (List<Column>) info.getColumns();
if (cols == null || cols.size() == 0)
return false;
ValueMappingInfo info2 = field2.getValueInfo();
List<Column> cols2 = (List<Column>) info2.getColumns();
if (cols2 == null || cols2.size() != cols.size())
return true;
if (cols.size() != 1)
return true;
Column col;
Column col2;
for (int i = 0; i < cols.size(); i++) {
col = cols.get(i);
col2 = cols2.get(i);
if (!StringUtils.equals(col.getName(), col2.getName()))
return true;
if (!StringUtils.equals(col.getTypeName(), col2.getTypeName()))
return true;
if (col.getSize() != col2.getSize())
return true;
if (col.getDecimalDigits() != col2.getDecimalDigits())
return true;
if (col.getFlag(Column.FLAG_UNINSERTABLE)
!= col2.getFlag(Column.FLAG_UNINSERTABLE))
return true;
if (col.getFlag(Column.FLAG_UNUPDATABLE)
!= col2.getFlag(Column.FLAG_UNUPDATABLE))
return true;
}
return false;
}
@Override
protected void serializeAttributeOverrideMappingContent(FieldMetaData fmd,
FieldMetaData orig, AnnotationBuilder ab) {
FieldMapping fm = (FieldMapping) fmd;
serializeColumns(fm.getValueInfo(), ColType.COL, fm.getMappingInfo()
.getTableName(), ab, fmd);
}
@Override
protected PersistenceStrategy getStrategy(FieldMetaData fmd) {
PersistenceStrategy strat = super.getStrategy(fmd);
FieldMapping field = (FieldMapping) fmd;
switch (strat) {
case MANY_MANY:
// we can differentiate a one-many by the fact that there is no
// secondary table join, or the fk is unique
if (field.getMappedBy() == null
&& (field.getMappingInfo().getJoinDirection()
== MappingInfo.JOIN_NONE
|| field.getElementMapping().getValueInfo().getUnique()
!= null))
return PersistenceStrategy.ONE_MANY;
break;
case MANY_ONE:
// inverse join cols or unique fk?
if (field.getValueInfo().getJoinDirection()
== MappingInfo.JOIN_INVERSE
|| field.getValueInfo().getUnique() != null)
return PersistenceStrategy.ONE_ONE;
// scan for primary-key-join-column
List<Column> cols = field.getValueInfo().getColumns();
boolean pkJoin = cols != null && cols.size() > 0;
for (int i = 0; pkJoin && i < cols.size(); i++)
pkJoin = cols.get(i).getFlag(Column.FLAG_PK_JOIN);
if (pkJoin)
return PersistenceStrategy.ONE_ONE;
break;
}
return strat;
}
@Override
protected void serializeFieldMappingContent(FieldMetaData fmd,
PersistenceStrategy strategy, AnnotationBuilder ab) {
if (fmd.getMappedBy() != null)
return;
// while I'd like to do auto detection based on join directions, etc.
// the distinguished column / table / etc names forces our hand
// esp for OpenJPA custom mappings.
FieldMapping field = (FieldMapping) fmd;
switch (strategy) {
case ONE_ONE:
case MANY_ONE:
serializeColumns(field.getValueInfo(), ColType.JOIN,
field.getMappingInfo().getTableName(), null, fmd);
return;
case ONE_MANY:
if (field.getMappingInfo().getJoinDirection() ==
MappingInfo.JOIN_NONE) {
serializeColumns(field.getElementMapping().getValueInfo(),
ColType.JOIN, null, null, fmd);
return;
}
// else no break
case MANY_MANY:
if (field.getMappingInfo().hasSchemaComponents()
|| field.getElementMapping().getValueInfo()
.hasSchemaComponents()) {
AnnotationBuilder abJoinTbl =
addAnnotation(JoinTable.class, fmd);
String table = field.getMappingInfo().getTableName();
if (table != null) {
int index = table.indexOf('.');
if (index < 0)
abJoinTbl.add("name", table);
else {
abJoinTbl.add("schema", table.substring(0, index));
abJoinTbl.add("name", table.substring(index + 1));
}
}
serializeColumns(field.getMappingInfo(),
ColType.JOIN, null, abJoinTbl, null);
serializeColumns(field.getElementMapping().getValueInfo(),
ColType.INVERSE, null, abJoinTbl, null);
}
return;
}
serializeColumns(field.getValueInfo(), ColType.COL,
field.getMappingInfo().getTableName(), null, fmd);
if (strategy == PersistenceStrategy.BASIC && isLob(field)) {
addAnnotation(Lob.class, fmd);
}
TemporalType temporal = getTemporal(field);
if (temporal != null) {
addAnnotation(Temporal.class, fmd).
add(null, temporal);
}
EnumType enumType = getEnumType(field);
if (enumType != null && enumType != EnumType.ORDINAL) {
addAnnotation(Enumerated.class, fmd).
add(null, enumType);
}
}
/**
* Determine if the field is a lob.
*/
private boolean isLob(FieldMapping field) {
for (Column col : (List<Column>) field.getValueInfo().getColumns())
if (col.getType() == Types.BLOB || col.getType() == Types.CLOB)
return true;
return false;
}
/**
* Return field's temporal type.
*/
private TemporalType getTemporal(FieldMapping field) {
if (field.getDeclaredTypeCode() != JavaTypes.DATE
&& field.getDeclaredTypeCode() != JavaTypes.CALENDAR)
return null;
DBDictionary dict = ((JDBCConfiguration) getConfiguration())
.getDBDictionaryInstance();
int def = dict.getJDBCType(field.getTypeCode(), false);
for (Column col : (List<Column>) field.getValueInfo().getColumns()) {
if (col.getType() == def)
continue;
switch (col.getType()) {
case Types.DATE:
return TemporalType.DATE;
case Types.TIME:
return TemporalType.TIME;
case Types.TIMESTAMP:
return TemporalType.TIMESTAMP;
}
}
return null;
}
/**
* Return enum type for the field.
*/
protected EnumType getEnumType(FieldMapping field) {
if (field.getDeclaredTypeCode() != JavaTypes.OBJECT)
return null;
if (!(field.getHandler() instanceof EnumValueHandler))
return null;
return ((EnumValueHandler) field.getHandler()).getStoreOrdinal()
? EnumType.ORDINAL : EnumType.STRING;
}
/**
* Serialize the columns in the given mapping info.
*/
private void serializeColumns(MappingInfo info, ColType type,
String secondary, AnnotationBuilder ab, Object meta) {
List<Column> cols = (List<Column>) info.getColumns();
if (cols == null)
return;
AnnotationBuilder abContainer = ab;
if (cols.size() > 1) {
Class grpType = type.getColumnGroupAnnotationType();
if (null != grpType) {
AnnotationBuilder abGrp = newAnnotationBuilder(grpType);
if (null == ab)
addAnnotation(abGrp, meta);
else
ab.add(null, abGrp);
abContainer = abGrp;
}
}
for (Column col : cols)
serializeColumn(col, type, secondary,
info.getUnique() != null, abContainer, meta);
}
/**
* Serialize a single column.
*/
private void serializeColumn(Column col, ColType type, String secondary,
boolean unique, AnnotationBuilder ab, Object meta) {
FieldMetaData fmd = meta instanceof FieldMetaData ?
(FieldMetaData) meta : null;
AnnotationBuilder abCol = newAnnotationBuilder(
type.getColumnAnnotationType());
if (col.getName() != null && (null == fmd ||
!col.getName().equalsIgnoreCase(fmd.getName())))
abCol.add("name", col.getName());
if (col.getTypeName() != null)
abCol.add("columnDefinition", col.getTypeName());
if (col.getTarget() != null
&& (type == ColType.JOIN || type == ColType.INVERSE
|| type == ColType.PK_JOIN))
abCol.add("referencedColumnName", col.getTarget());
if (type == ColType.COL || type == ColType.JOIN
|| type == ColType.PK_JOIN) {
if (unique)
abCol.add("unique", true);
if (col.isNotNull())
abCol.add("nullable", false);
if (col.getFlag(Column.FLAG_UNINSERTABLE))
abCol.add("insertable", false);
if (col.getFlag(Column.FLAG_UNUPDATABLE))
abCol.add("updatable", false);
if (secondary != null)
abCol.add("table", secondary);
if (type == ColType.COL) {
if (col.getSize() > 0 && col.getSize() != 255)
abCol.add("length", col.getSize());
if (col.getDecimalDigits() != 0)
abCol.add("scale", col.getDecimalDigits());
}
}
if (type != ColType.COL || abCol.hasComponents()) {
if (null != ab) {
String key = null;
if (ab.getType() == JoinTable.class) {
switch(type) {
case JOIN:
key = "joinColumns";
break;
case INVERSE:
key = "inverseJoinColumns";
break;
}
}
ab.add(key, abCol);
} else {
addAnnotation(abCol, meta);
}
}
}
private void serializeUniqueConstraint(Unique unique,
AnnotationBuilder ab) {
StringBuilder sb = new StringBuilder();
Column[] columns = unique.getColumns();
for (Column column:columns) {
if (sb.length() > 0)
sb.append(", ");
sb.append(column.getName());
}
if (columns.length > 1)
sb.insert(0, "{").append("}");
ab.add("columnNames", sb.toString());
}
@Override
protected SerializationComparator newSerializationComparator() {
return new AnnotationPersistenceMappingSerializer.
MappingSerializationComparator();
}
@Override
protected void addSystemMappingElements(Collection toSerialize) {
if (isQueryMode())
toSerialize.addAll(getQueryResultMappings(null));
}
@Override
protected int type(Object o) {
int type = super.type(o);
if (type == -1 && o instanceof QueryResultMapping)
return TYPE_RESULTMAP;
return type;
}
/**
* Return the result set mappings for the given scope.
*/
private List<QueryResultMapping> getQueryResultMappings(ClassMetaData cm) {
if (_results == null || _results.isEmpty())
return (List<QueryResultMapping>) Collections.EMPTY_LIST;
List<QueryResultMapping> result = null;
for (int i = 0; i < _results.size(); i++) {
QueryResultMapping element = _results.get(i);
if ((cm == null && element.getSourceScope() != null) || (cm != null
&& element.getSourceScope() != cm.getDescribedType()))
continue;
if (result == null)
result = new ArrayList<QueryResultMapping>(_results.size() - i);
result.add(element);
}
return (result == null)
? (List<QueryResultMapping>) Collections.EMPTY_LIST : result;
}
@Override
protected void serializeSystemMappingElement(Object obj) {
if (obj instanceof QueryResultMapping)
serializeQueryResultMapping((QueryResultMapping) obj, null);
}
@Override
protected void serializeQueryMappings(ClassMetaData meta) {
for (QueryResultMapping res : getQueryResultMappings(meta))
serializeQueryResultMapping(res, meta);
}
/**
* Serialize given result set mapping.
*/
private void serializeQueryResultMapping(QueryResultMapping meta,
ClassMetaData clsmeta) {
AnnotationBuilder ab = addAnnotation(SqlResultSetMapping.class, meta);
if (null != clsmeta)
addAnnotation(ab, clsmeta);
ab.add("name", meta.getName());
for (QueryResultMapping.PCResult pc : meta.getPCResults()) {
AnnotationBuilder abEntRes =
newAnnotationBuilder(EntityResult.class);
ab.add("entities", abEntRes);
abEntRes.add("entityClass", pc.getCandidateType());
Object discrim = pc.getMapping(pc.DISCRIMINATOR);
if (discrim != null)
abEntRes.add("discriminatorColumn", discrim.toString());
for (String path : pc.getMappingPaths()) {
AnnotationBuilder abFldRes =
newAnnotationBuilder(FieldResult.class);
abEntRes.add("fields", abFldRes);
abFldRes.add("name", path);
abFldRes.add("column", pc.getMapping(path).toString());
}
}
for (Object col : meta.getColumnResults()) {
AnnotationBuilder abColRes =
newAnnotationBuilder(ColumnResult.class);
abColRes.add("name", col.toString());
}
}
@Override
protected void serializeSequence(SequenceMetaData meta) {
if (SequenceMapping.IMPL_VALUE_TABLE.equals(meta.getSequencePlugin())) {
super.serializeSequence(meta);
return;
}
AnnotationBuilder abTblGen = addAnnotation(TableGenerator.class, meta);
SequenceMapping seq = (SequenceMapping) meta;
abTblGen.add("name", seq.getName());
String table = seq.getTable();
if (table != null) {
int dotIdx = table.indexOf('.');
if (dotIdx == -1)
abTblGen.add("table", table);
else {
abTblGen.add("table", table.substring(dotIdx + 1));
abTblGen.add("schema", table.substring(0, dotIdx));
}
}
if (!StringUtils.isEmpty(seq.getPrimaryKeyColumn()))
abTblGen.add("pkColumnName", seq.getPrimaryKeyColumn());
if (!StringUtils.isEmpty(seq.getSequenceColumn()))
abTblGen.add("valueColumnName", seq.getSequenceColumn());
if (!StringUtils.isEmpty(seq.getPrimaryKeyValue()))
abTblGen.add("pkColumnValue", seq.getPrimaryKeyValue());
if (seq.getAllocate() != 50 && seq.getAllocate() != -1)
abTblGen.add("allocationSize", seq.getAllocate() + "");
if (seq.getInitialValue() != 0 && seq.getInitialValue() != -1)
abTblGen.add("initialValue", seq.getInitialValue() + "");
}
/**
* Column types serialized under different names.
*/
private static enum ColType {
COL,
JOIN,
INVERSE,
PK_JOIN,
DISC;
private Class<? extends Annotation> getColumnAnnotationType() {
switch(this) {
case COL:
return javax.persistence.Column.class;
case JOIN:
case INVERSE:
return JoinColumn.class;
case PK_JOIN:
return PrimaryKeyJoinColumn.class;
case DISC:
return DiscriminatorColumn.class;
}
return null;
}
private Class<? extends Annotation> getColumnGroupAnnotationType() {
switch(this) {
case JOIN:
case INVERSE:
return JoinColumns.class;
case PK_JOIN:
return PrimaryKeyJoinColumns.class;
}
return null;
}
}
/**
* Extends {@link SerializationComparator} for store-specific tags such
* as &lt;sql-result-set-mapping&gt;.
*
* @author Pinaki Poddar
*/
protected class MappingSerializationComparator
extends SerializationComparator {
protected int compareUnknown(Object o1, Object o2) {
if (!(o1 instanceof QueryResultMapping))
return super.compareUnknown(o1, o2);
QueryResultMapping res1 = (QueryResultMapping) o1;
QueryResultMapping res2 = (QueryResultMapping) o2;
// system scope before class scope
Object scope1 = res1.getSourceScope();
Object scope2 = res2.getSourceScope();
if (scope1 == null && scope2 != null)
return -1;
if (scope1 != null && scope2 == null)
return 1;
// compare on listing index, or if none/same, use name
int listingIndex1 = res1.getListingIndex();
int listingIndex2 = res2.getListingIndex();
if (listingIndex1 != listingIndex2)
return listingIndex1 - listingIndex2;
return res1.getName ().compareTo (res2.getName ());
}
}
}

View File

@ -21,10 +21,11 @@ package org.apache.openjpa.persistence.jdbc;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.meta.MetaDataFactory;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataParser;
import org.apache.openjpa.persistence.PersistenceMetaDataFactory;
import org.apache.openjpa.persistence.XMLPersistenceMetaDataParser;
import org.apache.openjpa.persistence.XMLPersistenceMetaDataSerializer;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataParser;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataSerializer;
/**
* {@link MetaDataFactory} for JPA mapping information.
@ -48,6 +49,15 @@ public class PersistenceMappingFactory
return parser;
}
protected AnnotationPersistenceMetaDataSerializer newAnnotationSerializer()
{
AnnotationPersistenceMappingSerializer ser =
new AnnotationPersistenceMappingSerializer((JDBCConfiguration)
repos.getConfiguration());
ser.setSyncMappingInfo(true);
return ser;
}
@Override
protected XMLPersistenceMetaDataParser newXMLParser(boolean loading) {
XMLPersistenceMappingParser parser = new XMLPersistenceMappingParser

View File

@ -0,0 +1,200 @@
/*
* 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.persistence;
import serp.util.Strings;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.EnumSet;
import java.lang.annotation.Annotation;
import org.apache.commons.lang.StringUtils;
/**
* Helper class to stringify annotation declarations.
*
* @author Gokhan Ergul
* @since 1.0.0
*/
public class AnnotationBuilder {
private Class<? extends Annotation> type;
private List<AnnotationEntry> components =
new ArrayList<AnnotationEntry>();
protected AnnotationBuilder(Class<? extends Annotation> type) {
this.type = type;
}
public Class<? extends Annotation> getType() {
return this.type;
}
public AnnotationBuilder add(String key, String val) {
return doAdd(key, val);
}
public AnnotationBuilder add(String key, boolean val) {
return doAdd(key, val);
}
public AnnotationBuilder add(String key, int val) {
return doAdd(key, val);
}
public AnnotationBuilder add(String key, Class val) {
return doAdd(key, val);
}
public AnnotationBuilder add(String key, EnumSet val) {
return doAdd(key, val);
}
public AnnotationBuilder add(String key, Enum val) {
return doAdd(key, val);
}
@SuppressWarnings("unchecked")
public AnnotationBuilder add(String key, AnnotationBuilder val) {
if (null == val)
return this;
AnnotationEntry ae = find(key);
if (null == ae) {
doAdd(key, val);
} else {
List<AnnotationBuilder> list;
if (ae.value instanceof List) {
list = (List<AnnotationBuilder>) ae.value;
} else if (ae.value instanceof AnnotationBuilder) {
list = new ArrayList<AnnotationBuilder> ();
list.add((AnnotationBuilder) ae.value);
ae.value = list;
} else {
throw new IllegalArgumentException(
"Unexpected type: " + ae.value);
}
list.add(val);
}
return this;
}
public boolean hasComponents() {
return components.size() > 0;
}
private AnnotationBuilder doAdd (String key, Object val) {
if (null != val)
components.add(new AnnotationEntry(key, val));
return this;
}
private AnnotationEntry find(String key) {
for(AnnotationEntry ae: components) {
// null key references considered equal
if (StringUtils.equals(ae.key, key))
return ae;
}
return null;
}
static String enumToString(Enum e) {
StringBuilder sb = new StringBuilder();
sb.append(Strings.getClassName(e.getClass())).
append(".").append(e);
return sb.toString();
}
static String enumSetToString(EnumSet set) {
StringBuilder sb = new StringBuilder();
for (Iterator i = set.iterator(); i.hasNext();) {
Object e = i.next();
sb.append(Strings.getClassName(e.getClass())).
append(".").append(e);
if (i.hasNext())
sb.append(", ");
}
return sb.toString();
}
protected void toString(StringBuilder sb) {
sb.append("@").append(Strings.getClassName(type));
if (components.size() == 0)
return;
sb.append("(");
for (Iterator<AnnotationEntry> i = components.iterator(); i.hasNext();)
{
AnnotationEntry e = i.next();
e.toString(sb);
if (i.hasNext())
sb.append(", ");
}
sb.append(")");
}
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
}
class AnnotationEntry {
String key;
Object value;
AnnotationEntry(String key, Object value) {
this.key = key;
this.value = value;
}
@SuppressWarnings("unchecked")
void toString(StringBuilder sb) {
if (null != key)
sb.append(key).append("=");
List.class.getTypeParameters();
if (value instanceof List) {
sb.append("{");
List<AnnotationBuilder> l = (List<AnnotationBuilder>) value;
for (Iterator<AnnotationBuilder> i = l.iterator(); i.hasNext();) {
AnnotationBuilder ab = i.next();
sb.append(ab.toString());
if (i.hasNext())
sb.append(", ");
}
sb.append("}");
} else if (value instanceof Class) {
String cls = ((Class) value).getName().replace('$', '.');
sb.append(cls).append(".class");
} else if (value instanceof String) {
sb.append('"').append(value).append('"');
} else if (value instanceof Enum) {
sb.append(AnnotationBuilder.enumToString((Enum) value));
} else if (value instanceof EnumSet) {
sb.append(AnnotationBuilder.enumSetToString((EnumSet) value));
} else {
sb.append(value);
}
}
}

View File

@ -125,6 +125,15 @@ public class PersistenceMetaDataFactory
(repos.getConfiguration());
}
/**
* Create a new annotation serializer.
*/
protected AnnotationPersistenceMetaDataSerializer
newAnnotationSerializer() {
return new AnnotationPersistenceMetaDataSerializer
(repos.getConfiguration());
}
/**
* Return XML metadata parser, creating it if it does not already exist.
*/

View File

@ -539,6 +539,26 @@ directory. Defaults to the current directory.
</listitem>
<listitem>
<para>
<literal>-metadata/-md &lt;class | package | none&gt;</literal>: Specify the
level the metadata should be generated at. Defaults to generating a single
package-level metadata file. Set to <literal>none</literal> to disable orm.xml
generation.
</para>
</listitem>
<listitem>
<para>
<literal>-annotations/-ann &lt;true/t | false/f&gt;</literal>: Set to true to
generate JPA annotations in generated java classes.
</para>
</listitem>
<listitem>
<para>
<literal>-accessType/-access &lt;field | property&gt;</literal>: Change access
type for generated annotations. Defaults to field access.
</para>
</listitem>
<listitem>
<para>
<literal>-useSchemaName/-sn &lt;true/t | false/f&gt;</literal>: Set this flag
to <literal>true</literal> to include the schema as well as table name in the
name of each generated class. This can be useful when dealing with multiple
@ -588,6 +608,13 @@ for every many-1/1-1 relation detected.
</listitem>
<listitem>
<para>
<literal>-useGenericCollections/-gc &lt;true/t | false/f&gt;</literal>: Set to
true to use generic collections on OneToMany and ManyToMany relations (requires
JDK 1.5 or higher).
</para>
</listitem>
<listitem>
<para>
<literal>-useDatastoreIdentity/-ds &lt;true/t | false/f&gt;</literal>: Set to
<literal>true</literal> to use datastore identity for tables that have single
numeric primary key columns. The tool typically uses application identity for
@ -672,8 +699,9 @@ property in the specified reverse customizer, and set to the given value.
<para>
Running the tool will generate <filename>.java</filename> files for each
generated class (and its application identity class, if applicable), along with
an <filename>orm.xml</filename> file containing the corresponding persistence
metadata.
JPA annotations (if enabled by setting <literal>-annotations true</literal>),
or an <filename>orm.xml</filename> file (if not disabled with <literal>
-metadata none</literal>) containing the corresponding persistence metadata.
</para>
</listitem>
<listitem>
@ -688,9 +716,9 @@ After you are satisfied with the generated classes and their mappings, you
should first compile the classes with <literal>javac</literal>, <literal>
jikes</literal>, or your favorite Java compiler. Make sure the classes are
located in the directory corresponding to the <literal>-package</literal> flag
you gave the reverse mapping tool. Next, move the generated <filename>
orm.xml</filename> file to a <filename>META-INF</filename> directory within a
directory in your classpath. Finally, enhance the classes
you gave the reverse mapping tool. Next, if you have generated an <filename>
orm.xml</filename>, move that file to a <filename>META-INF</filename> directory
within a directory in your classpath. Finally, enhance the classes
if necessary (see <xref linkend="ref_guide_pc_enhance"/> ).
</para>
</listitem>