/**
* <copyright>
*
* Copyright (c) 2005, 2006, 2007, 2008 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: EClassAnnotator.java,v 1.12 2008/08/11 20:41:47 mtaal Exp $
*/
package org.eclipse.emf.teneo.annotations.mapper;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.teneo.PersistenceOptions;
import org.eclipse.emf.teneo.annotations.StoreAnnotationsException;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEClass;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEStructuralFeature;
import org.eclipse.emf.teneo.annotations.pannotation.DiscriminatorColumn;
import org.eclipse.emf.teneo.annotations.pannotation.DiscriminatorType;
import org.eclipse.emf.teneo.annotations.pannotation.DiscriminatorValue;
import org.eclipse.emf.teneo.annotations.pannotation.Entity;
import org.eclipse.emf.teneo.annotations.pannotation.Inheritance;
import org.eclipse.emf.teneo.annotations.pannotation.InheritanceType;
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.extension.ExtensionPoint;
import org.eclipse.emf.teneo.mapping.strategy.StrategyUtil;
/**
* Sets the annotation on an eclass.
*
* @author <a href="mailto:mtaal@elver.org">Martin Taal</a>
* @version $Revision: 1.12 $
*/
public class EClassAnnotator extends AbstractAnnotator implements ExtensionPoint {
// The logger
protected static final Log log = LogFactory.getLog(EClassAnnotator.class);
private InheritanceType optionDefaultInheritanceMapping = InheritanceType.SINGLE_TABLE;
// The list of processed eclasses, is used to ensure that a superclass is done before a subclass
private final ArrayList<PAnnotatedEClass> processedAClasses = new ArrayList<PAnnotatedEClass>();
private EFeatureAnnotator eFeatureAnnotator = null;
/**
* Returns the annotated version of an EClass, Returns false if no efeatures of this eclass
* should be annotated, true if its features can be annotated.
*/
protected boolean annotate(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.");
}
final EClass eclass = (EClass) aClass.getModelElement();
// check if already processed
if (processedAClasses.contains(aClass)) {
return false;
}
// do not process the document root
if (ExtendedMetaData.INSTANCE.isDocumentRoot(eclass)) {
return false;
}
log.debug("Creating mapping for eclass " + eclass.getName());
// first do the superclasses
for (EClass superEclass : aClass.getModelEClass().getESuperTypes()) {
final PAnnotatedEClass superAClass = aClass.getPaModel().getPAnnotated(superEclass);
if (superAClass == null) {
throw new StoreAnnotationsException(
"Mapping Exception, no Annotated Class for EClass: " +
superEclass.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.");
}
if (!processedAClasses.contains(superAClass)) {
annotate(superAClass);
}
}
log.debug(" Adding default annotations for EClass: " + aClass.getModelElement().getName());
processedAClasses.add(aClass);
log.debug("Setting the superentity of the eclass");
setSuperEntity(aClass);
final boolean isInheritanceRoot =
aClass.getPaSuperEntity() == null || aClass.getPaSuperEntity().getMappedSuperclass() != null; // last
// A not mappable type will not get an entity annotation.
// Even the features of non-mappable types are mapped because
// the efeatures can be inherited through multiple inheritance
final boolean mappable = isMappableAnnotatedClass(aClass);
// add entity or set entity name
if (mappable && aClass.getEntity() == null && aClass.getEmbeddable() == null) {
Entity entity = getFactory().createEntity();
entity.setEModelElement(eclass);
aClass.setEntity(entity);
}
if (aClass.getEntity() != null && aClass.getEntity().getName() == null) {
aClass.getEntity().setName(getEntityNameStrategy().toEntityName(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 InheritanceType inheritanceType;
if (aClass.getInheritance() != null) {
inheritanceType = aClass.getInheritance().getStrategy();
} else {
// get the inheritance from the supers, if defined there
final Inheritance inheritanceFromSupers = getInheritanceFromSupers(aClass);
inheritanceType =
inheritanceFromSupers != null ? inheritanceFromSupers.getStrategy()
: optionDefaultInheritanceMapping;
// if this is the root then add a specific inheritance annotation
if (isInheritanceRoot) {
final Inheritance inheritance = getFactory().createInheritance();
inheritance.setStrategy(inheritanceType);
inheritance.setEModelElement(eclass);
aClass.setInheritance(inheritance);
}
}
// add PrimaryKeyJoinColumn in case of a joined
if (!isInheritanceRoot && inheritanceType.equals(InheritanceType.JOINED) &&
aClass.getPrimaryKeyJoinColumns().size() == 0) {
ArrayList<String> idFeatures = new ArrayList<String>();
PAnnotatedEClass aSuperClass = null;
for (EClass eSuperClass : aClass.getModelEClass().getESuperTypes()) {
aSuperClass = getAnnotatedModel().getPAnnotated(eSuperClass);
idFeatures.addAll(StrategyUtil.getIDFeaturesNames(aSuperClass, getPersistenceOptions()
.getDefaultIDFeatureName()));
if (!idFeatures.isEmpty()) {
break;
}
}
for (String idFeature : idFeatures) {
final PrimaryKeyJoinColumn pkjc = getFactory().createPrimaryKeyJoinColumn();
pkjc.setName(getSqlNameStrategy().getPrimaryKeyJoinColumnName(aSuperClass, idFeature));
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)) ||
inheritanceType.equals(InheritanceType.JOINED) || inheritanceType
.equals(InheritanceType.TABLE_PER_CLASS))) {
final Table table = getFactory().createTable();
table.setEModelElement(eclass);
// name is set in next step
aClass.setTable(table);
}
if (aClass.getTable() != null && aClass.getTable().getName() == null) {
aClass.getTable().setName(getSqlNameStrategy().getTableName(aClass));
}
if (addDiscriminator(aClass)) {
// 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)) {
// note defaults of primitive types are all defined in the model
final DiscriminatorColumn dc = getFactory().createDiscriminatorColumn();
dc.setEModelElement(eclass);
dc.setName(getSqlNameStrategy().getDiscriminatorColumnName());
aClass.setDiscriminatorColumn(dc);
}
// add a discriminator value
if (aClass.getDiscriminatorValue() == null && inheritanceType.equals(InheritanceType.SINGLE_TABLE)) {
final DiscriminatorValue dv = getFactory().createDiscriminatorValue();
final DiscriminatorColumn dc = getDiscriminatorColumn(aClass);
if (dc != null && dc.getDiscriminatorType() != null &&
dc.getDiscriminatorType().getValue() == DiscriminatorType.INTEGER_VALUE) {
// use the entityname to translate to an int value,
// hopefully hashcode is more or less unique...
final String entityName = getEntityName(eclass);
log
.warn("Generating an integer discriminator value for entity " +
entityName +
". The hashcode of the entityName is used as the discriminatorvalue. This may not be unique! To ensure uniques you should set a @DiscriminatorValue annotation");
dv.setValue("" + entityName.hashCode());
} else {
dv.setValue(getEntityName(eclass));
}
dv.setEModelElement(eclass);
aClass.setDiscriminatorValue(dv);
}
}
// Add default PkJoinColumns for SecondaryTables.
for (SecondaryTable secondaryTable : aClass.getSecondaryTables()) {
final EList<PrimaryKeyJoinColumn> 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<PAnnotatedEStructuralFeature> aIdFeatures = aClass.getPaIdFeatures();
for (PAnnotatedEStructuralFeature idef : aIdFeatures) {
final PrimaryKeyJoinColumn pkJoinColumn = PannotationFactory.eINSTANCE.createPrimaryKeyJoinColumn();
pkJoinColumn.setName(getSqlNameStrategy().getSecondaryTablePrimaryKeyJoinColumnName(idef));
pkJoinColumns.add(pkJoinColumn);
}
}
}
for (PAnnotatedEStructuralFeature aStructuralFeature : aClass.getPaEStructuralFeatures()) {
eFeatureAnnotator.annotate(aStructuralFeature);
}
return true;
}
protected boolean addDiscriminator(PAnnotatedEClass aClass) {
return true;
}
// finds the DiscriminatorColumn in the aClass or its super entities
protected DiscriminatorColumn getDiscriminatorColumn(PAnnotatedEClass aClass) {
if (aClass.getDiscriminatorColumn() != null) {
return aClass.getDiscriminatorColumn();
}
// or use aClass.getPaMappedSupers()?
if (aClass.getPaSuperEntity() != null) {
return getDiscriminatorColumn(aClass.getPaSuperEntity());
}
return null;
}
/** Sets the {@link EFeatureAnnotator} */
@Override
protected void initialize() {
super.initialize();
eFeatureAnnotator = createAnnotator(EFeatureAnnotator.class);
}
/**
* Returns the inheritance of the passed annotated class or from one of its super annotated
* class
*/
protected Inheritance getInheritanceFromSupers(PAnnotatedEClass childPA) {
if (childPA == null) {
return null;
}
if (childPA.getInheritance() != null) {
return childPA.getInheritance();
}
return getInheritanceFromSupers(childPA.getPaSuperEntity());
}
/** Set the super entity */
protected void setSuperEntity(PAnnotatedEClass aClass) {
assert (aClass.getPaSuperEntity() == null);
final EClass eclass = aClass.getModelEClass();
if (eclass.getESuperTypes().size() == 0) {
return;
}
// check for overridden using extends
if (aClass.getEntity() != null && aClass.getEntity().getExtends() != null) {
final EClass superEClass = aClass.getPaModel().getEClass(aClass.getEntity().getExtends());
final PAnnotatedEClass superAClass = aClass.getPaModel().getPAnnotated(superEClass);
if (!processedAClasses.contains(superAClass)) {
annotate(superAClass);
}
aClass.setPaSuperEntity(superAClass);
return;
}
final PAnnotatedEClass superAClass = aClass.getPaModel().getPAnnotated(eclass.getESuperTypes().get(0));
if (superAClass.getEntity() != null || superAClass.getMappedSuperclass() != null) {
aClass.setPaSuperEntity(superAClass);
}
}
/** Returns fals for jpox and true for hibernate */
protected boolean isMappableAnnotatedClass(PAnnotatedEClass aClass) {
final EClass eclass = aClass.getModelEClass();
if (!mapInterfaceEClass() && eclass.isInterface()) {
log.debug("Not mapping interfaces and this is an interface eclass, ignore it");
return false;
}
if (aClass.getTransient() != null) {
return false; // not mappable
}
if (!getPersistenceOptions().isSetEntityAutomatically() && aClass.getEntity() == null &&
aClass.getEmbeddable() == null) {
log.debug("Entities are not added automatically and this eclass: " + aClass.getModelEClass().getName() +
" does not have an entity/embeddable annotation.");
return false;
}
// ignore these
if (!mapMappedSuperEClass() && aClass.getMappedSuperclass() != null) {
if (aClass.getEntity() != null) {
log
.warn("EClass " +
eclass.getName() +
" has entity as well as mappedsuperclass annotation, following mappedsuperclass annotation, therefore ignoring it for the mapping");
}
return false;
}
return true;
}
/**
* Map Interface EClasses, default false, overridden by hibernate to return true
*/
protected boolean mapInterfaceEClass() {
return false;
}
/** Map a mapped superclass, this differs for jpox and hibernate */
protected boolean mapMappedSuperEClass() {
return true;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.emf.teneo.annotations.mapper.AbstractAnnotator#setPersistenceOptions(org.eclipse
* .emf.teneo.PersistenceOptions)
*/
@Override
public void setPersistenceOptions(PersistenceOptions persistenceOptions) {
super.setPersistenceOptions(persistenceOptions);
if (persistenceOptions.getInheritanceMapping() != null) {
InheritanceType it = InheritanceType.get(persistenceOptions.getInheritanceMapping());
if (it == InheritanceType.JOINED) {
optionDefaultInheritanceMapping = InheritanceType.JOINED;
log.debug("Option inheritance: joined");
} else if (it == InheritanceType.SINGLE_TABLE) {
optionDefaultInheritanceMapping = InheritanceType.SINGLE_TABLE;
log.debug("Option inheritance: single");
} else if (it == InheritanceType.TABLE_PER_CLASS) {
optionDefaultInheritanceMapping = InheritanceType.TABLE_PER_CLASS;
log.debug("Option inheritance: table per class");
} else {
throw new IllegalArgumentException("Inheritance mapping option: " +
persistenceOptions.getInheritanceMapping() + " is not supported");
}
}
}
}