/*******************************************************************************
* Copyright (c) 1998, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
* 05/16/2008-1.0M8 Guy Pelletier
* - 218084: Implement metadata merging functionality between mapping files
* 09/23/2008-1.1 Guy Pelletier
* - 241651: JPA 2.0 Access Type support
******************************************************************************/
package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Embeddable;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;
import org.eclipse.persistence.internal.jpa.metadata.columns.AttributeOverrideMetadata;
import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
/**
* An embedded relationship accessor. It may define all the same attributes
* as an entity, therefore, it also must handle nesting embedded's to the nth
* level. An embedded owning descriptor is a reference back to the actual
* owning entity's descriptor where the first embedded was discovered.
*
* @author Guy Pelletier
* @since TopLink EJB 3.0 Reference Implementation
*/
public class EmbeddedAccessor extends MappingAccessor {
private List<AttributeOverrideMetadata> m_attributeOverrides;
/**
* INTERNAL:
* Used for OX mapping.
*/
public EmbeddedAccessor() {
super("<embedded>");
}
/**
* INTERNAL:
*/
protected EmbeddedAccessor(String xmlElement) {
super(xmlElement);
}
/**
* INTERNAL:
*/
public EmbeddedAccessor(Annotation embedded, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) {
super(embedded, accessibleObject, classAccessor);
// Set the attribute overrides if some are present.
m_attributeOverrides = new ArrayList<AttributeOverrideMetadata>();
// Process the attribute overrides first.
Annotation attributeOverrides = getAnnotation(AttributeOverrides.class);
if (attributeOverrides != null) {
for (Annotation attributeOverride : (Annotation[]) MetadataHelper.invokeMethod("value", attributeOverrides)) {
m_attributeOverrides.add(new AttributeOverrideMetadata(attributeOverride, accessibleObject));
}
}
// Process the single attribute override second.
Annotation attributeOverride = getAnnotation(AttributeOverride.class);
if (attributeOverride != null) {
m_attributeOverrides.add(new AttributeOverrideMetadata(attributeOverride, accessibleObject));
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public List<AttributeOverrideMetadata> getAttributeOverrides() {
return m_attributeOverrides;
}
/**
* INTERNAL:
*/
@Override
public void initXMLObject(MetadataAccessibleObject accessibleObject) {
super.initXMLObject(accessibleObject);
// Initialize lists of ORMetadata objects.
initXMLObjects(m_attributeOverrides, accessibleObject);
// Set other column metadata that was not populated through OX.
for (AttributeOverrideMetadata attributeOverride : m_attributeOverrides) {
attributeOverride.getColumn().setAttributeName(attributeOverride.getName());
}
}
/**
* INTERNAL:
*/
@Override
public boolean isEmbedded() {
return true;
}
/**
* INTERNAL:
* Process an embedded.
*/
public void process() {
// Process the embeddable class (and its accessors) first.
processEmbeddableClass();
// Create an aggregate mapping and do the rest of the work.
AggregateObjectMapping mapping = new AggregateObjectMapping();
mapping.setIsReadOnly(false);
mapping.setIsNullAllowed(true);
mapping.setReferenceClassName(getReferenceClassName());
mapping.setAttributeName(getAttributeName());
// Will check for PROPERTY access
setAccessorMethods(mapping);
// Process attribute overrides.
for (AttributeOverrideMetadata attributeOverride : m_attributeOverrides) {
processAttributeOverride(mapping, attributeOverride);
}
// Process association overrides (this is an annotation only thing).
processAssociationOverrides(mapping);
// process properties
processProperties(mapping);
// Process a @ReturnInsert and @ReturnUpdate (to log a warning message)
processReturnInsertAndUpdate();
// Store this descriptor metadata. It may be needed again later on to
// look up a mappedBy attribute etc.
getDescriptor().addEmbeddableDescriptor(getReferenceDescriptor());
// Add the mapping to the descriptor and we are done.
getDescriptor().addMapping(mapping);
}
/**
* INTERNAL:
* Process an AssociationOverride annotation for an embedded object, that
* is, an aggregate object mapping in EclipseLink.
*
* This functionality is not supported in XML for JPA.
* TODO: Perhaps we should add it to the EclipseLink XML schema.
*
* Also this functionality is currently optional in the EJB 3.0 spec, but
* since EclipseLink can handle it, it is implemented and assumes the user
* has properly configured its use since it will fail silently.
*/
protected void processAssociationOverride(Object associationOverride, AggregateObjectMapping aggregateMapping) {
MetadataDescriptor aggregateDescriptor = getReferenceDescriptor();
// AssociationOverride.name(), the name of the attribute we want to
// override.
String name = (String) MetadataHelper.invokeMethod("name", associationOverride);
DatabaseMapping mapping = aggregateDescriptor.getMappingForAttributeName(name);
if (mapping != null && mapping.isOneToOneMapping()) {
int index = 0;
for (Annotation joinColumn : (Annotation[]) MetadataHelper.invokeMethod("joinColumns", associationOverride)) {
// We can't change the mapping from the aggregate descriptor
// so we have to add field name translations. This needs to be
// tested since I am not entirely sure if this will actually
// work.
// In composite primary key case, how do we association the
// foreign keys? Right now we assume the association overrides
// are specified in the same order as the original joinColumns,
// therefore in the same order the foreign keys were added to
// the mapping.
DatabaseField fkField = ((OneToOneMapping) mapping).getForeignKeyFields().elementAt(index++);
aggregateMapping.addFieldNameTranslation((String) MetadataHelper.invokeMethod("name", joinColumn), fkField.getName());
}
} else {
// For now fail silently.
}
}
/**
* INTERNAL:
* Process an AssociationOverrides annotation for an embedded object, that
* is, an aggregate object mapping in EclipseLink.
*
* It will also look for an AssociationOverride annotation.
*/
protected void processAssociationOverrides(AggregateObjectMapping mapping) {
// Look for an @AssociationOverrides.
Annotation associationOverrides = getAnnotation(AssociationOverrides.class);
if (associationOverrides != null) {
for (Annotation associationOverride : (Annotation[]) MetadataHelper.invokeMethod("value", associationOverrides)) {
processAssociationOverride(associationOverride, mapping);
}
}
// Look for an @AssociationOverride.
Annotation associationOverride = getAnnotation(AssociationOverride.class);
if (associationOverride != null) {
processAssociationOverride(associationOverride, mapping);
}
}
/**
* INTERNAL:
* Process an attribute override for an embedded object, that is, an
* aggregate object mapping in EclipseLink.
*/
protected void processAttributeOverride(AggregateObjectMapping mapping, AttributeOverrideMetadata attributeOverride) {
String attributeName = attributeOverride.getName();
// Look for an aggregate mapping for the attribute name.
DatabaseMapping aggregateMapping = getReferenceDescriptor().getMappingForAttributeName(attributeName);
if (aggregateMapping == null) {
throw ValidationException.invalidEmbeddableAttribute(getReferenceDescriptor().getJavaClass(), attributeName, getJavaClass(), mapping.getAttributeName());
}
// A sub-class (entity or mapped superclass) to a mapped superclass may
// override an embedded attribute override. This case will only hit
// since an entity may define attribute overrides to override in a
// mapped superclass. Within an embeddable there is no current way to
// specify attribute overrides (besides on the embedded mapping itself).
// So calling getDescriptor() is correct and calling
// getOwningDescriptor() is incorrect since it would allow attribute
// overrides defined on the owning entity to override nested embedded
// attributes.
if (getDescriptor().hasAttributeOverrideFor(attributeName)) {
// Update the field on this metadata column. We do that so that
// an embedded id can associate the correct id fields.
attributeOverride.getColumn().setDatabaseField(getDescriptor().getAttributeOverrideFor(attributeName).getColumn().getDatabaseField());
}
// Now make sure we have a table set on the attribute override field.
// If we need to default one, it should be the table from the owning
// descriptor.
DatabaseField overrideField = attributeOverride.getColumn().getDatabaseField();
if (overrideField.getTableName().equals("")) {
overrideField.setTable(getOwningDescriptor().getPrimaryTable());
}
DatabaseField aggregateField = aggregateMapping.getField();
// If the override field is to an id field, we need to update the list
// of primary keys on the owning descriptor. Embeddables can be shared
// and different owners may want to override the attribute with a
// different column.
if (getOwningDescriptor().isPrimaryKeyField(aggregateField)) {
getOwningDescriptor().removePrimaryKeyField(aggregateField);
getOwningDescriptor().addPrimaryKeyField(overrideField);
}
// Set the field name translation on the mapping.
mapping.addFieldNameTranslation(overrideField.getQualifiedName(), aggregateField.getName());
}
/**
* INTERNAL:
* This method processes an embeddable class, if we have not processed it
* yet. The reason for lazy processing of embeddable class is because of
* rules governing access-type of embeddable. See GlassFish issue #831 for
* more details.
*
* Be careful while changing the order of processing.
*/
protected void processEmbeddableClass() {
EmbeddableAccessor accessor = getProject().getEmbeddableAccessor(getReferenceClassName());
if (accessor == null) {
// Before throwing an exception we must make one final check for
// an Embeddable annotation on the referenced class. At this point
// we know the referenced class was not tagged as an embeddable
// in a mapping file and was not included in the list of classes
// for this persistence unit. Its inclusion therefore in this
// persistence unit is through the use of an Embedded annotation
// or an embedded element within a known entity. Therefore validate
// that the reference class does indeed have an Embeddable
// annotation.
MetadataClass metadataClass = new MetadataClass(getReferenceClass());
if (metadataClass.isAnnotationNotPresent(Embeddable.class)) {
throw ValidationException.invalidEmbeddedAttribute(getJavaClass(), getAttributeName(), getReferenceClass());
} else {
accessor = new EmbeddableAccessor(metadataClass.getAnnotation(Embeddable.class), getReferenceClass(), getProject());
getProject().addEmbeddableAccessor(accessor);
}
}
if (accessor.isProcessed()) {
// We have already processed this embeddable class. Let's validate
// that it is not used in entities with conflicting access type
// when the embeddable doesn't have its own explicit setting. The
// biggest mistake that could occur otherwise is that FIELD
// processing 'could' yield a different mapping set then PROPERTY
// processing would. Do we really care? If both access types
// yielded the same mappings then the only difference would be
// how they are accessed and well ... does it really matter at this
// point? The only way to know if they would yield different
// mappings would be by processing the class for each access type
// and comparing the yield or some other code to manually inspect
// the class. I think this error should be removed since the spec
// states:
// "Embedded objects belong strictly to their owning entity, and
// are not sharable across persistent entities. Attempting to
// share an embedded object across entities has undefined
// semantics."
// I think we should assume the users know what they are are doing
// in this case (that is, if they opt to share an embeddable).
if (! accessor.hasAccess()) {
// We inherited our access from our owning entity.
if (! accessor.getDescriptor().getDefaultAccess().equals(getOwningDescriptor().getDefaultAccess())) {
throw ValidationException.conflictingAccessTypeForEmbeddable(accessor.getJavaClass(), accessor.usesPropertyAccess(), getOwningDescriptor().getJavaClass(), getOwningDescriptor().getClassAccessor().usesPropertyAccess());
}
}
} else {
// Need to set the owning descriptor on the embeddable class before
// we proceed any further in the processing.
accessor.setOwningDescriptor(getOwningDescriptor());
accessor.process();
accessor.setIsProcessed();
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setAttributeOverrides(List<AttributeOverrideMetadata> attributeOverrides) {
m_attributeOverrides = attributeOverrides;
}
}