/******************************************************************************* * Copyright (c) 2012, 2016 Oracle and/or its affiliates. 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: * 10/09/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support * 10/25/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support * 10/30/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support * 11/28/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support ******************************************************************************/ package org.eclipse.persistence.internal.jpa.metadata.converters; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.jpa.metadata.ORMetadata; import org.eclipse.persistence.internal.jpa.metadata.accessors.MetadataAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAnnotation; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass; import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings; import org.eclipse.persistence.internal.mappings.converters.AttributeNamePrefix; import org.eclipse.persistence.mappings.DatabaseMapping; /** * Object to hold onto convert metadata. * * Key notes: * - any metadata mapped from XML to this class must be compared in the * equals method. * - any metadata mapped from XML to this class must be initialized in the * initXMLObject method. * - when loading from annotations, the constructor accepts the metadata * accessor this metadata was loaded from. Used it to look up any * 'companion' annotation needed for processing. * - methods should be preserved in alphabetical order. * * @author Guy Pelletier * @since EclipseLink 2.5 */ public class ConvertMetadata extends ORMetadata { private String m_text; private Boolean m_isForMapKey; private Boolean m_disableConversion; private MetadataClass m_converterClass; private String m_converterClassName; private String m_attributeName; /** * INTERNAL: * Used for XML loading. */ public ConvertMetadata() { super("<convert>"); } /** * INTERNAL: * Used for annotation loading. */ public ConvertMetadata(MetadataAnnotation convert, MetadataAccessor accessor) { super(convert, accessor); m_converterClass = getMetadataClass(convert.getAttributeClass("converter", Void.class)); m_attributeName = convert.getAttributeString("attributeName"); m_disableConversion = convert.getAttributeBooleanDefaultFalse("disableConversion"); } /** * INTERNAL: * Return true if any auto apply converter should be disabled. */ public boolean disableConversion() { return m_disableConversion != null && m_disableConversion; } /** * INTERNAL: */ @Override public boolean equals(Object objectToCompare) { if (objectToCompare instanceof ConvertMetadata) { ConvertMetadata convert = (ConvertMetadata) objectToCompare; if (! valuesMatch(m_text, convert.getText())) { return false; } if (! valuesMatch(m_converterClassName, convert.getConverterClassName())) { return false; } if (! valuesMatch(m_attributeName, convert.getAttributeName())) { return false; } return valuesMatch(m_disableConversion, convert.getDisableConversion()); } return false; } @Override public int hashCode() { int result = m_text != null ? m_text.hashCode() : 0; result = 31 * result + (m_disableConversion != null ? m_disableConversion.hashCode() : 0); result = 31 * result + (m_converterClassName != null ? m_converterClassName.hashCode() : 0); result = 31 * result + (m_attributeName != null ? m_attributeName.hashCode() : 0); return result; } /** * INTERNAL: * Used for OX mapping. */ public String getAttributeName() { return m_attributeName; } /** * INTERNAL: */ public MetadataClass getConverterClass() { return m_converterClass; } /** * INTERNAL: * Used for OX mapping. */ public String getConverterClassName() { return m_converterClassName; } /** * INTERNAL: * Used for OX mapping. */ public Boolean getDisableConversion() { return m_disableConversion; } /** * INTERNAL: * Used for OX mapping. * Any ORMetadata that supported mixed types, that is, text or other * metadata should override this method. */ @Override protected String getText() { return m_text; } /** * INTERNAL: */ public boolean hasAttributeName() { return m_attributeName != null && ! m_attributeName.equals(""); } /** * INTERNAL: */ public boolean hasConverterClass() { return m_converterClass != null && ! m_converterClass.isVoid(); } /** * INTERNAL: */ @Override public void initXMLObject(MetadataAccessibleObject accessibleObject, XMLEntityMappings entityMappings) { super.initXMLObject(accessibleObject, entityMappings); // Trim any leading and trailing white spaces from the text if specified. if (m_text != null) { m_text = m_text.trim(); } // Initialize the converter class name. m_converterClass = initXMLClassName(m_converterClassName); } /** * INTERNAL: * Return true if this convert metadata is for a map key. Way to tell is * if there is an attribute name that begins with "key". * * Calling this method will also update the attribute name on the first call * to it. This call is made when sorting convert annotations. Unlike XML, * where the user can sort through <convert> and <map-key-convert> elements, * there is only a single Convert annotation that uses a "key" prefix on the * attribute name to signify a map key convert. An unforunate decision by * the JPA spec committee, but we can make it work of course. */ public boolean isForMapKey() { if (m_isForMapKey == null) { final String key = AttributeNamePrefix.KEY.getName(); if (m_attributeName != null && m_attributeName.startsWith(key)) { // Update the attribute name. m_attributeName = m_attributeName.equals(key) ? "" : m_attributeName.substring(key.length() + 1); m_isForMapKey = true; } else { m_isForMapKey = false; } } return m_isForMapKey; } /** * INTERNAL: * Verify mapping passed to register {@code AttributeConverter} class. * @param mapping Database attribute mapping. * @param referenceClass JPA annotated class. * @param accessor Class accessor. * @param embeddedAttributeName Content of {@code <name>} * from {@code attributeName="value.<name>"}. This value shall never * be {@code null} when {@code @ElementCollection} mapping is being * processed. */ private MetadataClass verify(final DatabaseMapping mapping, final MetadataClass referenceClass, final ClassAccessor accessor, final String embeddedAttributeName) throws ValidationException { // Validate the attribute name first if there is one. if (hasAttributeName()) { String attributeName; // Aggregate object mapping if (mapping.isAggregateObjectMapping()) { attributeName = getAttributeName(); // Coming from @ElementCollection mapping with value.<name> attributeName. } else if (mapping.isAggregateCollectionMapping() && embeddedAttributeName != null && embeddedAttributeName.length() > 0) { attributeName = embeddedAttributeName; // Unsupported mapping, throw an exception } else { throw ValidationException.invalidMappingForConvertWithAttributeName( accessor.getJavaClassName(), mapping.getAttributeName()); } // Validate the attribute name existing on the embeddable and update // the reference class. final ClassAccessor embeddableAccessor = getProject().getEmbeddableAccessor(referenceClass); final MappingAccessor mappingAccessor = embeddableAccessor .getDescriptor().getMappingAccessor(attributeName); if (mappingAccessor == null) { throw ValidationException.embeddableAttributeNameForConvertNotFound( accessor.getJavaClassName(), mapping.getAttributeName(), embeddableAccessor.getJavaClassName(), getAttributeName()); } return mappingAccessor.getReferenceClass(); } else { // In an aggregate object case, the attribute name must be specified. if (mapping.isAggregateObjectMapping()) { throw ValidationException.missingMappingConvertAttributeName( accessor.getJavaClassName(), mapping.getAttributeName()); } } return referenceClass; } /** * INTERNAL: * Apply converter class. * @param mapping Database attribute mapping. * @param referenceClass JPA annotated class. * @param accessor Class accessor. * @param isForMapKey Is this converter for MapKey? */ private void apply(final DatabaseMapping mapping, final MetadataClass referenceClass, final ClassAccessor accessor, final boolean isForMapKey) { // If we have a converter class, validate its existence and apply. if (hasConverterClass()) { if (getProject().hasConverterAccessor(getConverterClass())) { getProject().getConverterAccessor(getConverterClass()).process( mapping, isForMapKey, getAttributeName()); } else { throw ValidationException.converterClassNotFound( accessor.getJavaClassName(), mapping.getAttributeName(), getConverterClass().getName()); } } else { // Check for an auto apply converter for the reference class. if (getProject().hasAutoApplyConverter(referenceClass)) { if (disableConversion()) { // If we're dealing with an aggregate object mapping we need to ensure we add // the converter to ensure we override an auto-apply converter to the mapping. // This converter will update the embedded mapping after it is cloned during // descriptor initialization. // All other mappings can just avoid adding the converter all together. if (mapping.isAggregateObjectMapping()) { getProject().getAutoApplyConverter(referenceClass).process( mapping, isForMapKey, getAttributeName(), true); } } else { // Apply the converter to the mapping. getProject().getAutoApplyConverter(referenceClass).process( mapping, isForMapKey, getAttributeName()); } } } } /** * INTERNAL: * Apply convert for {@code @ElementCollection} mapping where we expect * {@code @Convert} annotation to contain {@code attributeName="value.<name>"}. * @param mapping Database attribute mapping. * @param referenceClass JPA annotated class. * @param accessor Class accessor. * @param embeddedAttributeName Content of {@code <name>} * from {@code attributeName="value.<name>"}. */ public void process(final DatabaseMapping mapping, final MetadataClass referenceClass, final ClassAccessor accessor, final String embeddedAttributeName) { // This code path is always coming from @ElementCollection mapping. final MetadataClass targetReferenceClass = verify(mapping, referenceClass, accessor, embeddedAttributeName); apply(mapping, targetReferenceClass, accessor, false); } /** * INTERNAL: * By the time we get here, we have the mapping that needs to have the * convert applied to. Do some validation checks along with some embedded * mapping traversing if need be and apply the converter. Will look an * auto-apply converter as well if one is not specified directly. * @param mapping Database attribute mapping. * @param referenceClass JPA annotated class. * @param accessor Class accessor. * @param isForMapKey Is this converter for MapKey? */ public void process(final DatabaseMapping mapping, final MetadataClass referenceClass, final ClassAccessor accessor, final boolean isForMapKey) { // This code path is not coming from @ElementCollection mapping // so <name> from value.<name> attributeName is not expected here. final MetadataClass targetReferenceClass = verify(mapping, referenceClass, accessor, null); apply(mapping, targetReferenceClass, accessor, isForMapKey); } /** * INTERNAL: * Used for OX mapping. */ public void setAttributeName(String attributeName) { m_attributeName = attributeName; } /** * INTERNAL: * Used for OX mapping. */ public void setConverterClassName(String converterClassName) { m_converterClassName = converterClassName; } /** * INTERNAL: * Used for OX mapping. */ public void setDisableConversion(Boolean disableConversion) { m_disableConversion = disableConversion; } /** * INTERNAL: * Used for OX mapping. */ public void setText(String text) { m_text = text; } }