Package org.hibernate.cfg

Source Code of org.hibernate.cfg.ComponentPropertyHolder

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.  All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA  02110-1301  USA
*/
package org.hibernate.cfg;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Converts;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.AnnotationException;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;

/**
* PropertyHolder for composites (Embeddable/Embedded).
* <p/>
* To facilitate code comments, I'll often refer to this example:
* <pre>
*     &#064;Embeddable
*     &#064;Convert( attributeName="city", ... )
*     class Address {
*         ...
*         &#064;Convert(...)
*         public String city;
*     }
*
*     &#064;Entity
*     &#064;Convert( attributeName="homeAddress.city", ... )
*     class Person {
*         ...
*         &#064;Embedded
*         &#064;Convert( attributeName="city", ... )
*         public Address homeAddress;
*     }
* </pre>
*
* As you can see, lots of ways to specify the conversion for embeddable attributes :(
*
* @author Steve Ebersole
* @author Emmanuel Bernard
*/
public class ComponentPropertyHolder extends AbstractPropertyHolder {
  //TODO introduce a overrideTable() method for columns held by sec table rather than the hack
  //     joinsPerRealTableName in ClassPropertyHolder
  private Component component;
  private boolean isOrWithinEmbeddedId;

  private boolean virtual;
  private String embeddedAttributeName;
  private Map<String,AttributeConversionInfo> attributeConversionInfoMap;

  public ComponentPropertyHolder(
      Component component,
      String path,
      PropertyData inferredData,
      PropertyHolder parent,
      Mappings mappings) {
    super( path, parent, inferredData.getPropertyClass(), mappings );
    final XProperty embeddedXProperty = inferredData.getProperty();
    setCurrentProperty( embeddedXProperty );
    this.component = component;
    this.isOrWithinEmbeddedId =
        parent.isOrWithinEmbeddedId()
            || ( embeddedXProperty != null &&
            ( embeddedXProperty.isAnnotationPresent( Id.class )
                || embeddedXProperty.isAnnotationPresent( EmbeddedId.class ) ) );

    this.virtual = embeddedXProperty == null;
    if ( !virtual ) {
      this.embeddedAttributeName = embeddedXProperty.getName();
      this.attributeConversionInfoMap = processAttributeConversions( embeddedXProperty );
    }
    else {
      embeddedAttributeName = "";
      this.attributeConversionInfoMap = Collections.emptyMap();
    }
  }

  /**
   * This is called from our constructor and handles (in order):<ol>
   *     <li>@Convert annotation at the Embeddable class level</li>
   *     <li>@Converts annotation at the Embeddable class level</li>
   *     <li>@Convert annotation at the Embedded attribute level</li>
   *     <li>@Converts annotation at the Embedded attribute level</li>
   * </ol>
   * <p/>
   * The order is important to ensure proper precedence.
   * <p/>
   * {@literal @Convert/@Converts} annotations at the Embeddable attribute level are handled in the calls to
   * {@link #startingProperty}.  Duplicates are simply ignored there.
   *
   * @param embeddedXProperty The property that is the composite being described by this ComponentPropertyHolder
   */
  private Map<String,AttributeConversionInfo> processAttributeConversions(XProperty embeddedXProperty) {
    final Map<String,AttributeConversionInfo> infoMap = new HashMap<String, AttributeConversionInfo>();

    final XClass embeddableXClass = embeddedXProperty.getType();

    // as a baseline, we want to apply conversions from the Embeddable and then overlay conversions
    // from the Embedded

    // first apply conversions from the Embeddable...
    {
      // @Convert annotation on the Embeddable class level
      final Convert convertAnnotation = embeddableXClass.getAnnotation( Convert.class );
      if ( convertAnnotation != null ) {
        final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
        if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
          throw new IllegalStateException( "@Convert placed on @Embeddable must define attributeName" );
        }
        infoMap.put( info.getAttributeName(), info );
      }
    }
    {
      // @Converts annotation on the Embeddable class level
      final Converts convertsAnnotation = embeddableXClass.getAnnotation( Converts.class );
      if ( convertsAnnotation != null ) {
        for ( Convert convertAnnotation : convertsAnnotation.value() ) {
          final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
          if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
            throw new IllegalStateException( "@Converts placed on @Embeddable must define attributeName" );
          }
          infoMap.put( info.getAttributeName(), info );
        }
      }
    }

    // then we can overlay any conversions from the Embedded attribute
    {
      // @Convert annotation on the Embedded attribute
      final Convert convertAnnotation = embeddedXProperty.getAnnotation( Convert.class );
      if ( convertAnnotation != null ) {
        final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
        if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
          throw new IllegalStateException( "Convert placed on Embedded attribute must define (sub)attributeName" );
        }
        infoMap.put( info.getAttributeName(), info );
      }
    }
    {
      // @Converts annotation on the Embedded attribute
      final Converts convertsAnnotation = embeddedXProperty.getAnnotation( Converts.class );
      if ( convertsAnnotation != null ) {
        for ( Convert convertAnnotation : convertsAnnotation.value() ) {
          final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
          if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
            throw new IllegalStateException( "Convert placed on Embedded attribute must define (sub)attributeName" );
          }
          infoMap.put( info.getAttributeName(), info );
        }
      }
    }

    return infoMap;
  }

  @Override
  protected String normalizeCompositePath(String attributeName) {
    return embeddedAttributeName + '.' + attributeName;
  }

  @Override
  protected String normalizeCompositePathForLogging(String attributeName) {
    return normalizeCompositePath( attributeName );
  }

  @Override
  public void startingProperty(XProperty property) {
    if ( property == null ) {
      return;
    }

    if ( virtual ) {
      return;
    }

    // again : the property coming in here *should* be the property on the embeddable (Address#city in the example),
    // so we just ignore it if there is already an existing conversion info for that path since they would have
    // precedence

    // technically we should only do this for properties of "basic type"

    final String path = embeddedAttributeName + '.' + property.getName();
    if ( attributeConversionInfoMap.containsKey( path ) ) {
      return;
    }

    {
      // @Convert annotation on the Embeddable attribute
      final Convert convertAnnotation = property.getAnnotation( Convert.class );
      if ( convertAnnotation != null ) {
        final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property );
        attributeConversionInfoMap.put( property.getName(), info );
      }
    }
    {
      // @Converts annotation on the Embeddable attribute
      final Converts convertsAnnotation = property.getAnnotation( Converts.class );
      if ( convertsAnnotation != null ) {
        for ( Convert convertAnnotation : convertsAnnotation.value() ) {
          final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, property );
          attributeConversionInfoMap.put( property.getName(), info );
        }
      }
    }
  }

  @Override
  protected AttributeConversionInfo locateAttributeConversionInfo(XProperty property) {
    final String propertyName = property.getName();

    // conversions on parent would have precedence
    AttributeConversionInfo conversion = locateAttributeConversionInfo( propertyName );
    if ( conversion != null ) {
      return conversion;
    }

    return null;
  }

  @Override
  protected AttributeConversionInfo locateAttributeConversionInfo(String path) {
    final String embeddedPath = embeddedAttributeName + '.' + path;
    AttributeConversionInfo fromParent = parent.locateAttributeConversionInfo( embeddedPath );
    if ( fromParent != null ) {
      return fromParent;
    }

    AttributeConversionInfo fromEmbedded = attributeConversionInfoMap.get( embeddedPath );
    if ( fromEmbedded != null ) {
      return fromEmbedded;
    }

    return attributeConversionInfoMap.get( path );
  }

  public String getEntityName() {
    return component.getComponentClassName();
  }

  public void addProperty(Property prop, Ejb3Column[] columns, XClass declaringClass) {
    //Ejb3Column.checkPropertyConsistency( ); //already called earlier
    /*
     * Check table matches between the component and the columns
     * if not, change the component table if no properties are set
     * if a property is set already the core cannot support that
     */
    if (columns != null) {
      Table table = columns[0].getTable();
      if ( !table.equals( component.getTable() ) ) {
        if ( component.getPropertySpan() == 0 ) {
          component.setTable( table );
        }
        else {
          throw new AnnotationException(
              "A component cannot hold properties split into 2 different tables: "
                  + this.getPath()
          );
        }
      }
    }
    addProperty( prop, declaringClass );
  }

  public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) {
    return parent.addJoin( joinTableAnn, noDelayInPkColumnCreation );

  }

  public String getClassName() {
    return component.getComponentClassName();
  }

  public String getEntityOwnerClassName() {
    return component.getOwner().getClassName();
  }

  public Table getTable() {
    return component.getTable();
  }

  public void addProperty(Property prop, XClass declaringClass) {
    component.addProperty( prop );
  }

  public KeyValue getIdentifier() {
    return component.getOwner().getIdentifier();
  }

  public boolean isOrWithinEmbeddedId() {
    return isOrWithinEmbeddedId;
  }

  public PersistentClass getPersistentClass() {
    return component.getOwner();
  }

  public boolean isComponent() {
    return true;
  }

  public boolean isEntity() {
    return false;
  }

  public void setParentProperty(String parentProperty) {
    component.setParentProperty( parentProperty );
  }

  @Override
  public Column[] getOverriddenColumn(String propertyName) {
    //FIXME this is yukky
    Column[] result = super.getOverriddenColumn( propertyName );
    if ( result == null ) {
      String userPropertyName = extractUserPropertyName( "id", propertyName );
      if ( userPropertyName != null ) result = super.getOverriddenColumn( userPropertyName );
    }
    if ( result == null ) {
      String userPropertyName = extractUserPropertyName( "_identifierMapper", propertyName );
      if ( userPropertyName != null ) result = super.getOverriddenColumn( userPropertyName );
    }
    return result;
  }

  private String extractUserPropertyName(String redundantString, String propertyName) {
    String result = null;
    String className = component.getOwner().getClassName();
    if ( propertyName.startsWith( className )
        && propertyName.length() > className.length() + 2 + redundantString.length() // .id.
        && propertyName.substring(
        className.length() + 1, className.length() + 1 + redundantString.length()
    ).equals( redundantString )
        ) {
      //remove id we might be in a @IdCLass case
      result = className + propertyName.substring( className.length() + 1 + redundantString.length() );
    }
    return result;
  }

  @Override
  public JoinColumn[] getOverriddenJoinColumn(String propertyName) {
    return super.getOverriddenJoinColumn( propertyName );
  }

  @Override
  public String toString() {
    return super.toString() + "(" + parent.normalizeCompositePathForLogging( embeddedAttributeName ) + ")";
  }
}
TOP

Related Classes of org.hibernate.cfg.ComponentPropertyHolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.