/** * <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.40 2009/03/07 21:15:19 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.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.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()); // TODO: throw error if both jointable and joincolumns have been set final List<JoinColumn> jcs = paReference.getJoinColumns() == null ? new ArrayList<JoinColumn>() : paReference.getJoinColumns(); final JoinTable jt = paReference.getJoinTable(); 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"); } boolean isMapValueIsEntity = false; if (hbReference.getHbIdBag() == null && otm.isList()) { // now we check if it is a list or a map final EClass eclass = eref.getEReferenceType(); if (hbReference.getMapKey() != null || hbReference.getHbMapKey() != null || hbReference.getMapKeyManyToMany() != null) { isMapValueIsEntity = (eclass.getEStructuralFeature("value") instanceof EReference); addMapKey(collElement, paReference); } else if (isMap) { isMapValueIsEntity = (eclass.getEStructuralFeature("value") instanceof EReference); addMapKey(collElement, hbReference); } else if (collElement.getName().compareTo("list") == 0) { // otm.isIndexed() addListIndex(collElement, paReference); } } final CollectionOfElements coe = hbReference .getHbCollectionOfElements(); // TODO OneToMany and CollectionOfElements are mutually exclusive. // Should throw exception if both there? addFetchType(collElement, (null != coe) ? coe.getFetch() : otm .getFetch()); addCascadesForMany(collElement, getCascades(hbReference.getHbCascade(), otm.getCascade())); 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 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 (!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, 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 { log.debug("Inverse is not set on purpose for indexed collections"); } final Element keyElement = collElement.addElement("key"); handleOndelete(keyElement, ((HbAnnotatedEReference) paReference) .getHbOnDelete()); // MT: added handling of join info final List<JoinColumn> jcs = paReference.getJoinColumns() == null ? new ArrayList<JoinColumn>() : paReference.getJoinColumns(); final JoinTable jt = paReference.getJoinTable(); if (jt != null) { addJoinTable(hbReference, collElement, keyElement, jt); } else { addKeyColumns(hbReference, keyElement, jcs); } addFetchType(collElement, otm.getFetch()); addCascadesForMany(collElement, getCascades(hbReference.getHbCascade(), otm.getCascade())); 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, 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()); } /** * Creates a onetomany element. * * @param collElement * @param targetEntity */ protected Element addOneToMany(PAnnotatedEReference paReference, PAnnotatedEClass referedToAClass, Element collElement, String featureName, String targetEntity) { if (isEObject(targetEntity)) { // anytype final Element any = collElement.addElement("many-to-any") .addAttribute("id-type", "long"); addColumnsAndFormula(any, paReference, getAnyTypeColumns( featureName, false), false, false); return any; } 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; } }