Package org.hibernate.ogm.util.impl

Source Code of org.hibernate.ogm.util.impl.PropertyMetadataProvider

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* JBoss, Home of Professional Open Source
* Copyright 2010-2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA  02110-1301, USA.
*/
package org.hibernate.ogm.util.impl;

import org.hibernate.HibernateException;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.ogm.datastore.map.impl.MapAssociationSnapshot;
import org.hibernate.ogm.datastore.spi.Association;
import org.hibernate.ogm.datastore.spi.AssociationContext;
import org.hibernate.ogm.datastore.spi.Tuple;
import org.hibernate.ogm.dialect.GridDialect;
import org.hibernate.ogm.grid.AssociationKey;
import org.hibernate.ogm.grid.AssociationKind;
import org.hibernate.ogm.grid.EntityKey;
import org.hibernate.ogm.grid.RowKey;
import org.hibernate.ogm.persister.CollectionPhysicalModel;
import org.hibernate.ogm.persister.EntityKeyBuilder;
import org.hibernate.ogm.persister.OgmCollectionPersister;
import org.hibernate.ogm.persister.OgmEntityPersister;
import org.hibernate.ogm.type.GridType;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.OneToOneType;
import org.hibernate.type.Type;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* @author Emmanuel Bernard
*/
public class PropertyMetadataProvider {
  private String tableName;
  private String[] keyColumnNames;
  private GridType keyGridType;
  private Object key;
  private SessionImplementor session;
  private AssociationKey collectionMetadataKey;
  private Association collectionMetadata;
  private Object[] columnValues;
  private GridDialect gridDialect;
  private OgmCollectionPersister collectionPersister;
  private boolean inverse;
  private Type propertyType;
  private String[] rowKeyColumnNames;
  private AssociationContext associationContext;
  /*
   * Return true if the other side association has been searched and not been found
   * The other side association is searched when we are looking forward to udpate it
   * and need to build the corresponding association key.
   *
   * It uses Boolean instead of boolean to make sure it's used only after being calculated
   */
  private Boolean isBidirectional;

  //fluent methods for populating data

  public PropertyMetadataProvider gridDialect(GridDialect gridDialect) {
    this.gridDialect = gridDialect;
    return this;
  }

  //optional: data retrieved from gridManager if not set up
  public PropertyMetadataProvider tableName(String tableName) {
    this.tableName = tableName;
    return this;
  }

  public PropertyMetadataProvider keyColumnNames(String[] keyColumnNames) {
    this.keyColumnNames = keyColumnNames;
    return this;
  }

  public PropertyMetadataProvider keyGridType(GridType keyGridType) {
    this.keyGridType = keyGridType;
    return this;
  }

  public PropertyMetadataProvider session(SessionImplementor session) {
    this.session = session;
    return this;
  }

  public PropertyMetadataProvider key(Object key) {
    this.key = key;
    return this;
  }

  public PropertyMetadataProvider keyColumnValues(Object[] columnValues) {
    this.columnValues = columnValues;
    return this;
  }

  public PropertyMetadataProvider inverse() {
    this.inverse = true;
    return this;
  }

  //action methods

  private AssociationKey getCollectionMetadataKey() {
    if ( collectionMetadataKey == null ) {
      final Object[] columnValues = getKeyColumnValues();
      collectionMetadataKey = new AssociationKey( tableName, keyColumnNames, columnValues );
      // We have a collection on the main side
      if (collectionPersister != null) {
        EntityKey entityKey;
        // we are explicitly looking to update the non owning side
        if ( inverse ) {
          //look for the other side of the collection, build the key of the other side's entity
          OgmEntityPersister elementPersister = (OgmEntityPersister) collectionPersister.getElementPersister();
          entityKey = EntityKeyBuilder.fromPersister(
              elementPersister,
              (Serializable) key,
              session
          );
          collectionMetadataKey.setCollectionRole( buildCollectionRole(collectionPersister) );
        }
        else {
          //we are on the right side, use the association property
          collectionMetadataKey.setCollectionRole( getUnqualifiedRole( collectionPersister ) );
          entityKey = EntityKeyBuilder.fromPersister(
              (OgmEntityPersister) collectionPersister.getOwnerEntityPersister(),
              (Serializable) key,
              session
          );
        }
        collectionMetadataKey.setOwnerEntityKey( entityKey );
        //TODO add information on the collection type, set, map, bag, list etc

        AssociationKind type = collectionPersister.getElementType().isEntityType() ? AssociationKind.ASSOCIATION : AssociationKind.EMBEDDED;
        collectionMetadataKey.setAssociationKind( type );
        collectionMetadataKey.setRowKeyColumnNames( collectionPersister.getRowKeyColumnNames() );
      }
      // We have a to-one on the main side
      else if ( propertyType != null ) {
        collectionMetadataKey.setAssociationKind( propertyType.isEntityType() ? AssociationKind.ASSOCIATION : AssociationKind.EMBEDDED );
        if ( propertyType instanceof EntityType ) {
          EntityType entityType = (EntityType) propertyType;
          OgmEntityPersister associatedPersister = (OgmEntityPersister) entityType.getAssociatedJoinable( session.getFactory() );
          EntityKey entityKey = new EntityKey(
              associatedPersister.getTableName(),
              associatedPersister.getIdentifierColumnNames(),
              columnValues
          );
          collectionMetadataKey.setOwnerEntityKey( entityKey );
          collectionMetadataKey.setRowKeyColumnNames( rowKeyColumnNames );
          collectionMetadataKey.setCollectionRole( getCollectionRoleFromToOne( associatedPersister ) );
        }
        else {
          throw new AssertionFailure( "Cannot detect associated entity metadata. propertyType is of unexpected type: " + propertyType.getClass() );
        }
      }
      else {
        throw new AssertionFailure( "Cannot detect associated entity metadata: collectionPersister and propertyType are both null" );
      }
    }
    return collectionMetadataKey;
  }

  /*
   * Try and find the inverse association matching from the associated entity
   * If a match is found, use the other side's association name as role
   * Otherwise use the table name
   */
  //TODO we could cache such knowledge in a service if that turns out to be costly
  private String getCollectionRoleFromToOne(OgmEntityPersister associatedPersister) {
    //code logic is slightly duplicated but the input and context is different, hence this choice
    Type[] propertyTypes = associatedPersister.getPropertyTypes();
    String otherSidePropertyName = null;
    for ( int index = 0 ; index <  propertyTypes.length ; index++ ) {
      Type type = propertyTypes[index];
      boolean matching = false;
      //we try and restrict type search as much as possible
      //we look for associations that also are collections
      if ( type.isAssociationType() && type.isCollectionType() ) {
        matching = isCollectionMatching( (CollectionType) type, tableName );
      }
      //we look for associations that are to-one
      else if ( type.isAssociationType() && ! type.isCollectionType() ) { //isCollectionType redundant but kept for readability
        matching = isToOneMatching( associatedPersister, index, type );
      }
      if ( matching ) {
        otherSidePropertyName = associatedPersister.getPropertyNames()[index];
        break;
      }
    }
    return processOtherSidePropertyName( otherSidePropertyName );
  }

  private boolean isCollectionMatching(CollectionType type, String primarySideTableName) {
    // Find the reverse side collection and check if the table name and key columns are matching
    // what we have on the main side
    String collectionRole = type.getRole();
    CollectionPhysicalModel reverseCollectionPersister = (CollectionPhysicalModel) session.getFactory().getCollectionPersister( collectionRole );
    boolean isSameTable = primarySideTableName.equals( reverseCollectionPersister.getTableName() );
    return isSameTable && Arrays.equals( keyColumnNames, reverseCollectionPersister.getKeyColumnNames() );
  }

  /*
   * Try and find the inverse association matching from the associated entity
   * If a match is found, use the other side's association name as role
   * Otherwise use the table name
   */
  //TODO we could cache such knowledge in a service if that turns out to be costly
  private String buildCollectionRole(OgmCollectionPersister collectionPersister) {
    String otherSidePropertyName = null;
    Loadable elementPersister = (Loadable) collectionPersister.getElementPersister();
    Type[] propertyTypes = elementPersister.getPropertyTypes();

    for ( int index = 0 ; index <  propertyTypes.length ; index++ ) {
      Type type = propertyTypes[index];
      //we try and restrict type search as much as possible
      if ( type.isAssociationType() ) {
        boolean matching = false;
        //if the main side collection is a one-to-many, the reverse side should be a to-one is not a collection
        if ( collectionPersister.isOneToMany() && ! type.isCollectionType() ) {
          matching = isToOneMatching( elementPersister, index, type );
        }
        //if the main side collection is not a one-to-many, the reverse side should be a collection
        else if ( ! collectionPersister.isOneToMany() && type.isCollectionType() ) {
          matching = isCollectionMatching( (CollectionType) type, collectionPersister.getTableName() );
        }
        if ( matching ) {
          otherSidePropertyName = elementPersister.getPropertyNames()[index];
          break;
        }
      }
    }
    return processOtherSidePropertyName( otherSidePropertyName );
  }

  private boolean isToOneMatching(Loadable elementPersister, int index, Type type) {
    if ( ( (EntityType) type ).isOneToOne() ) {
      // If that's a OneToOne check the associated property name and see if it matches where we come from
      // we need to do that as OneToOne don't define columns
      OneToOneType oneToOneType = (OneToOneType) type;
      String associatedProperty = oneToOneType.getRHSUniqueKeyPropertyName();
      if ( associatedProperty != null ) {
        OgmEntityPersister mainSidePersister = (OgmEntityPersister) oneToOneType.getAssociatedJoinable( session.getFactory() );
        try {
          int propertyIndex = mainSidePersister.getPropertyIndex( associatedProperty );
          return mainSidePersister.getPropertyTypes()[propertyIndex] == propertyType;
        }
        catch ( HibernateException e ) {
          //not the right property
          //probably should not happen
        }
      }
    }
    //otherwise we do a key column comparison to see if it matches
    return Arrays.equals( keyColumnNames, elementPersister.getPropertyColumnNames( index ) );
  }

  private String processOtherSidePropertyName(String otherSidePropertyName) {
    //if we found the matching property on the reverse side, we are
    //bidirectional, otherwise we are not
    if ( otherSidePropertyName != null ) {
      isBidirectional = Boolean.TRUE;
    }
    else {
      isBidirectional = Boolean.FALSE;
      otherSidePropertyName = tableName;
    }
    return otherSidePropertyName;
  }

  private String getUnqualifiedRole(CollectionPersister persister) {
    String entity = persister.getOwnerEntityPersister().getEntityName();
    String role = persister.getRole();
    return role.substring( entity.length() + 1 );
  }

  private Object[] getKeyColumnValues() {
    if ( columnValues == null ) {
      columnValues = LogicalPhysicalConverterHelper.getColumnsValuesFromObjectValue(
          key, keyGridType, keyColumnNames, session
      );
    }
    return columnValues;
  }

  public Tuple createAndPutAssociationTuple(RowKey rowKey) {
    Tuple associationTuple = gridDialect.createTupleAssociation( getCollectionMetadataKey(), rowKey );
    getCollectionMetadata().put( rowKey, associationTuple);
    return associationTuple;
  }

  /*
   * Load a collection and create it if it is not found
   */
  public Association getCollectionMetadata() {
    if ( collectionMetadata == null ) {
      // Compute bi-directionality first
      AssociationKey key = getCollectionMetadataKey();
      if ( isBidirectional == Boolean.FALSE ){
        //fake association to prevent unidirectional associations to keep record of the inverse side
        collectionMetadata = new Association( new MapAssociationSnapshot( Collections.EMPTY_MAP ) );
      }
      else {
        collectionMetadata = gridDialect.getAssociation( key, this.getAssociationContext() );
        if (collectionMetadata == null) {
          collectionMetadata = gridDialect.createAssociation( key );
        }
      }
    }
    return collectionMetadata;
  }

  /*
   * Does not create a collection if it is not found
   */
  public Association getCollectionMetadataOrNull() {
    if ( collectionMetadata == null ) {
      collectionMetadata = gridDialect.getAssociation( getCollectionMetadataKey(), this.getAssociationContext() );
    }
    return collectionMetadata;
  }

  public void flushToCache() {
    //If we don't have a bidirectional association, do not update the info
    //to prevent unidirectional associations to keep record of the inverse side
    if ( isBidirectional != Boolean.FALSE ) {
      if ( getCollectionMetadata().isEmpty() ) {
        gridDialect.removeAssociation( getCollectionMetadataKey() );
        collectionMetadata = null;
      }
      else {
        gridDialect.updateAssociation( getCollectionMetadata(), getCollectionMetadataKey() );
      }
    }
  }

  public PropertyMetadataProvider collectionPersister(OgmCollectionPersister collectionPersister) {
    this.collectionPersister = collectionPersister;
    return this;
  }

  public PropertyMetadataProvider propertyType(Type type) {
    this.propertyType = type;
    return this;
  }

  public PropertyMetadataProvider rowKeyColumnNames(String[] rowKeyColumnNames) {
    this.rowKeyColumnNames = rowKeyColumnNames;
    return this;
  }

  private AssociationContext getAssociationContext() {
    if ( associationContext == null ) {
      if ( collectionPersister != null ) {
        associationContext = collectionPersister.getAssociationContext();
      }
      else {
        List<String> selectableColumns = new ArrayList<String>( rowKeyColumnNames.length );
        for ( String column : rowKeyColumnNames ) {
          selectableColumns.add( column );
        }
        associationContext = new AssociationContext( selectableColumns );
      }
    }
    return associationContext;
  }
}
TOP

Related Classes of org.hibernate.ogm.util.impl.PropertyMetadataProvider

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.