blob: b9c26d2d6dd8f501481a7a9f2c001ff268fce317 [file] [log] [blame]
/**
* <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: OneToManyMapper.java,v 1.44 2010/03/25 00:12:42 mtaal Exp $
*/
package org.eclipse.emf.teneo.hibernate.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.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEAttribute;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEClass;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEReference;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEStructuralFeature;
import org.eclipse.emf.teneo.annotations.pannotation.FetchType;
import org.eclipse.emf.teneo.annotations.pannotation.JoinColumn;
import org.eclipse.emf.teneo.annotations.pannotation.JoinTable;
import org.eclipse.emf.teneo.annotations.pannotation.OneToMany;
import org.eclipse.emf.teneo.extension.ExtensionPoint;
import org.eclipse.emf.teneo.hibernate.hbannotation.CollectionOfElements;
import org.eclipse.emf.teneo.hibernate.hbmodel.HbAnnotatedEReference;
import org.eclipse.emf.teneo.hibernate.hbmodel.HbAnnotatedETypeElement;
import org.eclipse.emf.teneo.simpledom.Element;
import org.eclipse.emf.teneo.util.StoreUtil;
/**
* Maps a OneToMany element to its mapping Context.
* <p>
* Assumes that the given {@link PAnnotatedEStructuralFeature} is normal, i.e.
* <ul>
* <li>it is a {@link PAnnotatedEReference};
* <li>it has a {@link OneToMany} annotation;
* <li>TODO
* </ul>
*
* @author <a href="mailto:mtaal at elver.org">Martin Taal</a>
*/
public class OneToManyMapper extends AbstractAssociationMapper implements ExtensionPoint {
/** The log */
private static final Log log = LogFactory.getLog(OneToManyMapper.class);
/** Process the paReference */
public void process(PAnnotatedEReference paReference) {
// TODO assuming it coincides with specified targetEntity, correct?
// Guaranteed by validation?
if (getOtherSide(paReference) == null) {
processOtMUni(paReference);
// mappedBy is not set anymore because it controls inverse
// see bugzilla 242479
// } else if
// (!paReference.getOneToMany().eIsSet(PannotationPackage.eINSTANCE.getOneToMany_MappedBy
// ()
// )) {
// throw new MappingException(
// "The many side of a bidirectional one to many association must be the owning side",
// paReference);
} else {
// MT: TODO add check, in this case unique should always true
// because an child can only occur once within
// the collection because
// of the bidirectional behavior.
processOtMBidiInverse(paReference);
}
}
/**
* joinTable.getInverseJoinColumns must be null TODO choose appropriate mapping according to the
* presence of JoinTable
*/
private void processOtMUni(PAnnotatedEReference paReference) {
if (log.isDebugEnabled()) {
log.debug("Generating one to many unidirectional mapping for " + paReference);
}
final HbAnnotatedEReference hbReference = (HbAnnotatedEReference) paReference;
final EReference eref = hbReference.getModelEReference();
final EClass refType = eref.getEReferenceType();
final PAnnotatedEClass referedToAClass = hbReference.getAReferenceType();
boolean isMap = StoreUtil.isMap(eref) && getHbmContext().isMapEMapAsTrueMap();
// TODO add isUnique on interface
// TODO request EMF team to deal correctly with unique attribute on
// EReferences
final Element collElement = addCollectionElement(paReference);
addAccessor(collElement);
if (hbReference.getImmutable() != null) {
collElement.addAttribute("mutable", "false");
}
if (((HbAnnotatedEReference) paReference).getHbCache() != null) {
addCacheElement(collElement, ((HbAnnotatedEReference) paReference).getHbCache());
}
// .getAnnotatedElement().getName(),
// paReference.getIndexed() != null &&
// paReference.getIndexed().isValue());
final Element keyElement = collElement.addElement("key");
handleOndelete(keyElement, hbReference.getHbOnDelete());
addForeignKeyAttribute(keyElement, paReference);
// TODO: throw error if both jointable and joincolumns have been set
final List<JoinColumn> jcs = getJoinColumns(paReference);
final JoinTable jt = getJoinTable(paReference);
if (jt != null) {
addJoinTable(hbReference, collElement, keyElement, jt);
} else {
addKeyColumns(hbReference, keyElement, jcs);
}
final OneToMany otm = hbReference.getOneToMany();
if (hbReference.getHbIdBag() != null) {
log.debug("Setting indexed=false because is an idbag");
otm.setIndexed(false);
}
// a special case see here:
// http://forum.hibernate.org/viewtopic.php?p=2383090
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=242479
if (!otm.isIndexed() && hbReference.getHbIdBag() == null && otm.getMappedBy() != null) {
collElement.addAttribute("inverse", "true");
} else if (otm.getFetch().equals(FetchType.EXTRA)) {
collElement.addAttribute("inverse", "true");
}
final EClass eclass = eref.getEReferenceType();
boolean isMapValueIsEntity = (eclass.getEStructuralFeature("value") instanceof EReference);
if (hbReference.getHbIdBag() == null) {
// now we check if it is a list or a map
if (hbReference.getMapKey() != null || hbReference.getHbMapKey() != null
|| hbReference.getMapKeyManyToMany() != null) {
addMapKey(collElement, paReference);
} else if (isMap) {
addMapKey(collElement, hbReference);
} else if (collElement.getName().compareTo("list") == 0) { // otm.isIndexed()
addListIndex(collElement, paReference);
}
}
// TODO OneToMany and CollectionOfElements are mutually exclusive.
// Should throw exception if both there?
addFetchType(collElement, getFetchType(hbReference, otm.getFetch()));
addCascadesForMany(collElement,
getCascades(hbReference.getHbCascade(), otm.getCascade(), otm.isOrphanRemoval()));
List<JoinColumn> inverseJoinColumns = jt != null && jt.getInverseJoinColumns() != null ? jt
.getInverseJoinColumns() : new ArrayList<JoinColumn>();
String targetName = null;
targetName = otm.getTargetEntity();
// final boolean isEasyEMFGenerated =
// getHbmContext().isEasyEMFGenerated(refType);
if (targetName == null) {
targetName = getHbmContext().getEntityName(refType);
}
// MT a manytomany is only required in case of unique=false, note that
// the ejb3 spec states that for uni otm
// always a jointable should be
// used (as a default). This is however to heavy for cases were a
// jointable is not required at all. Also
// hibernate supports uni otm without join table.
if (hbReference.getEmbedded() != null) {
addCompositeElement(collElement, hbReference);
} else if (isMap && !isMapValueIsEntity) {
final EAttribute valueEAttribute = (EAttribute) eclass.getEStructuralFeature("value");
final PAnnotatedEAttribute valuePAttribute = paReference.getPaModel().getPAnnotated(
valueEAttribute);
// Put column at the reference level to the attribute itself
if (valuePAttribute.getColumn() == null && paReference.getColumn() != null) {
valuePAttribute.setColumn(EcoreUtil.copy(paReference.getColumn()));
}
addElementElement(collElement, valuePAttribute, getColumns(valuePAttribute),
otm.getTargetEntity());
} else if (!isEObject(targetName) && jt != null) {
// A m2m forces a join table, note that isunique does not completely
// follow the semantics of emf, unique on
// an otm means that an element can only occur once in the table, if
// unique is false then you in effect have
// a
// mtm relation
// because an item can occur twice or more in the list.
// To force a jointable on a real otm a jointable annotation should
// be specified.
final Element mtm = addManyToMany(hbReference, referedToAClass, collElement, targetName,
inverseJoinColumns, getUnique(otm.isUnique()));
addForeignKeyAttribute(mtm, paReference);
if (hbReference.getNotFound() != null) {
mtm.addAttribute("not-found", hbReference.getNotFound().getAction().getName().toLowerCase());
}
} else {
final Element otmElement = addOneToMany(paReference, referedToAClass, collElement,
eref.getName(), targetName);
addForeignKeyAttribute(keyElement, paReference);
if (hbReference.getNotFound() != null) {
otmElement.addAttribute("not-found", hbReference.getNotFound().getAction().getName()
.toLowerCase());
}
}
mapFilter(collElement, ((HbAnnotatedETypeElement) paReference).getFilter());
}
/**
* Process bidirectional one-to-many
*/
private void processOtMBidiInverse(PAnnotatedEReference paReference) {
if (log.isDebugEnabled()) {
log.debug("Generating one to many bidirectional inverse mapping for " + paReference);
}
// final Element collElement =
// addCollectionElement(paReference.getAnnotatedElement().getName(),
// paReference.isIndexed());
final Element collElement = addCollectionElement(paReference);
addAccessor(collElement);
final EReference eref = paReference.getModelEReference();
final HbAnnotatedEReference hbReference = (HbAnnotatedEReference) paReference;
final PAnnotatedEClass referedToAClass = hbReference.getAReferenceType();
if (hbReference.getHbCache() != null) {
addCacheElement(collElement, hbReference.getHbCache());
}
if (hbReference.getImmutable() != null) {
collElement.addAttribute("mutable", "false");
}
// MT: note inverse does not work correctly with hibernate for indexed
// collections, see 7.3.3 of the hibernate
// manual 3.1.1
final OneToMany otm = paReference.getOneToMany();
if (!otm.isIndexed() && otm.getMappedBy() != null && hbReference.getHbIdBag() == null) {
collElement.addAttribute("inverse", "true");
} else if (otm.getFetch().equals(FetchType.EXTRA)) {
collElement.addAttribute("inverse", "true");
} else {
log.debug("Inverse is not set on purpose for indexed collections");
}
final Element keyElement = collElement.addElement("key");
handleOndelete(keyElement, ((HbAnnotatedEReference) paReference).getHbOnDelete());
addForeignKeyAttribute(keyElement, paReference);
final List<JoinColumn> jcs = getJoinColumns(paReference);
final JoinTable jt = getJoinTable(paReference);
if (jt != null) {
addJoinTable(hbReference, collElement, keyElement, jt);
} else {
addKeyColumns(hbReference, keyElement, jcs);
}
addFetchType(collElement, otm.getFetch());
addCascadesForMany(collElement,
getCascades(hbReference.getHbCascade(), otm.getCascade(), otm.isOrphanRemoval()));
boolean isMap = StoreUtil.isMap(eref) && getHbmContext().isMapEMapAsTrueMap();
boolean isMapValueIsEntity = false;
if (hbReference.getHbIdBag() == null && otm.isList()) {
// now we check if it is a list or a map
if (hbReference.getMapKey() != null || hbReference.getHbMapKey() != null
|| hbReference.getMapKeyManyToMany() != null) {
addMapKey(collElement, paReference);
} else if (isMap) {
final EClass eclass = eref.getEReferenceType();
isMapValueIsEntity = (eclass.getEStructuralFeature("value") instanceof EReference);
addMapKey(collElement, hbReference);
} else if (collElement.getName().compareTo("list") == 0) { // otm.isIndexed()
addListIndex(collElement, paReference);
}
}
String targetName = otm.getTargetEntity();
if (targetName == null) {
targetName = getHbmContext().getEntityName(eref.getEReferenceType());
}
if (paReference.getEmbedded() != null) {
addCompositeElement(collElement, paReference);
} else if (isMap && !isMapValueIsEntity) {
final EClass eclass = eref.getEReferenceType();
final EAttribute valueEAttribute = (EAttribute) eclass.getEStructuralFeature("value");
final PAnnotatedEAttribute valuePAttribute = paReference.getPaModel().getPAnnotated(
valueEAttribute);
addElementElement(collElement, valuePAttribute, getColumns(valuePAttribute),
otm.getTargetEntity());
} else if (jt != null) {
final List<JoinColumn> inverseJoinColumns = jt != null && jt.getInverseJoinColumns() != null ? jt
.getInverseJoinColumns() : new ArrayList<JoinColumn>();
final Element mtm = addManyToMany(hbReference, referedToAClass, collElement, targetName,
inverseJoinColumns, getUnique(otm.isUnique()));
addForeignKeyAttribute(mtm, paReference);
if (hbReference.getNotFound() != null) {
mtm.addAttribute("not-found", hbReference.getNotFound().getAction().getName().toLowerCase());
}
} else {
final Element otmElement = addOneToMany(paReference, referedToAClass, collElement,
eref.getName(), targetName);
addForeignKeyAttribute(keyElement, paReference);
if (hbReference.getNotFound() != null) {
otmElement.addAttribute("not-found", hbReference.getNotFound().getAction().getName()
.toLowerCase());
}
}
mapFilter(collElement, ((HbAnnotatedETypeElement) paReference).getFilter());
}
// https://forum.hibernate.org/viewtopic.php?f=1&t=10296
protected boolean getUnique(boolean isUnique) {
final String hbVersion = getHbmContext().getPersistenceOptions().getHibernateVersion();
if (hbVersion == null || !"4.2.7SP1".equals(hbVersion)) {
return isUnique;
}
return false;
}
/**
* Creates a onetomany element.
*
* @param collElement
* @param targetEntity
*/
protected Element addOneToMany(PAnnotatedEReference paReference,
PAnnotatedEClass referedToAClass, Element collElement, String featureName, String targetEntity) {
final HbAnnotatedEReference hbReference = (HbAnnotatedEReference) paReference;
if (isEObject(targetEntity) || hbReference.getAny() != null
|| hbReference.getAnyMetaDef() != null) { // anytype
final String assocName = getHbmContext().getPropertyName(
hbReference.getModelEStructuralFeature());
return collElement.add(createAny(assocName, hbReference, hbReference.getAny(),
hbReference.getAnyMetaDef(), true));
} else {
String tag = "one-to-many";
if (((HbAnnotatedEReference) paReference).getHbIdBag() != null) {
tag = "many-to-many";
}
if (referedToAClass.isOnlyMapAsEntity()
|| !getHbmContext().forceUseOfInstance(referedToAClass)) {
return collElement.addElement(tag).addAttribute("entity-name", targetEntity);
} else {
return collElement.addElement(tag).addAttribute("class",
getHbmContext().getInstanceClassName(referedToAClass.getModelEClass()));
}
}
}
/**
* Creates a many-to-many to handle the unidirectional manytomany. A unidirectional manytomany is
* now specified using the one to many annotation while its implementation has a join table.
*/
private Element addManyToMany(HbAnnotatedEReference hbReference,
PAnnotatedEClass referedToAClass, Element collElement, String targetEntity,
List<JoinColumn> invJoinColumns, boolean unique) {
final Element manyToMany;
if (referedToAClass.isOnlyMapAsEntity() || !getHbmContext().forceUseOfInstance(referedToAClass)) {
manyToMany = collElement.addElement("many-to-many").addAttribute("entity-name", targetEntity)
.addAttribute("unique", unique ? "true" : "false");
} else {
manyToMany = collElement
.addElement("many-to-many")
.addAttribute("class",
getHbmContext().getInstanceClassName(referedToAClass.getModelEClass()))
.addAttribute("unique", unique ? "true" : "false");
}
addKeyColumns(hbReference, manyToMany, invJoinColumns); // pass null for
// jointable
return manyToMany;
}
/** Add composite-element */
private Element addCompositeElement(Element collElement, PAnnotatedEReference paReference) {
// TODO: handle nested components: nested-composite-element
final Element componentElement = collElement.addElement("composite-element").addAttribute(
"class", getHbmContext().getInstanceClassName(paReference.getEReferenceType()));
getHbmContext().setCurrent(componentElement);
try {
// process the features of the target
final PAnnotatedEClass componentAClass = paReference.getAReferenceType();
getHbmContext().processFeatures(componentAClass.getPaEStructuralFeatures());
} finally {
getHbmContext().setCurrent(collElement.getParent());
}
return componentElement;
}
private FetchType getFetchType(HbAnnotatedEReference hbReference, FetchType defaultFetchType) {
final CollectionOfElements coe = hbReference.getHbCollectionOfElements();
if (coe != null) {
return coe.getFetch();
}
if (hbReference.getElementCollection() != null) {
return hbReference.getElementCollection().getFetch();
}
return defaultFetchType;
}
}