| /** |
| * <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; |
| } |
| } |