| /******************************************************************************* |
| * Copyright (c) 2006, 2011 Oracle. 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: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.jpa.core.internal.context; |
| |
| import java.util.Iterator; |
| import org.eclipse.jpt.common.utility.internal.CollectionTools; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.Transformer; |
| import org.eclipse.jpt.jpa.core.context.AttributeMapping; |
| import org.eclipse.jpt.jpa.core.context.Column; |
| import org.eclipse.jpt.jpa.core.context.ColumnMapping; |
| import org.eclipse.jpt.jpa.core.context.Entity; |
| import org.eclipse.jpt.jpa.core.context.JoinColumn; |
| import org.eclipse.jpt.jpa.core.context.JoinTable; |
| import org.eclipse.jpt.jpa.core.context.JpaNamedContextNode; |
| import org.eclipse.jpt.jpa.core.context.PersistentType; |
| import org.eclipse.jpt.jpa.core.context.ReadOnlyAttributeOverride; |
| import org.eclipse.jpt.jpa.core.context.ReadOnlyJoinColumn; |
| import org.eclipse.jpt.jpa.core.context.ReadOnlyPersistentAttribute; |
| import org.eclipse.jpt.jpa.core.context.ReadOnlyRelationship; |
| import org.eclipse.jpt.jpa.core.context.ReferenceTable; |
| import org.eclipse.jpt.jpa.core.context.Relationship; |
| import org.eclipse.jpt.jpa.core.context.RelationshipMapping; |
| import org.eclipse.jpt.jpa.core.context.TypeMapping; |
| import org.eclipse.jpt.jpa.core.jpa2.context.AttributeMapping2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.context.CollectionMapping2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.context.ElementCollectionMapping2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.context.MetamodelField; |
| import org.eclipse.jpt.jpa.db.Table; |
| |
| /** |
| * Gather some of the behavior common to the Java and XML models. :-( |
| */ |
| public final class MappingTools { |
| |
| /** |
| * Default join table name from the JPA spec:<br> |
| * "The concatenated names of the two associated primary |
| * entity tables, separated by a underscore." |
| * <pre> |
| * [owning table name]_[target table name] |
| * </pre> |
| * <strong>NB:</strong> The <em>names</em> are concatenated, |
| * <em>not</em> the <em>identifiers</em>. |
| * E.g. the join table for <code>"Foo"</code> and <code>"baR"</code> |
| * (where the "delimited" identifier is required) is: |
| * <pre> |
| * "Foo_baR" |
| * </pre> |
| * not |
| * <pre> |
| * "Foo"_"baR" |
| * </pre> |
| * As a result, we cannot honestly calculate the default name without a |
| * database connection. We need the database to convert the resulting name |
| * to an identifier appropriate to the current database. |
| */ |
| public static String buildJoinTableDefaultName(ReadOnlyRelationship relationship) { |
| if (relationship.getJpaProject().getDataSource().connectionProfileIsActive()) { |
| return buildDbJoinTableDefaultName(relationship); |
| } |
| // continue with a "best effort": |
| String owningTableName = relationship.getTypeMapping().getPrimaryTableName(); |
| if (owningTableName == null) { |
| return null; |
| } |
| RelationshipMapping relationshipMapping = relationship.getMapping(); |
| if (relationshipMapping == null) { |
| return null; |
| } |
| Entity targetEntity = relationshipMapping.getResolvedTargetEntity(); |
| if (targetEntity == null) { |
| return null; |
| } |
| String targetTableName = targetEntity.getPrimaryTableName(); |
| if (targetTableName == null) { |
| return null; |
| } |
| return owningTableName + '_' + targetTableName; |
| } |
| |
| /** |
| * Use the database to build a more accurate default name. |
| */ |
| private static String buildDbJoinTableDefaultName(ReadOnlyRelationship relationship) { |
| Table owningTable = relationship.getTypeMapping().getPrimaryDbTable(); |
| if (owningTable == null) { |
| return null; |
| } |
| RelationshipMapping relationshipMapping = relationship.getMapping(); |
| if (relationshipMapping == null) { |
| return null; |
| } |
| Entity targetEntity = relationshipMapping.getResolvedTargetEntity(); |
| if (targetEntity == null) { |
| return null; |
| } |
| Table targetTable = targetEntity.getPrimaryDbTable(); |
| if (targetTable == null) { |
| return null; |
| } |
| String name = owningTable.getName() + '_' + targetTable.getName(); |
| return owningTable.getDatabase().convertNameToIdentifier(name); |
| } |
| |
| /** |
| * Default collection table name from the JPA spec:<br> |
| * "The concatenation of the name of the containing entity and |
| * the name of the collection attribute, separated by an underscore." |
| * <pre> |
| * [owning entity name]_[attribute name] |
| * </pre> |
| */ |
| public static String buildCollectionTableDefaultName(ElementCollectionMapping2_0 mapping) { |
| Entity entity = mapping.getEntity(); |
| if (entity == null) { |
| return null; |
| } |
| String owningEntityName = entity.getName(); |
| String attributeName = mapping.getName(); |
| return owningEntityName + '_' + attributeName; |
| } |
| |
| /** |
| * Return the join column's default name; |
| * which is typically<pre> |
| * [attribute name]_[referenced column name] |
| * </pre> |
| * But, if we don't have an attribute name (e.g. in a unidirectional |
| * one-to-many or many-to-many) is<pre> |
| * [target entity name]_[referenced column name] |
| * </pre> |
| * @see #buildJoinTableDefaultName(ReadOnlyRelationship) |
| */ |
| public static String buildJoinColumnDefaultName(ReadOnlyJoinColumn joinColumn, ReadOnlyJoinColumn.Owner owner) { |
| if (owner.joinColumnsSize() != 1) { |
| return null; |
| } |
| String prefix = owner.getAttributeName(); |
| if (prefix == null) { |
| Entity targetEntity = owner.getRelationshipTarget(); |
| if (targetEntity == null) { |
| return null; |
| } |
| prefix = targetEntity.getName(); |
| } |
| // It's not clear which of these is correct.... |
| // The spec implies that the referenced column is always the |
| // *primary key* column of the target entity; i.e. the primary key as |
| // defined on the *database* (or, possibly, the primary key as defined |
| // by the target entity's Id mapping?), not the name of the referenced |
| // column irrespective of whether it is the primary key on the target |
| // entity's table. But this seems like it would be wrong; since the |
| // referenced column need not be a primary key and we don't always have |
| // access to the database. |
| // |
| // Column targetColumn = joinColumn.getTargetPrimaryKeyDbColumn(); |
| String targetColumnName = joinColumn.getReferencedColumnName(); |
| if (targetColumnName == null) { |
| return null; |
| } |
| String name = prefix + '_' + targetColumnName; |
| // Again, it's not clear which of these is correct.... |
| // Converting the name to an identifier will result in the identifier |
| // being delimited nearly every time (at least on non-Sybase/MS |
| // databases); but that probably is not the intent of the spec.... |
| // |
| // return targetColumn.getDatabase().convertNameToIdentifier(name); |
| return name; |
| } |
| |
| /** |
| * Return the name of the attribute in the specified mapping's target entity |
| * that is owned by the mapping. |
| */ |
| public static String getTargetAttributeName(RelationshipMapping relationshipMapping) { |
| if (relationshipMapping == null) { |
| return null; |
| } |
| Entity targetEntity = relationshipMapping.getResolvedTargetEntity(); |
| if (targetEntity == null) { |
| return null; |
| } |
| for (ReadOnlyPersistentAttribute attribute : CollectionTools.iterable(targetEntity.getPersistentType().allAttributes())) { |
| if (attribute.getMapping().isOwnedBy(relationshipMapping)) { |
| return attribute.getName(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * If appropriate, return the name of the single primary key column of the |
| * relationship target. |
| * Spec states:<br> |
| * "The same name as the primary key column of the referenced table."<br> |
| * We are assuming that the primary key column is defined by the mappings instead of the database. |
| */ |
| public static String buildJoinColumnDefaultReferencedColumnName(ReadOnlyJoinColumn.Owner joinColumnOwner) { |
| if (joinColumnOwner.joinColumnsSize() != 1) { |
| return null; |
| } |
| Entity targetEntity = joinColumnOwner.getRelationshipTarget(); |
| if (targetEntity == null) { |
| return null; |
| } |
| return targetEntity.getPrimaryKeyColumnName(); |
| } |
| |
| public static ColumnMapping getColumnMapping(String attributeName, PersistentType persistentType) { |
| if ((attributeName == null) || (persistentType == null)) { |
| return null; |
| } |
| for (Iterator<ReadOnlyPersistentAttribute> stream = persistentType.allAttributes(); stream.hasNext(); ) { |
| ReadOnlyPersistentAttribute persAttribute = stream.next(); |
| if (attributeName.equals(persAttribute.getName())) { |
| if (persAttribute.getMapping() instanceof ColumnMapping) { |
| return (ColumnMapping) persAttribute.getMapping(); |
| } |
| // keep looking or return null??? |
| } |
| } |
| return null; |
| } |
| |
| public static RelationshipMapping getRelationshipMapping(String attributeName, TypeMapping typeMapping) { |
| if ((attributeName == null) || (typeMapping == null)) { |
| return null; |
| } |
| for (Iterator<AttributeMapping> stream = typeMapping.allAttributeMappings(); stream.hasNext(); ) { |
| AttributeMapping attributeMapping = stream.next(); |
| if (attributeName.equals(attributeMapping.getName())) { |
| if (attributeMapping instanceof RelationshipMapping) { |
| return (RelationshipMapping) attributeMapping; |
| } |
| // keep looking or return null??? |
| } |
| } |
| return null; |
| } |
| |
| public static void convertReferenceTableDefaultToSpecifiedJoinColumn(ReferenceTable referenceTable) { |
| JoinColumn defaultJoinColumn = referenceTable.getDefaultJoinColumn(); |
| if (defaultJoinColumn != null) { |
| String columnName = defaultJoinColumn.getDefaultName(); |
| String referencedColumnName = defaultJoinColumn.getDefaultReferencedColumnName(); |
| JoinColumn joinColumn = referenceTable.addSpecifiedJoinColumn(); |
| joinColumn.setSpecifiedName(columnName); |
| joinColumn.setSpecifiedReferencedColumnName(referencedColumnName); |
| } |
| } |
| |
| public static void convertJoinTableDefaultToSpecifiedInverseJoinColumn(JoinTable joinTable) { |
| JoinColumn defaultInverseJoinColumn = joinTable.getDefaultInverseJoinColumn(); |
| if (defaultInverseJoinColumn != null) { |
| String columnName = defaultInverseJoinColumn.getDefaultName(); |
| String referencedColumnName = defaultInverseJoinColumn.getDefaultReferencedColumnName(); |
| JoinColumn joinColumn = joinTable.addSpecifiedInverseJoinColumn(0); |
| joinColumn.setSpecifiedName(columnName); |
| joinColumn.setSpecifiedReferencedColumnName(referencedColumnName); |
| } |
| } |
| |
| public static String getMetamodelFieldMapKeyTypeName(CollectionMapping2_0 mapping) { |
| PersistentType targetType = mapping.getResolvedTargetType(); |
| String mapKey = mapping.getMapKey(); |
| if ((mapKey == null) || (targetType == null)) { |
| String mapKeyClass = mapping.getMapKeyClass(); |
| return mapKeyClass != null ? mapKeyClass : MetamodelField.DEFAULT_TYPE_NAME; |
| } |
| ReadOnlyPersistentAttribute mapKeyAttribute = targetType.resolveAttribute(mapKey); |
| if (mapKeyAttribute == null) { |
| return MetamodelField.DEFAULT_TYPE_NAME; |
| } |
| AttributeMapping2_0 mapKeyMapping = (AttributeMapping2_0) mapKeyAttribute.getMapping(); |
| if (mapKeyMapping == null) { |
| return MetamodelField.DEFAULT_TYPE_NAME; |
| } |
| return mapKeyMapping.getMetamodelTypeName(); |
| } |
| |
| // TODO move to TypeMapping? may need different name (or may need to rename existing #resolve...) |
| public static Column resolveOverriddenColumn(TypeMapping overridableTypeMapping, String attributeName) { |
| // convenience null check to simplify client code |
| if (overridableTypeMapping == null) { |
| return null; |
| } |
| |
| for (TypeMapping typeMapping : CollectionTools.iterable(overridableTypeMapping.inheritanceHierarchy())) { |
| Column column = typeMapping.resolveOverriddenColumn(attributeName); |
| if (column != null) { |
| return column; |
| } |
| } |
| return null; |
| } |
| |
| // TODO move to TypeMapping? may need different name (or may need to rename existing #resolve...) |
| public static Relationship resolveOverriddenRelationship(TypeMapping overridableTypeMapping, String attributeName) { |
| // convenience null check to simplify client code |
| if (overridableTypeMapping == null) { |
| return null; |
| } |
| |
| for (TypeMapping typeMapping : CollectionTools.iterable(overridableTypeMapping.inheritanceHierarchy())) { |
| Relationship relationship = typeMapping.resolveOverriddenRelationship(attributeName); |
| if (relationship != null) { |
| return relationship; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return whether the specified nodes are "duplicates". |
| * @see JpaNamedContextNode#duplicates(Object) |
| */ |
| public static <T extends JpaNamedContextNode<? super T>> boolean nodesAreDuplicates(T node1, T node2) { |
| return nodesArePotentalDuplicates_(node1, node2) && |
| ! node1.overrides(node2) && |
| ! node2.overrides(node1); |
| } |
| |
| /** |
| * Return whether the specified nodes<ul> |
| * <li>are <em>not</em> the same node |
| * <li>have the same non-empty name |
| * <li>are in the same persistence unit |
| * </ul> |
| */ |
| private static <T extends JpaNamedContextNode<? super T>> boolean nodesArePotentalDuplicates_(T node1, T node2) { |
| return (node1 != node2) && |
| StringTools.stringIsNotEmpty(node1.getName()) && |
| node1.getName().equals(node2.getName()) && |
| (node1.getPersistenceUnit() == node2.getPersistenceUnit()); |
| } |
| |
| /** |
| * Return whether the first specified node "overrides" the second, |
| * based on the "precedence" of their classes. |
| * @see JpaNamedContextNode#overrides(Object) |
| */ |
| public static <T extends JpaNamedContextNode<? super T>> boolean nodeOverrides(T node1, T node2, Iterable<Class<? extends T>> precedenceTypeList) { |
| // this isn't ideal, but use it until adopters protest... |
| return nodesArePotentalDuplicates_(node1, node2) && |
| calculatePrecedence(node1, precedenceTypeList) < calculatePrecedence(node2, precedenceTypeList); |
| } |
| |
| /** |
| * Loop through the specified classes; return the index of the first class |
| * the specified node is an instance of. |
| */ |
| private static <T extends JpaNamedContextNode<? super T>> int calculatePrecedence(T node, Iterable<Class<? extends T>> precedenceTypeList) { |
| int precedence = 0; |
| for (Class<?> nodeClass : precedenceTypeList) { |
| if (nodeClass.isInstance(node)) { |
| return precedence; |
| } |
| precedence++; |
| } |
| throw new IllegalArgumentException("unknown named node: " + node); //$NON-NLS-1$ |
| } |
| |
| public static String getPrimaryKeyColumnName(Entity entity) { |
| String pkColumnName = null; |
| for (Iterator<ReadOnlyPersistentAttribute> stream = entity.getPersistentType().allAttributes(); stream.hasNext(); ) { |
| ReadOnlyPersistentAttribute attribute = stream.next(); |
| String current = attribute.getPrimaryKeyColumnName(); |
| if (current != null) { |
| // 229423 - if the attribute is a primary key, but it has an attribute override, |
| // use the override column instead |
| ReadOnlyAttributeOverride attributeOverride = entity.getAttributeOverrideContainer().getOverrideNamed(attribute.getName()); |
| if (attributeOverride != null) { |
| current = attributeOverride.getColumn().getName(); |
| } |
| } |
| if (pkColumnName == null) { |
| pkColumnName = current; |
| } else { |
| if (current != null) { |
| // if we encounter a composite primary key, return null |
| return null; |
| } |
| } |
| } |
| // if we encounter only a single primary key column name, return it |
| return pkColumnName; |
| } |
| |
| /** |
| * "Unqualify" the specified attribute name, removing the mapping's name |
| * from the front of the attribute name if it is present. For example, if |
| * the mapping's name is <code>"foo"</code>, the attribute name |
| * <code>"foo.bar"</code> would be converted to <code>"bar"</code>). |
| * Return <code>null</code> if the attribute name cannot be "unqualified". |
| */ |
| public static String unqualify(String mappingName, String attributeName) { |
| if (mappingName == null) { |
| return null; |
| } |
| if ( ! attributeName.startsWith(mappingName)) { |
| return null; |
| } |
| int mappingNameLength = mappingName.length(); |
| if (attributeName.length() <= mappingNameLength) { |
| return null; |
| } |
| return (attributeName.charAt(mappingNameLength) == '.') ? attributeName.substring(mappingNameLength + 1) : null; |
| } |
| |
| /** |
| * This transformer will prepend a specified qualifier, followed by a |
| * dot ('.'), to a string. For example, if a mapping's name is |
| * <code>"foo"</code> and one of its attribute's is named |
| * <code>"bar"</code>, the attribute's name will be transformed |
| * into <code>"foo.bar"</code>. If the specified qualifier is |
| * <code>null</code> (or an empty string), only a dot will be prepended |
| * to a string. |
| */ |
| public static class QualifierTransformer |
| implements Transformer<String, String> |
| { |
| private final String prefix; |
| public QualifierTransformer(String qualifier) { |
| super(); |
| this.prefix = (qualifier == null) ? "." : qualifier + '.'; //$NON-NLS-1$ |
| } |
| public String transform(String s) { |
| return this.prefix + s; |
| } |
| } |
| |
| |
| // ********** constructor ********** |
| |
| /** |
| * Suppress default constructor, ensuring non-instantiability. |
| */ |
| private MappingTools() { |
| super(); |
| throw new UnsupportedOperationException(); |
| } |
| } |