blob: b05dea17ddd3ecc88947e29f835f54fcbc0f0578 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2005, 2006, 2007 Springsite BV (The Netherlands) and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Martin Taal
* </copyright>
*
* $Id: DefaultAnnotator.java,v 1.25.2.4 2007/03/05 20:17:11 mtaal Exp $
*/
package org.eclipse.emf.teneo.annotations.mapper;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.teneo.PersistenceOptions;
import org.eclipse.emf.teneo.annotations.StoreAnnotationsException;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEAttribute;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEClass;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEDataType;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEPackage;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEReference;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEStructuralFeature;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedModel;
import org.eclipse.emf.teneo.annotations.pannotation.Basic;
import org.eclipse.emf.teneo.annotations.pannotation.CascadeType;
import org.eclipse.emf.teneo.annotations.pannotation.Column;
import org.eclipse.emf.teneo.annotations.pannotation.DiscriminatorColumn;
import org.eclipse.emf.teneo.annotations.pannotation.DiscriminatorValue;
import org.eclipse.emf.teneo.annotations.pannotation.Entity;
import org.eclipse.emf.teneo.annotations.pannotation.EnumType;
import org.eclipse.emf.teneo.annotations.pannotation.Enumerated;
import org.eclipse.emf.teneo.annotations.pannotation.FetchType;
import org.eclipse.emf.teneo.annotations.pannotation.Id;
import org.eclipse.emf.teneo.annotations.pannotation.Inheritance;
import org.eclipse.emf.teneo.annotations.pannotation.InheritanceType;
import org.eclipse.emf.teneo.annotations.pannotation.JoinColumn;
import org.eclipse.emf.teneo.annotations.pannotation.JoinTable;
import org.eclipse.emf.teneo.annotations.pannotation.ManyToMany;
import org.eclipse.emf.teneo.annotations.pannotation.ManyToOne;
import org.eclipse.emf.teneo.annotations.pannotation.OneToMany;
import org.eclipse.emf.teneo.annotations.pannotation.OneToOne;
import org.eclipse.emf.teneo.annotations.pannotation.PannotationFactory;
import org.eclipse.emf.teneo.annotations.pannotation.PrimaryKeyJoinColumn;
import org.eclipse.emf.teneo.annotations.pannotation.SecondaryTable;
import org.eclipse.emf.teneo.annotations.pannotation.Table;
import org.eclipse.emf.teneo.annotations.pannotation.Temporal;
import org.eclipse.emf.teneo.annotations.pannotation.TemporalType;
import org.eclipse.emf.teneo.annotations.pannotation.Transient;
import org.eclipse.emf.teneo.ecore.EClassNameStrategy;
import org.eclipse.emf.teneo.util.AssertUtil;
import org.eclipse.emf.teneo.util.SQLCaseStrategy;
import org.eclipse.emf.teneo.util.StoreUtil;
/**
* Adds default annotations to an existing pamodel. Default annotations are added on the basis of the emf type
* information. It sets the default annotations according to the ejb3 spec.
*
* @author <a href="mailto:mtaal@elver.org">Martin Taal</a>
* @version $Revision: 1.25.2.4 $
*/
public class DefaultAnnotator {
/** The source of the annotations of extended metadata used by emf */
private static final String ANNOTATION_SOURCE_METADATA = "http:///org/eclipse/emf/ecore/util/ExtendedMetaData";
/** The annotation source used to read additional facets */
private static final String FACET_SOURCE_LIST = "http://facet.elver.org/List";
/** Unique facet name */
private static final String FACET_UNIQUE = "unique";
/** Index facet name */
private static final String FACET_INDEX = "indexed";
/** The logger */
protected static final Log log = LogFactory.getLog(DefaultAnnotator.class);
/** Inheritance Mapping, with convenience bools, these values are default */
private InheritanceType optionDefaultInheritanceMapping = InheritanceType.SINGLE_TABLE_LITERAL;
/** Add entity if not present or only handle entied eclasses */
private boolean optionAddEntityAnnotation = true;
/** Determines if always a join table is used for non-contained relations */
private boolean optionJoinTableForNonContainedAssociations = false;
/** Determines the join table naming strategy */
private String optionJoinTableNamingStrategy = null;
/** Set orphan delete on containment */
private boolean optionSetCascadeAllOnContainment = true;
/** Force eager containment */
private boolean optionFetchContainmentEagerly = false;
/** Current Temporal */
private TemporalType optionDefaultTemporal = null;
/** The default id feature name */
private String optionDefaultIDFeatureName = null;;
/** Option maximum column length */
private int optionMaximumSqlLength = -1;
/** Option ID feature as primary key */
private boolean optionIDFeatureAsPrimaryKey = true;
/** The sql naming strategy */
private SQLCaseStrategy optionSQLCaseStrategy = null;
/** The eclass qualify approach */
private EClassNameStrategy optionEClassNameStrategy = null;
/** Convenience link to pamodel factory */
private PannotationFactory aFactory = PannotationFactory.eINSTANCE;
/** The annotated model which is being processed */
private PAnnotatedModel annotatedModel;
/**
* The list of processed eclasses, is used to ensure that a superclass is done before a subclass
*/
private ArrayList processedAClasses = new ArrayList();
// todo: enable this again
// private HashMap entityNames;
/**
* Adds default annotations to a pamodel, the method is synchronized because globals are set. Not necessary because
* this class should always be used single threaded but okay.
*/
public synchronized void map(PAnnotatedModel annotatedModel, PersistenceOptions po) {
setLocalOptions(po);
annotatedModel.setInitialized(true);
this.annotatedModel = annotatedModel;
// computeEntityNames();
for (Iterator it = annotatedModel.getPaEPackages().iterator(); it.hasNext();) {
processPackage((PAnnotatedEPackage) it.next());
}
}
/**
* compute name of each entity TODO: enable this again private void computeEntityNames() { entityNames = new
* HashMap(); for (Iterator pi = annotatedModel.getPaEPackages().iterator(); pi.hasNext(); ) { for (Iterator ci =
* ((PAnnotatedEPackage) pi.next()).getPaEClasses().iterator(); ci.hasNext();) { PAnnotatedEClass paClass =
* (PAnnotatedEClass) ci.next(); EClass aClass = paClass.getAnnotatedEClass(); if (paClass.getEntity() != null &&
* paClass.getEntity().getName() != null) { entityNames.put(aClass, paClass.getEntity().getName()); } else if
* (paClass.getEmbeddable() == null && paClass.getMappedSuperclass() == null) { entityNames.put(aClass,
* aClass.getName()); } } } }
*/
/** Sets the options in a number of members */
protected void setLocalOptions(PersistenceOptions po) {
if (po.getInheritanceMapping() != null) {
switch (InheritanceType.get(po.getInheritanceMapping()).getValue()) {
case InheritanceType.JOINED:
optionDefaultInheritanceMapping = InheritanceType.JOINED_LITERAL;
log.debug("Option inheritance: joined");
break;
case InheritanceType.SINGLE_TABLE:
optionDefaultInheritanceMapping = InheritanceType.SINGLE_TABLE_LITERAL;
log.debug("Option inheritance: single");
break;
case InheritanceType.TABLE_PER_CLASS:
optionDefaultInheritanceMapping = InheritanceType.TABLE_PER_CLASS_LITERAL;
log.debug("Option inheritance: table per class");
break;
default:
throw new IllegalArgumentException("Inheritance mapping option: " + po.getInheritanceMapping()
+ " is not supported");
}
}
log.debug("Option: Default inheritance setting: " + optionDefaultInheritanceMapping.getName());
optionAddEntityAnnotation = po.isSetEntityAutomatically();
log.debug("Option: Automatically adding entity annotation: " + optionAddEntityAnnotation);
optionFetchContainmentEagerly = po.isFetchContainmentEagerly();
log.debug("Option: Eagerly load all containment relations: " + optionFetchContainmentEagerly);
optionSetCascadeAllOnContainment = po.isSetCascadeAllOnContainment();
log.debug("Option set cascade all on containment: " + optionSetCascadeAllOnContainment);
optionDefaultIDFeatureName = po.getDefaultIDFeatureName();
log.debug("Option default id feature name: " + optionDefaultIDFeatureName);
optionEClassNameStrategy = po.getEClassNameStrategy();
log.debug("Qualify EClass name option: " + optionEClassNameStrategy.getClass().getName());
optionMaximumSqlLength = po.getMaximumSqlNameLength();
log.debug("Maximum column length: " + optionMaximumSqlLength);
optionIDFeatureAsPrimaryKey = po.isIDFeatureAsPrimaryKey();
log.debug("ID Feature as primary key " + optionIDFeatureAsPrimaryKey);
optionJoinTableForNonContainedAssociations = po.isJoinTableForNonContainedAssociations();
log.debug("JoinTableForNonContainedAssociations " + po.isJoinTableForNonContainedAssociations());
optionSQLCaseStrategy = po.getSQLCaseStrategy();
log.debug("SQLCaseStrategy " + optionSQLCaseStrategy.getClass().getName());
optionJoinTableNamingStrategy = po.getJoinTableNamingStrategy();
log.debug("JoinTableNamingStrategy " + optionJoinTableNamingStrategy);
optionDefaultTemporal = TemporalType.get(po.getDefaultTemporalValue());
if (optionDefaultTemporal == null) {
throw new IllegalArgumentException("Temporal value not found: " + po.getDefaultTemporalValue());
}
if (optionJoinTableNamingStrategy == null || optionJoinTableNamingStrategy.compareToIgnoreCase("ejb3") != 0
&& optionJoinTableNamingStrategy.compareToIgnoreCase("unique") != 0) {
throw new IllegalArgumentException("JoinTable naming strategy option: " + optionJoinTableNamingStrategy
+ " is not supported");
}
}
/** Maps one epackage */
protected void processPackage(PAnnotatedEPackage aPackage) {
log.debug(">>>> Adding default annotations for EPackage " + aPackage.getAnnotatedElement().getName());
log.debug("Processing EDataTypes");
for (Iterator it = aPackage.getPaEDataTypes().iterator(); it.hasNext();) {
processEDataType((PAnnotatedEDataType) it.next());
}
log.debug("Processing EClasses");
for (Iterator it = aPackage.getPaEClasses().iterator(); it.hasNext();) {
processClass((PAnnotatedEClass) it.next());
}
}
/** Process an edatatype, does nothing in this impl. */
protected void processEDataType(PAnnotatedEDataType ped) {
}
/** Returns the annotated version of an EClass */
protected void processClass(PAnnotatedEClass aClass) {
if (aClass == null) {
throw new StoreAnnotationsException(
"Mapping Exception, no Annotated Class for EClass, "
+ "a common cause is that you did not register all EPackages in the DataStore/Helper Class. "
+ "When there are references between EClasses in different EPackages then they need to be handled in one DataStore/Helper Class.");
}
log.debug(" Adding default annotations for EClass: " + aClass.getAnnotatedElement().getName());
// do not process the document root
if (aClass.getAnnotatedEClass().getName().compareTo("DocumentRoot") == 0) {
return;
}
// check if already processed
if (processedAClasses.contains(aClass))
return;
// first do the superclasses
for (Iterator it = aClass.getAnnotatedEClass().getESuperTypes().iterator(); it.hasNext();) {
final EClass eclass = (EClass) it.next();
final PAnnotatedEClass superAClass = aClass.getPaModel().getPAnnotated(eclass);
if (superAClass == null) {
throw new StoreAnnotationsException(
"Mapping Exception, no Annotated Class for EClass: "
+ eclass.getName()
+ " a common cause is that you did not register all EPackages in the DataStore/Helper Class. "
+ "When there are references between EClasses in different EPackages then they need to be handled in one DataStore/Helper Class.");
}
processClass(superAClass);
}
final EClass eclass = (EClass) aClass.getAnnotatedElement();
final String transientSource = "http://ejb.elver.org/Transient";
if (aClass.getAnnotatedEClass().getEAnnotation(transientSource) != null) {
log.debug("EClass " + aClass.getAnnotatedEClass().getName() + " has transient annotation ");
return;
}
if (!optionAddEntityAnnotation && aClass.getEntity() == null && aClass.getEmbeddable() == null
&& aClass.getMappedSuperclass() == null) {
log.debug("Entities are not added automatically and this eclass: " + aClass.getAnnotatedEClass().getName()
+ " does not have an entity/embeddable/mappedsuperclass annotation.");
// NOTE: should the aClass be removed from its pamodel?
return;
}
processedAClasses.add(aClass);
// TODO: should eclasses with interface=true be mapped, i.e. have entity specified?
// add entity or set entity name
if (aClass.getEntity() == null && aClass.getEmbeddable() == null && aClass.getMappedSuperclass() == null) {
Entity entity = aFactory.createEntity();
entity.setEModelElement(eclass);
entity.setName(getEntityName(eclass));
aClass.setEntity(entity);
} else if (aClass.getEntity() != null && aClass.getEntity().getName() == null) {
aClass.getEntity().setName(getEntityName(eclass));
}
// get the inheritance from the supertype or use the global inheritance setting
// Note only an 'entitied' root gets an inheritance annotation. This is according to the spec.
final boolean isInheritanceRoot = isInheritanceRoot(aClass);
final InheritanceType inheritanceType;
if (aClass.getInheritance() != null) {
inheritanceType = aClass.getInheritance().getStrategy();
} else if (isInheritanceRoot) { // use default
Inheritance inheritance = aFactory.createInheritance();
inheritance.setStrategy(optionDefaultInheritanceMapping);
inheritance.setEModelElement(eclass);
aClass.setInheritance(inheritance);
inheritanceType = optionDefaultInheritanceMapping;
} else { // try to get from the root from the inheritance tree
Inheritance inheritance = getInheritanceFromSupers(aClass);
if (inheritance == null) { // not found use default
inheritanceType = optionDefaultInheritanceMapping;
} else {
inheritanceType = inheritance.getStrategy();
}
}
// add PrimaryKeyJoinColumn
if (!isInheritanceRoot && inheritanceType.equals(InheritanceType.JOINED_LITERAL)) {
ArrayList idFeatures = new ArrayList();
boolean firstDone = false;
EClass superClass = null;
for (Iterator it = aClass.getAnnotatedEClass().getESuperTypes().iterator(); it.hasNext();) {
final EClass eSuperClass = (EClass) it.next();
final PAnnotatedEClass aSuperClass = annotatedModel.getPAnnotated(eSuperClass);
if (!firstDone) {
superClass = eSuperClass;
}
final List superList = getIDFeaturesNames(aSuperClass);
idFeatures.removeAll(superList);
idFeatures.addAll(superList);
if (!idFeatures.isEmpty())
break;
}
for (Iterator it = idFeatures.iterator(); it.hasNext();) {
final String idFeature = (String) it.next();
final PrimaryKeyJoinColumn pkjc = aFactory.createPrimaryKeyJoinColumn();
final String colName = superClass.getName() + "_" + idFeature;
pkjc.setName(trunc(colName, true));
aClass.getPrimaryKeyJoinColumns().add(pkjc);
}
}
// add the table annotation or the name annotation of the table
// only do this if this is the root in case of singletable or when this is the joined table strategy
if (aClass.getTable() == null
&& ((isInheritanceRoot && inheritanceType.equals(InheritanceType.SINGLE_TABLE_LITERAL))
|| inheritanceType.equals(InheritanceType.JOINED_LITERAL) || inheritanceType
.equals(InheritanceType.TABLE_PER_CLASS_LITERAL))) {
final Table table = aFactory.createTable();
table.setEModelElement(eclass);
table.setName(trunc(getEntityName(eclass).replace('.', '_'), false));
aClass.setTable(table);
} else if (aClass.getTable() != null && aClass.getTable().getName() == null) {
aClass.getTable().setName(trunc(getEntityName(eclass), false));
}
// if the strategy is all classes of one hierarchy in one table and this is not the superclass
// then all properties should be nullable
// TODO when not all eclasses are entities then this computation is incorrect, the isInheritanceRoot
// should be the top most class in the hierarchy which is an entity
final boolean forceOptional = !isInheritanceRoot
&& inheritanceType.equals(InheritanceType.SINGLE_TABLE_LITERAL);
// For hibernate as well as jpox the discriminator column is only required for
// single table, the ejb3 spec does not make a clear statement about the requirement
// to also have a discriminator column for joined
if (isInheritanceRoot && aClass.getDiscriminatorColumn() == null
&& inheritanceType.equals(InheritanceType.SINGLE_TABLE_LITERAL)) {
// note defaults of primitive types are all defined in the model
final DiscriminatorColumn dc = aFactory.createDiscriminatorColumn();
dc.setEModelElement(eclass);
aClass.setDiscriminatorColumn(dc);
}
// add a discriminator value
if (aClass.getDiscriminatorValue() == null && inheritanceType.equals(InheritanceType.SINGLE_TABLE_LITERAL)) {
final DiscriminatorValue dv = aFactory.createDiscriminatorValue();
dv.setValue(getEntityName(eclass));
dv.setEModelElement(eclass);
aClass.setDiscriminatorValue(dv);
}
for (Iterator it = aClass.getPaEStructuralFeatures().iterator(); it.hasNext();) {
PAnnotatedEStructuralFeature aStructuralFeature = (PAnnotatedEStructuralFeature) it.next();
processEFeature(aStructuralFeature, forceOptional);
}
// Add default PkJoinColumns for SecondaryTables.
for (Iterator iter = aClass.getSecondaryTables().iterator(); iter.hasNext();) {
final SecondaryTable secondaryTable = (SecondaryTable) iter.next();
final EList pkJoinColumns = secondaryTable.getPkJoinColumns();
if (pkJoinColumns.size() == 0) {
// No PkJoinColumns configured for this secondary table, so populate with defaults based on the ID
// attributes of the primary table.
final List aIdAttributes = aClass.getPaIdAttributes();
for (Iterator iter2 = aIdAttributes.iterator(); iter2.hasNext();) {
PAnnotatedEAttribute aIdAttribute = (PAnnotatedEAttribute) iter2.next();
final PrimaryKeyJoinColumn pkJoinColumn = PannotationFactory.eINSTANCE.createPrimaryKeyJoinColumn();
pkJoinColumn.setName(trunc(aIdAttribute.getAnnotatedEAttribute().getName(), true));
pkJoinColumns.add(pkJoinColumn);
}
}
}
}
/** Process the features of the eclass */
protected void processEFeature(PAnnotatedEStructuralFeature aStructuralFeature, boolean forceOptional) {
EStructuralFeature eStructuralFeature = aStructuralFeature.getAnnotatedEStructuralFeature();
boolean errorOccured = true;
try {
// a feature is transient if:
// - transient is true and it is an eattribute or
// - transient is true and it does not have an opposite
// - transietn is true and it's opposite is not a containment relation
final boolean isTransient = eStructuralFeature.isTransient()
&& (eStructuralFeature instanceof EAttribute
|| ((EReference) eStructuralFeature).getEOpposite() == null
|| !((EReference) eStructuralFeature).getEOpposite().isContainment() || ((EReference) eStructuralFeature)
.getEOpposite().isTransient());
if (aStructuralFeature.getTransient() == null && (eStructuralFeature.isVolatile() || isTransient)) {
log.debug("Structural feature " + eStructuralFeature.getName()
+ " is transient, therefore adding transient annotation");
final Transient trans = aFactory.createTransient();
trans.setEModelElement(eStructuralFeature);
aStructuralFeature.setTransient(trans);
// note next statement will force continue
}
// do not do anything further for transients
// process transients further because they can be part of a featuremap, the specific mapper should
// handle transient
// Note that this means that transient features will still have additional annotations such as basic etc.
// if (aStructuralFeature.getTransient() != null) return;
if (aStructuralFeature instanceof PAnnotatedEAttribute) {
final PAnnotatedEAttribute aAttribute = (PAnnotatedEAttribute) aStructuralFeature;
if (((PAnnotatedEAttribute) aStructuralFeature).getVersion() != null)
return; // do not add more info
final Class instanceClass = eStructuralFeature.getEType().getInstanceClass();
boolean isMany = false;
// instanceClass will be null for enums
// Lob-annotated attributes must not be treated as one-to-many.
// eattributes with a hibernate type annotations should not be treated as a list
final String typeSource = "http://annotations.hibernate.org/Type";
if (instanceClass != null && aAttribute.getLob() == null
&& aAttribute.getAnnotatedEAttribute().getEAttributeType().getEAnnotation(typeSource) == null) {
isMany = eStructuralFeature.isMany() || instanceClass.isArray()
|| Collection.class.isAssignableFrom(instanceClass)
|| Set.class.isAssignableFrom(instanceClass) || List.class.isAssignableFrom(instanceClass);
isMany = isMany && !StoreUtil.isElementOfAGroup(eStructuralFeature);
}
if (isMany) {
processOneToManyAttribute(aAttribute, forceOptional);
} else {
processSingleAttribute(aAttribute, forceOptional);
}
if (aAttribute.getColumn() != null && aAttribute.getColumn().getName() == null) {
aAttribute.getColumn().setName(trunc(aAttribute.getAnnotatedEAttribute().getName(), true));
}
} else if (aStructuralFeature instanceof PAnnotatedEReference) {
final PAnnotatedEReference aReference = (PAnnotatedEReference) aStructuralFeature;
// detect the type of relation
// note using the emf model it can not be checked if a relation is a
// uni-manytoone (2.1.8.3.2) or a uni onetoone (2.1.8.3.1)
// neither can a uni-manytomany (2.1.8.5.2) be detected
// because there is no eopposite. However this can be
// specified manually, the system as a default will choose uni-manytoone
final EReference eReference = (EReference) aStructuralFeature.getAnnotatedElement();
final EReference eOpposite = eReference.getEOpposite();
// elements of a group are never multi-occurence because the multi-occurence is
// handled by the containing featuremap
final boolean isMany = eReference.isMany() && !StoreUtil.isElementOfAGroup(eReference);
final boolean isOppositeMany = eOpposite != null && eOpposite.isMany()
&& !StoreUtil.isElementOfAGroup(eOpposite);
final boolean mtmBidirectionalRelation = isMany && eOpposite != null && isOppositeMany;
final boolean mtmUnidirectionalRelation = isMany && eOpposite == null
&& aReference.getManyToMany() != null;
final boolean otmBidirectionalRelation = isMany && eOpposite != null && !isOppositeMany;
final boolean otmUnidirectionalRelation = isMany && eOpposite == null;
// note as a default if the system has to choose between oto uni or mto uni then it will
// place a mto
final boolean otoBidirectionalRelation = !isMany && eOpposite != null && !isOppositeMany;
final boolean otoUnidirectionalRelation = !isMany && eOpposite == null
&& (aReference.getOneToOne() != null || !aReference.getPrimaryKeyJoinColumns().isEmpty());
final boolean mtoBidirectionalRelation = !isMany && eOpposite != null && isOppositeMany;
final boolean mtoUnidirectionalRelation = !isMany && eOpposite == null && !otoUnidirectionalRelation;
if (mtmBidirectionalRelation) {
processBidirectionalManyToManyReference(aReference, forceOptional);
} else if (mtmUnidirectionalRelation) {
processUnidirectionalManyToManyReference(aReference, forceOptional);
} else if (otmBidirectionalRelation || otmUnidirectionalRelation) {
processOneToManyReference(aReference, forceOptional);
} else if (otoBidirectionalRelation || otoUnidirectionalRelation) {
processOneToOneReference(aReference, forceOptional);
} else if (mtoBidirectionalRelation) {
processManyToOneReference(aReference, forceOptional);
} else if (mtoUnidirectionalRelation) {
processManyToOneReference(aReference, forceOptional);
}
// handle column naming at this level
if (aReference.getColumn() != null && aReference.getColumn().getName() == null) {
aReference.getColumn().setName(trunc(eReference.getName(), true));
}
} else {
throw new IllegalArgumentException("This type of StructuralFeature is not supported: "
+ aStructuralFeature.getClass().getName());
}
errorOccured = false;
} finally {
// check that at least one ann was set
if (aStructuralFeature instanceof PAnnotatedEAttribute) {
PAnnotatedEAttribute pae = (PAnnotatedEAttribute) aStructuralFeature;
assert (errorOccured || pae.getBasic() != null || pae.getVersion() != null || pae.getId() != null
|| pae.getTransient() != null || pae.getOneToMany() != null);
} else {
PAnnotatedEReference par = (PAnnotatedEReference) aStructuralFeature;
assert (errorOccured || par.getTransient() != null || par.getOneToMany() != null
|| par.getManyToMany() != null || par.getManyToOne() != null || par.getOneToOne() != null);
}
}
}
/** Add default annotation to aAttribute: these are id, basic and enum */
protected void processSingleAttribute(PAnnotatedEAttribute aAttribute, boolean forceNullable) {
log.debug(" Adding default annotations for EAttribute " + aAttribute.getAnnotatedElement().getName());
final EAttribute eAttribute = (EAttribute) aAttribute.getAnnotatedElement();
// this is done before adding the id because an enumerated can also be an id
if (eAttribute.getEType() instanceof EEnum && aAttribute.getEnumerated() == null) {
final Enumerated enumerated = aFactory.createEnumerated();
enumerated.setValue(EnumType.STRING_LITERAL);
enumerated.setEModelElement(eAttribute);
aAttribute.setEnumerated(enumerated);
}
if (optionIDFeatureAsPrimaryKey && eAttribute.isID() && aAttribute.getId() == null) {
final Id id = aFactory.createId();
id.setEModelElement(eAttribute);
aAttribute.setId(id);
return; // after id do not add basic
} else if (aAttribute.getId() != null) {
return; // after id do not do basic
}
if (aAttribute.getTemporal() == null) {
Class clazz = eAttribute.getEAttributeType().getInstanceClass();
// clazz is hidden somewhere
if (clazz == null || Object.class.equals(clazz)) {
ArrayList eclassifiers = getItemTypes((EDataType) eAttribute.getEType());
for (Iterator it = eclassifiers.iterator(); it.hasNext();) {
EClassifier eclassifier = (EClassifier) it.next();
if (eclassifier.getInstanceClass() != null) {
clazz = eclassifier.getInstanceClass();
break;
}
}
}
if (clazz != null && Date.class.isAssignableFrom(clazz)) {
final Temporal temporal = aFactory.createTemporal();
temporal.setValue(optionDefaultTemporal);
aAttribute.setTemporal(temporal);
temporal.setEModelElement(eAttribute);
} else if (clazz != null && Calendar.class.isAssignableFrom(clazz)) {
final Temporal temporal = aFactory.createTemporal();
temporal.setValue(optionDefaultTemporal);
aAttribute.setTemporal(temporal);
temporal.setEModelElement(eAttribute);
}
}
if (aAttribute.getBasic() == null) {
// primitive defaults are set in the model itself
final Basic basic = aFactory.createBasic();
basic.setEModelElement(eAttribute);
// NOTE: the ejb3 spec says that for primitivie optional does not apply, this is
// confusing why having this then? If this applies then for each basic and nullable
// field a column annotation has to be added to force nullability
basic.setOptional(forceNullable || !eAttribute.isRequired() || eAttribute.isUnsettable());
aAttribute.setBasic(basic);
}
if (forceNullable) {
aAttribute.getBasic().setOptional(true);
}
if (aAttribute.getId() != null) {
aAttribute.getBasic().setOptional(false);
if (aAttribute.getColumn() != null && aAttribute.getColumn().isNullable()) {
log.warn("The column of a primary key property is null, this will often result in database errors!");
}
}
// decide if a column annotation should be added, this is done
// when the maxLength or length, totalDigits or fractionDigits are set
// and when no other column has been set
if (aAttribute.getColumn() == null) {
String maxLength = getExtendedMetaData(eAttribute, "maxLength");
if (maxLength == null) {
maxLength = getExtendedMetaData(eAttribute, "length");
}
final String totalDigits = getExtendedMetaData(eAttribute, "totalDigits");
final String fractionDigits = getExtendedMetaData(eAttribute, "fractionDigits");
if (maxLength != null || totalDigits != null || fractionDigits != null) {
final Column column = aFactory.createColumn();
if (maxLength != null) {
column.setLength(Integer.parseInt(maxLength)); // you'll find parse errors!
}
if (totalDigits != null) {
column.setPrecision(Integer.parseInt(totalDigits));
}
if (fractionDigits != null) {
column.setScale(Integer.parseInt(fractionDigits));
}
aAttribute.setColumn(column);
}
}
}
/** Handles a many EAttribute which is a list of simple types */
protected void processOneToManyAttribute(PAnnotatedEAttribute aAttribute, boolean forceNullable) {
final String logStr = aAttribute.getAnnotatedEAttribute().getName() + "/"
+ aAttribute.getAnnotatedEAttribute().getEContainingClass().getName();
log.debug("EAttribute " + logStr + " needs a onetomany");
final EAttribute eAttribute = (EAttribute) aAttribute.getAnnotatedElement();
OneToMany otm = aAttribute.getOneToMany();
final boolean otmWasSet = otm != null; // otm was set manually
if (otm == null) {
log.debug("One to many not present adding one");
otm = aFactory.createOneToMany();
aAttribute.setOneToMany(otm);
otm.setEModelElement(eAttribute);
if (optionFetchContainmentEagerly) {
otm.setFetch(FetchType.EAGER_LITERAL);
}
} else {
log.debug("One to many present adding default information if required");
}
// set cascade if not set
if (otm.getCascade().isEmpty())
otm.getCascade().add(CascadeType.ALL_LITERAL);
if (otm.getTargetEntity() == null || otm.getTargetEntity() == null) {
otm.setTargetEntity(getTargetTypeName(aAttribute));
}
if (aAttribute.getJoinTable() == null) {
// note not optional because lists of simple types are embedded
addJoinColumns(aAttribute.getPaEClass(), aAttribute.getAnnotatedEAttribute(), aAttribute, FeatureMapUtil
.isFeatureMap(eAttribute), true); // with featuremap optional is true
}
// set unique and indexed
if (!otmWasSet) {
log
.debug("Setting indexed and unique on otm from eAttribute.isOrdered/isUnique because otm was not set manually");
otm.setIndexed(eAttribute.isOrdered());
otm.setUnique(eAttribute.isUnique());
EAnnotation ean = aAttribute.getAnnotatedElement().getEAnnotation(FACET_SOURCE_LIST);
if (ean != null && ean.getDetails() != null) {
if (ean.getDetails().get(FACET_INDEX) != null) {
log.warn("Setting indexed from deprecated annotation: " + FACET_INDEX);
otm.setIndexed(((String) ean.getDetails().get(FACET_INDEX)).compareToIgnoreCase("true") == 0);
}
if (ean.getDetails().get(FACET_UNIQUE) != null) {
log.warn("Setting unique from deprecated annotation: " + FACET_UNIQUE);
otm.setUnique(((String) ean.getDetails().get(FACET_UNIQUE)).compareToIgnoreCase("true") == 0);
}
}
ean = aAttribute.getAnnotatedElement().getEAnnotation("http://annotation.elver.org/Indexed");
if (ean != null && ean.getDetails() != null) {
if (ean.getDetails().get("value") != null) {
log.warn("Setting indexed from deprecated annotation: http://annotation.elver.org/Indexed");
otm.setIndexed(((String) ean.getDetails().get("value")).compareToIgnoreCase("true") == 0);
}
}
ean = aAttribute.getAnnotatedElement().getEAnnotation("http://annotation.elver.org/Unique");
if (ean != null && ean.getDetails() != null) {
if (ean.getDetails().get("value") != null) {
log.warn("Setting indexed from deprecated annotation: http://annotation.elver.org/Unique");
otm.setUnique(((String) ean.getDetails().get("value")).compareToIgnoreCase("true") == 0);
}
}
}
}
/** Returns the type name of a many attribute */
protected String getTargetTypeName(PAnnotatedEAttribute aAttribute) {
final EAttribute eAttribute = aAttribute.getAnnotatedEAttribute();
// check on equality on object.class is used for listunion simpleunions
final Class instanceClass = eAttribute.getEAttributeType().getInstanceClass();
if (instanceClass != null && !Object.class.equals(instanceClass) && !List.class.equals(instanceClass)) {
if (instanceClass.isArray()) {
// get rid of the [] at the end
return eAttribute.getEType().getInstanceClassName().substring(0,
eAttribute.getEType().getInstanceClassName().length() - 2);
}
return instanceClass.getName();
} else {
// the type is hidden somewhere deep get it
// the edatatype is the java.util.list
// it has an itemType which is the name of the element edatatype
// which contains the instanceclass
// takes also into account inheritance between datatypes
// NOTE the otm.targetentity can consist of a comma delimited list of target
// entities this is required for listunion types but is not according to the ejb3 spec!
ArrayList eclassifiers = getItemTypes((EDataType) eAttribute.getEType());
if (eclassifiers.size() > 0) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < eclassifiers.size(); i++) {
final EClassifier eclassifier = (EClassifier) eclassifiers.get(i);
if (i > 0)
result.append(",");
result.append(eclassifier.getInstanceClassName());
}
return result.toString();
} else {
return Object.class.getName();
}
}
}
/**
* Adds default annotations to a onetomany ereference, this method handles both the uni- and the bidirectional case
*/
protected void processOneToManyReference(PAnnotatedEReference aReference, boolean forceOptional) {
final String logStr = aReference.getAnnotatedEReference().getName() + "/"
+ aReference.getAnnotatedEReference().getEContainingClass().getName();
if (aReference.getManyToMany() != null || aReference.getOneToOne() != null || aReference.getManyToOne() != null) {
throw new StoreMappingException("The feature/eclass " + logStr + " should be a OneToMany but "
+ "it already has a ManyToMany, OneToOne or ManyToOne annotation");
}
final EReference eReference = (EReference) aReference.getAnnotatedElement();
OneToMany otm = aReference.getOneToMany();
final boolean otmWasSet = otm != null; // otm was set manually
if (otm == null) {
log.debug("EReference + " + logStr + " does not have a onetomany annotation, adding one");
otm = aFactory.createOneToMany();
aReference.setOneToMany(otm);
otm.setEModelElement(eReference);
if (eReference.isContainment() && optionFetchContainmentEagerly) {
otm.setFetch(FetchType.EAGER_LITERAL);
}
} else {
log.debug("EReference + " + logStr + " has onetomany, check if defaults should be set");
}
if (otm.getMappedBy() == null && eReference.getEOpposite() != null) {
otm.setMappedBy(eReference.getEOpposite().getName());
}
setCascade(otm.getCascade(), eReference.isContainment());
// NOTE Sometimes EMF generated getters/setters have a
// very generic type (EObject), if the type can be derived here then this should
// be added here
if (otm.getTargetEntity() == null) {
otm.setTargetEntity(getEntityName(eReference.getEReferenceType()));
}
// set unique and indexed
if (!otmWasSet) {
log.debug("Setting indexed and unique from ereference because otm was not set manually!");
otm.setIndexed(eReference.isOrdered());
otm.setUnique(eReference.isUnique());
if (aReference.getAnnotatedEReference().getEOpposite() != null) {
log.debug("Setting unique because is bidirectional (has eopposite) otm");
otm.setUnique(true);
}
} else if (aReference.getAnnotatedEReference().getEOpposite() != null) {
log.warn("The EReference " + logStr
+ " is not unique (allows duplicates) but it is bi-directional, this is not logical");
}
// only use a jointable if the relation is non unique
final boolean isEObject = EClassNameStrategy.EOBJECT_ECLASS_URI.compareTo(otm.getTargetEntity()) == 0;
if (isEObject || // in case of eobject always a join table is required
(optionJoinTableForNonContainedAssociations && !eReference.isContainment()) || !otm.isUnique()) {
JoinTable joinTable = aReference.getJoinTable();
if (joinTable == null) {
joinTable = aFactory.createJoinTable();
aReference.setJoinTable(joinTable);
}
joinTable.setEModelElement(eReference);
// see remark in manytomany about naming of jointables
if (joinTable.getName() == null) {
if (!isEObject && optionJoinTableNamingStrategy.compareToIgnoreCase("ejb3") == 0) {
final String jTableName = getEntityName(eReference.getEContainingClass()) + "_"
+ getEntityName(eReference.getEReferenceType());
joinTable.setName(trunc(jTableName, false));
} else {
AssertUtil.assertTrue("option optionJoinTableNamingStrategy " + optionJoinTableNamingStrategy
+ " not supported", isEObject
|| optionJoinTableNamingStrategy.compareToIgnoreCase("unique") == 0);
final String jTableName = getEntityName(eReference.getEContainingClass()) + "_"
+ eReference.getName();
joinTable.setName(trunc(jTableName, false));
}
}
// note joincolumns in jointable can be generated automatically by hib/jpox.
} else if (aReference.getJoinColumns() == null || aReference.getJoinColumns().isEmpty()) { // add
// joincolum(s)
// the name of this eclass, the name of the property on the other side
if (aReference.getAnnotatedEReference().getEOpposite() != null) {
// get opposite
EReference opposite = aReference.getAnnotatedEReference().getEOpposite();
addJoinColumns(aReference.getPaModel().getPAnnotated(opposite.getEContainingClass()), opposite,
aReference, aReference.getEmbedded() == null, true);
} else { // no prop on the other side just use this one
addJoinColumns(aReference.getPaEClass(), aReference.getAnnotatedEReference(), aReference, aReference
.getEmbedded() == null, true);
}
}
}
/** Adds default annotations to a bidirectional many to many ereference */
protected void processBidirectionalManyToManyReference(PAnnotatedEReference aReference, boolean forceOptional) {
final String featureLogStr = aReference.getAnnotatedEReference().getName() + "/"
+ aReference.getAnnotatedEReference().getEContainingClass().getName();
if (aReference.getOneToMany() != null || aReference.getOneToOne() != null || aReference.getManyToOne() != null) {
throw new StoreMappingException("The feature/eclass " + featureLogStr + " should be a ManyToMany but "
+ "it already has a OneToMany, OneToOne or ManyToOne annotation");
}
final EReference eReference = (EReference) aReference.getAnnotatedElement();
final EReference eOpposite = eReference.getEOpposite();
assert (eOpposite != null && eOpposite.isMany());
ManyToMany mtm = aReference.getManyToMany();
final boolean mtmWasSet = mtm != null; // mtm was set manually
if (mtm == null) {
log.debug("Adding manytomany annotations to ereference: " + featureLogStr);
mtm = aFactory.createManyToMany();
aReference.setManyToMany(mtm);
mtm.setEModelElement(eReference);
} else {
log.debug("ManyToMany present check if default information should be added");
}
setCascade(mtm.getCascade(), eReference.isContainment());
if (mtm.getTargetEntity() == null) {
mtm.setTargetEntity(getEntityName(eReference.getEReferenceType()));
}
// determine where to place the jointable annotation and where to place the mappedby
// use a certain logic to determine as each is only set on one side
// note that the join is always set on the other side of mapped by!
// note that we can not do setJoinHere = !setMappedByHere because there are situations
// that even for mtm no mappedby is set on either side, nl. in case of containment
// also check if the other side has a (manual) manytomany with mappedby set
// bugzilla: 164808
final PAnnotatedEReference otherPA = aReference.getPaModel().getPAnnotated(eOpposite);
if (mtm.getMappedBy() == null && setMappedBy(eReference)
&& (otherPA.getManyToMany() == null || otherPA.getManyToMany().getMappedBy() == null)) {
mtm.setMappedBy(eOpposite.getName());
}
JoinTable joinTable = aReference.getJoinTable();
if (joinTable == null) {
joinTable = aFactory.createJoinTable();
aReference.setJoinTable(joinTable);
}
joinTable.setEModelElement(eReference);
// set unique and indexed
if (!mtmWasSet) {
log
.debug("Setting indexed and unique from ereference.isOrdered/isUnique because mtm was not set manually!");
mtm.setIndexed(eReference.isOrdered());
}
EAnnotation ean = aReference.getAnnotatedElement().getEAnnotation(FACET_SOURCE_LIST);
if (ean != null && ean.getDetails() != null) {
if (ean.getDetails().get(FACET_INDEX) != null) {
log.warn("Setting indexed from deprecated annotation: " + FACET_INDEX);
mtm.setIndexed(((String) ean.getDetails().get(FACET_INDEX)).compareTo("true") == 0);
}
}
ean = aReference.getAnnotatedElement().getEAnnotation("http://annotation.elver.org/Indexed");
if (ean != null && ean.getDetails() != null) {
if (ean.getDetails().get("value") != null) {
log.warn("Setting indexed from deprecated annotation: http://annotation.elver.org/Indexed");
mtm.setIndexed(((String) ean.getDetails().get("value")).compareToIgnoreCase("true") == 0);
}
}
// NOTE that the ejb3 spec states that the jointable should be the concatenation of the
// tablenames of the owning entities with an underscore, this will quickly lead to nameclashes
// in the case there is more than one relation between two classes. This can be pretty likely
// if the inheritance strategy is single_table.
// now possibility to use a different naming strategy
if (joinTable.getName() == null) {
// In case the reference is not indexed then one join table can be used for both sides
// If indexed then separate join tables should be used.
final String jTableName;
if (optionJoinTableNamingStrategy.compareToIgnoreCase("ejb3") == 0) {
if (mtm.isIndexed()) {
jTableName = getEntityName(eReference.getEContainingClass()) + "_"
+ getEntityName(eOpposite.getEContainingClass());
} else {
if (compareNames(eReference, eOpposite)) {
jTableName = getEntityName(eOpposite.getEContainingClass()) + "_"
+ getEntityName(eReference.getEContainingClass());
} else {
jTableName = getEntityName(eReference.getEContainingClass()) + "_"
+ getEntityName(eOpposite.getEContainingClass());
}
}
} else {
AssertUtil.assertTrue("option optionJoinTableNamingStrategy " + optionJoinTableNamingStrategy
+ " not supported", optionJoinTableNamingStrategy.compareToIgnoreCase("unique") == 0);
if (mtm.isIndexed()) {
jTableName = getEntityName(eReference.getEContainingClass()) + "_" + eReference.getName();
} else {
if (compareNames(eReference, eOpposite)) {
jTableName = getEntityName(eOpposite.getEContainingClass()) + "_" + eOpposite.getName();
} else {
jTableName = getEntityName(eReference.getEContainingClass()) + "_" + eReference.getName();
}
}
}
joinTable.setName(trunc(jTableName, false));
}
if (/* joinTable.getJoinColumns() == null || */joinTable.getJoinColumns().size() == 0) { // no joincolumns,
// so
// add them
// if (joinTable.getJoinColumns() == null) joinTable.getJoinColumns().addAll(aFactory.createJoinColumns());
joinTable.getJoinColumns().addAll(getJoinColumns(aReference.getPaEClass(), eReference, false, false, true));
}
if (/* joinTable.getInverseJoinColumns() == null || */joinTable.getInverseJoinColumns().size() == 0) { // no
// inversejoincolumns,
// so
// add
// them
// if (joinTable.getInverseJoinColumns() == null)
// joinTable.setInverseJoinColumns(aFactory.createJoinColumns());
joinTable.getInverseJoinColumns().addAll(
getJoinColumns(annotatedModel.getPAnnotated(eOpposite.getEContainingClass()), eOpposite, false,
false, true));
}
}
/** Adds default annotations to a unidirectional many to many ereference */
protected void processUnidirectionalManyToManyReference(PAnnotatedEReference aReference, boolean forceOptional) {
final String featureLogStr = aReference.getAnnotatedEReference().getName() + "/"
+ aReference.getAnnotatedEReference().getEContainingClass().getName();
if (aReference.getOneToMany() != null || aReference.getOneToOne() != null || aReference.getManyToOne() != null) {
throw new StoreMappingException("The feature/eclass " + featureLogStr + " should be a ManyToMany but "
+ "it already has a OneToMany, OneToOne or ManyToOne annotation");
}
final EReference eReference = (EReference) aReference.getAnnotatedElement();
final ManyToMany mtm = aReference.getManyToMany();
log.debug("ManyToMany present check if default information should be added");
mtm.setEModelElement(eReference);
setCascade(mtm.getCascade(), eReference.isContainment());
if (mtm.getTargetEntity() == null) {
mtm.setTargetEntity(getEntityName(eReference.getEReferenceType()));
}
// with a unidirectional mtm the join is always placed here
JoinTable joinTable = aReference.getJoinTable();
if (joinTable == null) {
joinTable = aFactory.createJoinTable();
aReference.setJoinTable(joinTable);
}
joinTable.setEModelElement(eReference);
// note that here not the eclass name is used for the opposite side but the name of the targetentity
// because that's the one which is known here
if (joinTable.getName() == null) {
if (optionJoinTableNamingStrategy.compareToIgnoreCase("ejb3") == 0) {
final String oppName = mtm.getTargetEntity();
final String jTableName = getEntityName(eReference.getEContainingClass()) + "_" + oppName;
joinTable.setName(trunc(jTableName, false));
} else {
AssertUtil.assertTrue("option optionJoinTableNamingStrategy " + optionJoinTableNamingStrategy
+ " not supported", optionJoinTableNamingStrategy.compareToIgnoreCase("unique") == 0);
final String jTableName = getEntityName(eReference.getEContainingClass()) + "_" + eReference.getName();
joinTable.setName(trunc(jTableName, false));
}
}
if (joinTable.getJoinColumns() == null) {
joinTable.getJoinColumns().addAll(
getJoinColumns(aReference.getPaEClass(), eReference, forceOptional, false, true));
}
}
/** Adds default annotations for a one to one reference */
protected void processOneToOneReference(PAnnotatedEReference aReference, boolean forceOptional) {
final String logStr = aReference.getAnnotatedEReference().getName() + "/"
+ aReference.getAnnotatedEReference().getEContainingClass().getName();
if (aReference.getOneToMany() != null || aReference.getManyToMany() != null
|| aReference.getManyToOne() != null) {
throw new StoreMappingException("The feature/eclass " + logStr + " should be a OneToOne but "
+ "it already has a OneToMany, ManyToMany or ManyToOne annotation");
}
final EReference eReference = (EReference) aReference.getAnnotatedElement();
OneToOne oto = aReference.getOneToOne();
if (oto == null) {
log.debug("EReference + " + logStr + " does not have a onetoone annotation, adding one");
oto = aFactory.createOneToOne();
aReference.setOneToOne(oto);
oto.setOptional(forceOptional || !eReference.isRequired() || eReference.isUnsettable());
oto.setEModelElement(eReference);
} else {
log.debug("EReference + " + logStr + " has an onetoone annotation setting defaults if required");
}
if (forceOptional)
oto.setOptional(true);
// determine where to put the mapped-by
if (oto.getMappedBy() == null && setMappedBy(eReference)) { // only works with different names
oto.setMappedBy(eReference.getEOpposite().getName());
}
setCascade(oto.getCascade(), eReference.isContainment());
// Note: Sometimes EMF generated getters/setters have a
// very generic type (EObject), if the type can be derived here then this should
// be added here
if (oto.getTargetEntity() == null) {
oto.setTargetEntity(getEntityName(eReference.getEReferenceType()));
}
}
/** Handles many to one for bidirectional and unidirectional cases */
protected void processManyToOneReference(PAnnotatedEReference aReference, boolean forceOptional) {
final String logStr = aReference.getAnnotatedEReference().getName() + "/"
+ aReference.getAnnotatedEReference().getEContainingClass().getName();
if (aReference.getOneToMany() != null || aReference.getManyToMany() != null || aReference.getOneToOne() != null) {
throw new StoreMappingException("The feature/eclass " + logStr + " should be a ManyToOne but "
+ "it already has a OneToMany, ManyToMany or OneToOne annotation");
}
final EReference eReference = (EReference) aReference.getAnnotatedElement();
ManyToOne mto = aReference.getManyToOne();
if (mto == null) {
log.debug("EReference + " + logStr + " does not have a manytoone annotation, adding one");
mto = aFactory.createManyToOne();
aReference.setManyToOne(mto);
mto.setOptional(forceOptional || !eReference.isRequired() || eReference.isUnsettable()
|| eReference.getEOpposite() != null);
mto.setEModelElement(eReference);
} else {
log.debug("EReference + " + logStr + " does have a manytoone annotation, using it");
log.debug("Setting optional because of inheritance mapping, this is a subclass");
if (forceOptional)
mto.setOptional(forceOptional);
}
setCascade(mto.getCascade(), eReference.isContainment());
// NOTE: Sometimes EMF generated getters/setters have a
// very generic type (EObject), if the type can be derived here then this should
// be added here
if (mto.getTargetEntity() == null) {
mto.setTargetEntity(getEntityName(eReference.getEReferenceType()));
}
// create a set of joincolumns, note that if this is a two-way relation then
// the other side will use the name of the ereference as second parameter,
// matching the joincolumns on the other side
if (aReference.getJoinColumns() == null || aReference.getJoinColumns().isEmpty()) {
// the name of the joincolumns is defined by the name of the other entity and its primary key fields
final PAnnotatedEClass aClass;
if (eReference.getEOpposite() == null) {
aClass = aReference.getPaModel().getPAnnotated(eReference.getEReferenceType());
} else {
aClass = aReference.getPaEClass();
}
if (aClass != null) { // aClass == null when the reference it to a high level type such as EObject
// note that the joincolumns are set to not insertable/updatable if
// this is a bidirectional relation without a join table, in that case the other side
// controls the columns
addJoinColumns(aClass, aReference.getAnnotatedEReference(), aReference, mto.isOptional(), eReference
.getEOpposite() == null
&& aReference.getJoinTable() == null);
}
}
}
/** Creates a set of joincolumns for a reference to the annotated eclass */
private void addJoinColumns(PAnnotatedEClass aClass, EStructuralFeature esf, PAnnotatedEStructuralFeature aFeature,
boolean optional, boolean isUpdateInsertable) {
aFeature.getJoinColumns().addAll(getJoinColumns(aClass, esf, optional, true, isUpdateInsertable));
}
/** Return a list of join columns */
private List getJoinColumns(PAnnotatedEClass aClass, EStructuralFeature esf, boolean optional,
boolean doUseFeatureName, boolean isUpdateInsertable) {
final List result = new ArrayList();
final List names = getIDFeaturesNames(aClass);
boolean useFeatureName = doUseFeatureName;
if (esf.getEType() == aClass.getAnnotatedEClass()) {
useFeatureName = true;
}
for (Iterator it = names.iterator(); it.hasNext();) {
String name = (String) it.next();
JoinColumn jc = aFactory.createJoinColumn();
final String jcName;
if (useFeatureName) {
jcName = aClass.getAnnotatedEClass().getName() + "_" + esf.getName() + "_" + name;
} else {
jcName = aClass.getAnnotatedEClass().getName() + "_" + name;
}
jc.setName(trunc(jcName, true));
jc.setNullable(optional);
jc.setUpdatable(isUpdateInsertable);
jc.setInsertable(isUpdateInsertable);
result.add(jc);
}
return result;
}
/** Returns true if this is the root of the inheritancetree which is persisted */
private boolean isInheritanceRoot(PAnnotatedEClass aClass) {
if (aClass.getMappedSuperclass() != null) {
return false;
}
if (aClass.getTransient() != null) {
return false;
}
for (Iterator it = aClass.getAnnotatedEClass().getESuperTypes().iterator(); it.hasNext();) {
PAnnotatedEClass superAClass = aClass.getPaModel().getPAnnotated((EClass) it.next());
if (superAClass != null && superAClass.getMappedSuperclass() == null
&& processedAClasses.contains(superAClass)) {
return false;
}
// and go up one level, can be used to skip non-entities in the structure
if (isInheritanceRoot(superAClass)) {
return false;
}
}
return true;
}
/** Returns the inheritance of the passed annotated class or from one of its super annotated class */
private Inheritance getInheritanceFromSupers(PAnnotatedEClass childPA) {
if (childPA.getInheritance() != null && processedAClasses.contains(childPA))
return childPA.getInheritance();
final EClass eChild = childPA.getAnnotatedEClass();
final List supers = eChild.getESuperTypes();
for (Iterator it = supers.iterator(); it.hasNext();) {
final EClass eSuper = (EClass) it.next();
final PAnnotatedEClass pae = annotatedModel.getPAnnotated(eSuper);
if (pae != null) {
final Inheritance inheritance = getInheritanceFromSupers(pae);
if (inheritance != null) {
return inheritance;
}
}
}
return null;
}
/** Walks up a edatatype inheritance structure to find the itemType */
private ArrayList getItemTypes(EDataType eDataType) {
final ArrayList result = new ArrayList();
if (eDataType == null)
return result;
final String itemType = getEAnnotationValue(eDataType, ANNOTATION_SOURCE_METADATA, "itemType");
if (itemType != null) {
result.add(getEClassifier(eDataType.getEPackage(), itemType));
return result;
}
final String memberTypes = getEAnnotationValue(eDataType, ANNOTATION_SOURCE_METADATA, "memberTypes");
if (memberTypes != null) {
String[] mtypes = memberTypes.split(" ");
for (int i = 0; i < mtypes.length; i++) {
EClassifier eclassifier = getEClassifier(eDataType.getEPackage(), mtypes[i]);
result.addAll(getItemTypes((EDataType) eclassifier));
}
return result;
}
final String baseType = getEAnnotationValue(eDataType, ANNOTATION_SOURCE_METADATA, "baseType");
if (baseType != null) {
final ArrayList tmpResult = getItemTypes((EDataType) getEClassifier(eDataType.getEPackage(), baseType));
if (tmpResult.size() > 0) {
result.addAll(tmpResult);
return result;
}
}
if (!Object.class.equals(eDataType.getInstanceClass())) {
result.add(eDataType);
}
return result;
}
/** Returns the eclassifier using either the name of the eclassifier or the name element */
private EClassifier getEClassifier(EPackage epackage, String searchName) {
final Iterator it = epackage.getEClassifiers().iterator();
while (it.hasNext()) {
EClassifier eclassifier = (EClassifier) it.next();
if (eclassifier.getName().compareTo(searchName) == 0)
return eclassifier;
String nameAnnotation = getEAnnotationValue(eclassifier, ANNOTATION_SOURCE_METADATA, "name");
if (nameAnnotation != null && searchName.compareTo(nameAnnotation) == 0) {
return eclassifier;
}
}
return null;
}
/** Checks if the cascade should be set in the cascade list, is only done if the list is empty */
private void setCascade(List cascadeList, boolean isContainment) {
if (!cascadeList.isEmpty())
return;
if (isContainment && !optionSetCascadeAllOnContainment) {
cascadeList.add(CascadeType.REMOVE_LITERAL);
cascadeList.add(CascadeType.MERGE_LITERAL);
cascadeList.add(CascadeType.PERSIST_LITERAL);
cascadeList.add(CascadeType.REFRESH_LITERAL);
} else if (isContainment) {
cascadeList.add(CascadeType.ALL_LITERAL);
} else {
cascadeList.add(CascadeType.MERGE_LITERAL);
cascadeList.add(CascadeType.PERSIST_LITERAL);
cascadeList.add(CascadeType.REFRESH_LITERAL);
}
}
/** Determines if mapped by should be set */
private boolean setMappedBy(EReference eReference) {
// only set in two way relation
// if has not been set on the other side (mappedtoFields)
// if not a containment relation, containment relations are handled differently
// the other side may neither be containment
final EReference eOpposite = eReference.getEOpposite();
if (eOpposite == null)
return false;
return compareNames(eReference, eOpposite);
// &&
// !eReference.isContainment() && !eOpposite.isContainment();
}
/**
* Determines where to place a certain annotation/characteristic, this is done by comparing names..
*/
private boolean compareNames(EReference here, EReference there) {
final String nameHere = here.eClass().getName() + here.getName();
final String nameThere = there.eClass().getName() + there.getName();
assert (nameHere.compareTo(nameThere) != 0);
return nameHere.compareTo(nameThere) > 0;
}
/** Returns the entity name of the eclass */
private String getEntityName(EClass eclass) {
if (eclass == null) {
throw new IllegalArgumentException(
"Passed eclass is null."
+ "This can occur if epackages which refer to eachother are placed in different ecore/xsd files "
+ "and they are not read using one resource set. The reference from one epackage to another must be "
+ "resolvable by EMF.");
}
final PAnnotatedEClass aclass = annotatedModel.getPAnnotated(eclass);
if (aclass != null && aclass.getEntity() != null && aclass.getEntity().getName() != null) {
return aclass.getEntity().getName();
}
return optionEClassNameStrategy.toUniqueName(eclass);
}
/** Get a specific extended metadate */
private String getExtendedMetaData(EAttribute eAttribute, String key) {
String value = getEAnnotationValue(eAttribute, "http:///org/eclipse/emf/ecore/util/ExtendedMetaData", key);
if (value == null) {
value = getEAnnotationValue(eAttribute.getEAttributeType(), "http:///org/eclipse/emf/ecore/util/ExtendedMetaData", key);
}
return value;
}
/** Returns the value of an annotation with a certain key */
private String getEAnnotationValue(EModelElement eModelElement, String source, String key) {
final EAnnotation eAnnotation = eModelElement.getEAnnotation(source);
if (eAnnotation == null)
return null;
return (String) eAnnotation.getDetails().get(key);
}
/**
* Returns the list of names of id props of the eclass, walks the inheritance tree to find the id feature, if none
* is found then the
*/
private List getIDFeaturesNames(PAnnotatedEClass aClass) {
final List list = getIDFeaturesNamesRecurse(aClass);
// See, 172756
if (list.isEmpty()) {
list.add(optionDefaultIDFeatureName);
}
return list;
}
/** Internal will walk the inheritance tree to find the id feature */
private List getIDFeaturesNamesRecurse(PAnnotatedEClass aClass) {
final ArrayList list = new ArrayList();
for (Iterator it = aClass.getAnnotatedEClass().getEStructuralFeatures().iterator(); it.hasNext();) {
EStructuralFeature feature = (EStructuralFeature) it.next();
PAnnotatedEStructuralFeature aStructuralFeature = aClass.getPaModel().getPAnnotated(feature);
if (aStructuralFeature instanceof PAnnotatedEAttribute) {
final PAnnotatedEAttribute aAttribute = (PAnnotatedEAttribute) aStructuralFeature;
final String attrName = aAttribute.getAnnotatedEAttribute().getName();
if (aAttribute.getId() != null && !list.contains(attrName)) {
list.add(attrName);
}
}
}
if (list.isEmpty() && aClass.getAnnotatedEClass().getESuperTypes().size() > 0) {
for (Iterator it = aClass.getAnnotatedEClass().getESuperTypes().iterator(); it.hasNext();) {
final EClass eClass = (EClass) it.next();
final PAnnotatedEClass aSuperClass = annotatedModel.getPAnnotated(eClass);
if (aSuperClass != null) {
final List superList = getIDFeaturesNamesRecurse(aSuperClass);
list.removeAll(superList);
list.addAll(superList);
}
if (!list.isEmpty()) {
return list;
}
}
if (!list.isEmpty()) {
return list;
}
// fall through
}
return list;
}
/** Utilit method to truncate a column name */
private String trunc(String truncName, boolean truncSuffix) {
final String correctedName = truncName.replace('.', '_');
if (optionMaximumSqlLength == -1)
return optionSQLCaseStrategy.convert(correctedName);
if (correctedName.length() < optionMaximumSqlLength)
return optionSQLCaseStrategy.convert(correctedName);
// truncate the part before the last _ because this is often the suffix
final int underscore = correctedName.lastIndexOf('_');
if (truncSuffix && underscore != -1 && underscore > 0) {
final String usStr = correctedName.substring(underscore);
if ((optionMaximumSqlLength - usStr.length()) < 0)
return optionSQLCaseStrategy.convert(correctedName);
return optionSQLCaseStrategy.convert(correctedName.substring(0, optionMaximumSqlLength - usStr.length())
+ usStr);
}
return optionSQLCaseStrategy.convert(correctedName.substring(0, optionMaximumSqlLength));
}
}