/** * <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: OneToManyReferenceAnnotator.java,v 1.13 2008/09/06 00:14:04 mtaal Exp $ */ package org.eclipse.emf.teneo.annotations.mapper; 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.EEnum; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEAttribute; import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEReference; 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.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.mapping.strategy.EntityNameStrategy; import org.eclipse.emf.teneo.util.StoreUtil; /** * Annotates an ereference. * * @author <a href="mailto:mtaal@elver.org">Martin Taal</a> * @version $Revision: 1.13 $ */ public class OneToManyReferenceAnnotator extends BaseEFeatureAnnotator implements ExtensionPoint { // The logger protected static final Log log = LogFactory.getLog(OneToManyReferenceAnnotator.class); /** Annotate it */ public void annotate(PAnnotatedEReference aReference) { final String logStr = aReference.getModelEReference().getName() + "/" + aReference.getModelEReference().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.getModelElement(); 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 = getFactory().createOneToMany(); aReference.setOneToMany(otm); otm.setEModelElement(eReference); if (eReference.isContainment() && getPersistenceOptions().isFetchContainmentEagerly()) { otm.setFetch(FetchType.EAGER); } } else { log.debug("EReference + " + logStr + " has onetomany, check if defaults should be set"); } // don't set mappedBy explicitly anymore // mappedBy is not set anymore because it controls inverse // see bugzilla 242479 // if (otm.getMappedBy() == null && eReference.getEOpposite() != null) { // otm.setMappedBy(eReference.getEOpposite().getName()); // } if (getPersistenceOptions().isMapEmbeddableAsEmbedded() && aReference.getAReferenceType().getEmbeddable() != null) { aReference.setEmbedded(getFactory().createEmbedded()); } if (getPersistenceOptions().isSetForeignKeyNames() && aReference.getForeignKey() == null) { // See bugzilla 211798: handle a specific case when this is a bidirectional // one-to-many/many-to-one. In that case the foreign key name has to be // the same on both sides and is set on the many-side. So use the // annotated reference from the other side to ensure that the same foreign key name // is used. if (eReference.getEOpposite() != null && !eReference.getEOpposite().isMany() && !eReference.getEOpposite().isTransient()) { final PAnnotatedEReference aOpposite = aReference.getPaModel().getPAnnotated(eReference.getEOpposite()); if (aOpposite != null && aOpposite.getTransient() == null) { // don't do anything as otherwise hibernate will create two // fk's with the same name // if (aOpposite.getForeignKey() != null) { // final ForeignKey fk = getFactory().createForeignKey(); // fk.setName(aOpposite.getForeignKey().getName()); // aReference.setForeignKey(fk); // } else { // aReference.setForeignKey(createFK(aOpposite)); // } } else { aReference.setForeignKey(createFK(aReference)); } } else { aReference.setForeignKey(createFK(aReference)); } } if (eReference.isContainment() || getPersistenceOptions().isSetDefaultCascadeOnNonContainment()) { setCascade(otm.getCascade(), eReference.isContainment()); } // handle a special case, an emap which is mapped as a real map and which has an // enumerate as the key // Disabled for now as the hibernate map-key does not support enumerates as the type // for the key when mapping as a true map if (false && StoreUtil.isMap(eReference) && getPersistenceOptions().isMapEMapAsTrueMap()) { final EStructuralFeature keyFeature = aReference.getEReferenceType().getEStructuralFeature("key"); if (keyFeature instanceof EAttribute) { final EAttribute keyAttribute = (EAttribute) keyFeature; final PAnnotatedEAttribute aKeyAttribute = aReference.getPaModel().getPAnnotated(keyAttribute); if (keyAttribute.getEType() instanceof EEnum && aKeyAttribute.getEnumerated() == null) { final Enumerated enumerated = getFactory().createEnumerated(); enumerated.setValue(EnumType.STRING); enumerated.setEModelElement(keyAttribute); aKeyAttribute.setEnumerated(enumerated); } } } // 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!"); // note force a join table in case of idbag! otm.setIndexed(!getPersistenceOptions().alwaysMapListAsBag() && !getPersistenceOptions().alwaysMapListAsIdBag() && eReference.isOrdered() && aReference.getOrderBy() == null); // in case of containment it is always unique // in case optionidbag then ignore the unique attribute on the ereference otm.setUnique(eReference.isContainment() || (!getPersistenceOptions().alwaysMapListAsIdBag() && eReference.isUnique())); if (aReference.getModelEReference().getEOpposite() != null) { log.debug("Setting unique because is bidirectional (has eopposite) otm"); otm.setUnique(true); } } else if (!otm.isUnique() && !eReference.isUnique() && aReference.getModelEReference().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 = EntityNameStrategy.EOBJECT_ECLASS_NAME.compareTo(otm.getTargetEntity()) == 0; // in case of eobject always a join table is required if (aReference.getJoinTable() != null || isEObject || (getPersistenceOptions().isJoinTableForNonContainedAssociations() && !eReference.isContainment()) || !otm.isUnique()) { JoinTable joinTable = aReference.getJoinTable(); if (joinTable == null) { joinTable = getFactory().createJoinTable(); aReference.setJoinTable(joinTable); } joinTable.setEModelElement(eReference); // see remark in manytomany about naming of jointables if (joinTable.getName() == null) { joinTable.setName(getSqlNameStrategy().getJoinTableName(aReference)); } // note joincolumns in jointable can be generated automatically by // hib/jpox. need to explicitly do this in case of // composite id if (joinTable.getJoinColumns().size() == 0) { final List<String> names = getSqlNameStrategy().getJoinTableJoinColumns(aReference, false); joinTable.getJoinColumns().addAll(getJoinColumns(names, false, true, otm)); } if (joinTable.getInverseJoinColumns().size() == 0 && aReference.getAReferenceType() != null) { final List<String> names = getSqlNameStrategy().getJoinTableJoinColumns(aReference, true); // todo: should the inverse join columns not be joinTable.getInverseJoinColumns().addAll(getJoinColumns(names, false, true, otm)); } } else if (aReference.getJoinColumns() == null || aReference.getJoinColumns().isEmpty()) { // add boolean borrowJoinColumnsOtherSide = false; final EReference eOther = getOpposite(aReference); if (eOther != null) { final PAnnotatedEReference aOther = aReference.getPaModel().getPAnnotated(eOther); // map the other side, before checking if there are joincolumns getEFeatureAnnotator().getManyToOneReferenceAnnotator().annotate(aOther); if (aOther.getJoinColumns() != null && !aOther.getJoinColumns().isEmpty()) { borrowJoinColumnsOtherSide = true; for (JoinColumn jc : aOther.getJoinColumns()) { aReference.getJoinColumns().add((JoinColumn) EcoreUtil.copy(jc)); } // repair updatable/insertable for (JoinColumn jc : aReference.getJoinColumns()) { jc.setUpdatable(true); jc.setInsertable(true); } } } if (!borrowJoinColumnsOtherSide) { final List<String> names = getSqlNameStrategy().getOneToManyEReferenceJoinColumns(aReference); aReference.getJoinColumns().addAll(getJoinColumns(names, aReference.getEmbedded() == null, true, otm)); } // In case of a bidirectional relation without a join table // do a special thing: if this is a list (with an index) then the association is always // managed from this side of the relation. This means that update/insert of the // joincolumns // on the other side is set to false. // See the hibernate manual: 6.3.3. Bidirectional associations with indexed collections if (otm.isList() && eOther != null) { final PAnnotatedEReference aOpposite = getAnnotatedModel().getPAnnotated(eOther); if (aReference.getTransient() == null) { if (aOpposite.getJoinColumns().size() > 0) { for (JoinColumn jc : aOpposite.getJoinColumns()) { jc.setInsertable(false); jc.setUpdatable(false); } } } } } } protected EReference getOpposite(PAnnotatedEReference aReference) { final EReference eReference = (EReference) aReference.getModelElement(); if (eReference.getEOpposite() != null) { return eReference.getEOpposite(); } // now handle a special case, the aReference is a map // and there is a mapped by and a one to many if (aReference.getOneToMany() == null || aReference.getOneToMany().getMappedBy() == null) { return null; } final EClass eclass = eReference.getEReferenceType(); if (getPersistenceOptions().isMapEMapAsTrueMap() && StoreUtil.isMapEntry(eclass)) { EStructuralFeature feature = eclass.getEStructuralFeature("value"); if (feature instanceof EReference) { final String mappedBy = aReference.getOneToMany().getMappedBy(); final EReference valueERef = (EReference) feature; final EClass valueEClass = valueERef.getEReferenceType(); final EStructuralFeature ef = valueEClass.getEStructuralFeature(mappedBy); if (ef == null || ef instanceof EAttribute) { return null; } return (EReference) ef; } else { return null; } } else { return null; } } }