/** * <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 Davide Marchignoli * </copyright> $Id: IdMapper.java,v 1.30 2009/03/07 21:15:19 mtaal Exp $ */ package org.eclipse.emf.teneo.hibernate.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.EClassifier; 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.PAnnotatedEPackage; import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEReference; import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEStructuralFeature; import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedModel; import org.eclipse.emf.teneo.annotations.pannotation.Column; import org.eclipse.emf.teneo.annotations.pannotation.GeneratedValue; import org.eclipse.emf.teneo.annotations.pannotation.GenerationType; import org.eclipse.emf.teneo.annotations.pannotation.JoinColumn; import org.eclipse.emf.teneo.annotations.pannotation.ManyToOne; import org.eclipse.emf.teneo.annotations.pannotation.SequenceGenerator; import org.eclipse.emf.teneo.annotations.pannotation.SequenceStyleGenerator; import org.eclipse.emf.teneo.annotations.pannotation.TableGenerator; import org.eclipse.emf.teneo.extension.ExtensionPoint; import org.eclipse.emf.teneo.hibernate.hbannotation.GenericGenerator; import org.eclipse.emf.teneo.hibernate.hbannotation.Parameter; import org.eclipse.emf.teneo.hibernate.hbmodel.HbAnnotatedEPackage; import org.eclipse.emf.teneo.simpledom.DocumentHelper; import org.eclipse.emf.teneo.simpledom.Element; /** * Mapper generating id entry for Hibernate from PAnnotatedElements. Throws an * error if called for non-root entities. * * @author <a href="mailto:marchign at elver.org">Davide Marchignoli</a> * @author <a href="mailto:mtaal at elver.org">Martin Taal</a> */ public class IdMapper extends AbstractAssociationMapper implements ExtensionPoint { /** The logger */ private static final Log log = LogFactory.getLog(IdMapper.class); /** the hibernate generator class names */ private static final String[] GENERATOR_CLASS_NAMES; /** initializes the hibernate class names array */ static { GENERATOR_CLASS_NAMES = new String[GenerationType.VALUES.size()]; GENERATOR_CLASS_NAMES[GenerationType.AUTO.getValue()] = "native"; GENERATOR_CLASS_NAMES[GenerationType.IDENTITY.getValue()] = "identity"; GENERATOR_CLASS_NAMES[GenerationType.SEQUENCE.getValue()] = "sequence"; GENERATOR_CLASS_NAMES[GenerationType.TABLE.getValue()] = "hilo"; GENERATOR_CLASS_NAMES[GenerationType.SEQUENCESTYLE.getValue()] = "org.hibernate.id.enhanced.SequenceStyleGenerator"; } /** Util method to create an id or composite-id element */ public static Element getCreateIdElement(Element entityElement, PAnnotatedEClass aClass) { if (aClass.getIdClass() != null) { // composite id Element element = entityElement.element("composite-id"); if (element == null) { element = DocumentHelper.createElement("composite-id") .addAttribute("class", aClass.getIdClass().getValue()) .addAttribute("mapped", "true"); entityElement.add(0, element); } return element; } else { Element element = entityElement.element("id"); if (element == null) { element = DocumentHelper.createElement("id"); entityElement.add(0, element); } return element; } } /** * Add synthetic id to the class */ public static Element addSyntheticId(MappingContext mc, Element entityElement) { if (entityElement.element("id") != null || entityElement.element("composite-id") != null) { throw new MappingException( "Syntheticid should only be called if there is no id element"); } final Element idElement = DocumentHelper.createElement("id"); entityElement.add(0, idElement); idElement .addAttribute("type", "long") . // NOTE: the name is also set so that the property name can be // used later to identify an id prop, // TODO: improve this addAttribute("name", mc.getIdColumnName()).addAttribute( "column", mc.getIdColumnName()).addElement("generator") .addAttribute("class", "native"); final Element meta = new Element("meta"); meta.addAttribute("attribute", HbMapperConstants.ID_META).addText( "true"); idElement.add(0, meta); if (mc.getSyntheticIdPropertyHandlerName() != null) { idElement.addAttribute("access", mc .getSyntheticIdPropertyHandlerName()); } return idElement; } /** * @return Returns the hibernate generator class for the given strategy. */ private static String hbGeneratorClass(GenerationType strategy) { return IdMapper.GENERATOR_CLASS_NAMES[strategy != null ? strategy .getValue() : GenerationType.AUTO.getValue()]; } /** * Process embedded id. */ public void processEmbeddedId(PAnnotatedEReference aReference) { final EReference eReference = aReference.getModelEReference(); final PAnnotatedEClass aClass = aReference.getPaModel().getPAnnotated( eReference.getEReferenceType()); final Element compositeIdElement = DocumentHelper .createElement("composite-id"); getHbmContext().getCurrent().add(0, compositeIdElement); compositeIdElement.addAttribute("name", eReference.getName()); final String className = getHbmContext().getInstanceClassName( aClass.getModelEClass()); compositeIdElement.addAttribute("class", className); getHbmContext().setCurrent(compositeIdElement); for (PAnnotatedEStructuralFeature aFeature : aClass .getPaEStructuralFeatures()) { if (aFeature instanceof PAnnotatedEAttribute) { PAnnotatedEAttribute aAttribute = (PAnnotatedEAttribute) aFeature; final Element keyPropertyElement = compositeIdElement .addElement("key-property"); keyPropertyElement.addAttribute("name", aFeature .getModelEStructuralFeature().getName()); addColumnsAndFormula(keyPropertyElement, aAttribute, getColumns(aAttribute), getHbmContext() .isCurrentElementFeatureMap(), false); setType(aAttribute, keyPropertyElement); } else if (aFeature instanceof PAnnotatedEReference && !((PAnnotatedEReference) aFeature).getModelEReference() .isMany()) { addKeyManyToOne(compositeIdElement, (PAnnotatedEReference) aFeature); } } getHbmContext().setCurrent(compositeIdElement.getParent()); addAccessor(compositeIdElement, hbmContext .getComponentPropertyHandlerName()); } private void addKeyManyToOne(Element currentParent, PAnnotatedEReference paReference) { log.debug("Process many-to-one " + paReference); final List<JoinColumn> jcs = getJoinColumns(paReference); final EClass referedTo = paReference.getModelEReference() .getEReferenceType(); final ManyToOne mto = paReference.getManyToOne(); String targetName = mto.getTargetEntity(); if (targetName == null) { targetName = getHbmContext().getEntityName(referedTo); } log.debug("Target " + targetName); final Element associationElement = addManyToOne(currentParent, paReference, targetName, true); addForeignKeyAttribute(associationElement, paReference); addLazyProxy(associationElement, mto.getFetch(), paReference); addJoinColumns(paReference, associationElement, jcs, getHbmContext() .isDoForceOptional(paReference) || mto.isOptional() || getHbmContext().isCurrentElementFeatureMap()); // MT: TODO; the characteristic of the other side should be checked (if // present), if the otherside is a onetoone // then this // should be set to true. But then this is then handled by a // bidirectional onetoone (I think). // if (joinColumns.isEmpty()) // associationElement.addAttribute("unique", "true"); } /** * Add property to the mapped id element */ public void processIdProperty(PAnnotatedEAttribute id) { final PAnnotatedEClass aClass = id.getPaEClass(); // check precondition if (aClass.getPaSuperEntity() != null && aClass.getPaSuperEntity().hasIdAnnotatedFeature()) { log .error("The annotated eclass: " + aClass + " has an id-annotated feature: " + id + " while it has a " + "superclass/type, id properties should always be specified in the top of the inheritance structure"); throw new MappingException( "The annotated eclass: " + aClass + " has an id-annotated feature: " + id + " while it has a " + "superclass/type, id properties should always be specified in the top of the inheritance structure"); } final EAttribute eAttribute = id.getModelEAttribute(); final List<Column> columns = getColumns(id); final GeneratedValue generatedValue = id.getGeneratedValue(); // if (column != null && column.getColumnDefinition() != null) { // // TODO support // log.error("Unsupported, ColumnDefinition in " + column); // throw new MappingException("Unsupported, ColumnDefinition", column); // } // if (column != null && column.getTable() != null) { // // TODO support // log.error("Unsupported, SecondaryTable in " + column); // throw new MappingException("Unsupported, SecondaryTable", column); // } final Element idElement = getCreateIdElement(getHbmContext() .getCurrent(), aClass); final boolean isCompositeId = aClass.getIdClass() != null; final Element usedIdElement; if (isCompositeId) { usedIdElement = idElement.addElement("key-property"); } else { usedIdElement = idElement; } // if idclass != null then this is a composite id which can not have a // unique constraint // on an id column addColumnsAndFormula(usedIdElement, id, columns, false, false, aClass .getIdClass() == null, true); usedIdElement.addAttribute("name", eAttribute.getName()); if (id.getEnumerated() == null) { setType(id, usedIdElement); if (eAttribute.getDefaultValue() != null) { usedIdElement.addAttribute("unsaved-value", eAttribute .getDefaultValue().toString()); } else if (eAttribute.getEType().getDefaultValue() != null) { usedIdElement.addAttribute("unsaved-value", eAttribute .getEType().getDefaultValue().toString()); } } else { // enumerated id final EClassifier eclassifier = id.getModelEAttribute().getEType(); if (!getHbmContext().isGeneratedByEMF() && !getHbmContext().isDynamic(eclassifier)) { final String typeName = getEnumUserType(id.getEnumerated()); final Class<?> instanceClass = getHbmContext() .getInstanceClass(eclassifier); usedIdElement.addElement("type").addAttribute("name", typeName) .addElement("param").addAttribute("name", "enumClassName").addText( instanceClass.getName()); } else if (!getHbmContext().isGeneratedByEMF() && getHbmContext().isDynamic(eclassifier)) { throw new UnsupportedOperationException( "DYNAMIC WITH ENUM ID NOT YET SUPPORTED"); } else if (id.getModelEAttribute().getEType().getInstanceClass() != null) { usedIdElement.addElement("type").addAttribute("name", getEnumUserType(id.getEnumerated())) .addElement("param").addAttribute("name", HbMapperConstants.ENUM_CLASS_PARAM).addText( eAttribute.getEType().getInstanceClass() .getName()); } else { final Element typeElement = usedIdElement.addElement("type") .addAttribute("name", hbDynamicEnumType(id.getEnumerated())); typeElement.addElement("param").addAttribute("name", HbMapperConstants.ECLASSIFIER_PARAM).addText( id.getModelEAttribute().getEType().getName()); typeElement.addElement("param").addAttribute("name", HbMapperConstants.EPACKAGE_PARAM).addText( id.getModelEAttribute().getEType().getEPackage() .getNsURI()); } } // TODO define what to do for unsettable id attribute (unlikely, maybe // error) if (generatedValue != null) { if (isCompositeId) { throw new MappingException( "Composite id can not have a generated value " + id.getModelEAttribute().getEContainingClass() .getName() + "/" + id.getModelEAttribute().getName()); } final Element generatorElement = usedIdElement .addElement("generator"); GenericGenerator gg; if (generatedValue.getGenerator() != null && (gg = getGenericGenerator(id.getPaModel(), generatedValue.getGenerator())) != null) { log .debug("GenericGenerator the strategy in the GeneratedValue is ignored (if even set)"); generatorElement.addAttribute("class", gg.getStrategy()); if (gg.getParameters() != null) { for (Parameter param : gg.getParameters()) { generatorElement.addElement("param").addAttribute( "name", param.getName()).addText( param.getValue()); } } } else if (GenerationType.IDENTITY.equals(generatedValue .getStrategy())) { generatorElement.addAttribute("class", "identity"); } else if (GenerationType.TABLE .equals(generatedValue.getStrategy())) { generatorElement.addAttribute("class", IdMapper .hbGeneratorClass(generatedValue.getStrategy())); if (generatedValue.getGenerator() != null) { // table // generator final TableGenerator tg = id.getPaModel() .getTableGenerator(id.getModelEAttribute(), generatedValue.getGenerator()); generatorElement.addElement("param").addAttribute("name", "table").setText( (tg.getTable() != null ? tg.getTable() : "uid_table")); // externalize generatorElement.addElement("param").addAttribute("name", "column").setText( tg.getValueColumnName() != null ? tg .getValueColumnName() : "next_hi_value_column"); // externalize generatorElement.addElement("param").addAttribute("name", "max_lo") .setText((tg.getAllocationSize() - 1) + ""); } else { generatorElement.addElement("param").addAttribute("name", "table").setText("uid_table"); // externalize generatorElement.addElement("param").addAttribute("name", "column").setText("next_hi_value_column"); // externalize } } else if (GenerationType.SEQUENCE.equals(generatedValue .getStrategy())) { if (generatedValue.getGenerator() != null) { final SequenceGenerator sg = id.getPaModel() .getSequenceGenerator(id.getModelEAttribute(), generatedValue.getGenerator()); if (sg.isSetAllocationSize()) { generatorElement.addAttribute("class", "seqhilo"); generatorElement.addElement("param").addAttribute( "name", "sequence").setText( sg.getSequenceName()); // generatorElement.addElement("param").addAttribute("name", // "initialValue").setText( // Integer.toString(sg.getInitialValue())); generatorElement.addElement("param").addAttribute( "name", "max_lo").setText( Integer.toString(sg.getAllocationSize() - 1)); } else { generatorElement .addAttribute("class", IdMapper .hbGeneratorClass(generatedValue .getStrategy())); generatorElement.addElement("param").addAttribute( "name", "sequence").setText( sg.getSequenceName()); } } else { generatorElement.addAttribute("class", IdMapper .hbGeneratorClass(generatedValue.getStrategy())); } } else if (GenerationType.SEQUENCESTYLE.equals(generatedValue .getStrategy())) { generatorElement.addAttribute("class", IdMapper .hbGeneratorClass(generatedValue.getStrategy())); if (generatedValue.getGenerator() != null) { final SequenceStyleGenerator sg = id.getPaModel() .getSequenceStyleGenerator(id.getModelEAttribute(), generatedValue.getGenerator()); generatorElement.addElement("param").addAttribute("name", "sequence_name").setText(sg.getSequenceName()); generatorElement.addElement("param").addAttribute("name", "optimizer").setText( sg.getOptimizer().getName().toLowerCase()); generatorElement.addElement("param").addAttribute("name", "initial_value").setText( Integer.toString(sg.getInitialValue())); generatorElement.addElement("param").addAttribute("name", "increment_size").setText( Integer.toString(sg.getIncrementSize())); } } else { generatorElement.addAttribute("class", IdMapper .hbGeneratorClass(generatedValue.getStrategy())); } } else { // check if there is a one-to-one with pk checkAddForeignGenerator(idElement, aClass); } } // check if one of the ereferences has a one-to-one and a // primarykeyjoincolumn // then use a special generator protected boolean checkAddForeignGenerator(Element idElement, PAnnotatedEClass aClass) { for (PAnnotatedEStructuralFeature aFeature : aClass .getPaEStructuralFeatures()) { if (aFeature instanceof PAnnotatedEReference) { final PAnnotatedEReference aReference = (PAnnotatedEReference) aFeature; if (aReference.getOneToOne() != null && !aReference.getPrimaryKeyJoinColumns().isEmpty()) { final Element genElement = idElement .addElement("generator"); genElement.addAttribute("class", "foreign"); final Element paramElement = genElement.addElement("param"); paramElement.addAttribute("name", "property"); paramElement .addText(aReference.getModelElement().getName()); return true; } } } return false; } /** * Returns a sequence generator on the basis of its name, if not found then * null is returned. */ public GenericGenerator getGenericGenerator(PAnnotatedModel paModel, String name) { for (PAnnotatedEPackage annotatedEPackage : paModel.getPaEPackages()) { final HbAnnotatedEPackage pae = (HbAnnotatedEPackage) annotatedEPackage; for (GenericGenerator gg : pae.getHbGenericGenerators()) { if (gg.getName() != null && gg.getName().compareTo(name) == 0) { if (gg.getStrategy() == null) { throw new MappingException("The GenericGenerator: " + name + " has no strategy defined!"); } return gg; } } } log.debug("No GenericGenerator defined under name: " + name); return null; } }