/**
   * Interface method implementation. This method is transactional by default and implemented by
   * calling {@link PersistenceDelegate#makePersistent(PersistentEntity[], PersistenceProvider[])}
   * For multi-sharded entities, the TX guarantee is limited to per shard and DOES NOT span across
   * multiple shards.
   *
   * @see PersistenceManager#makePersistent(PersistentEntity[])
   */
  public PersistentEntity[] makePersistent(PersistentEntity[] entities)
      throws PersistenceException {
    if (null == entities || entities.length == 0) {
      // If no entities to persist then return whatever is passed and do not execute rest of the
      // code.
      return entities;
    }
    entities = filterNullEntities(entities);

    // place holder for the multi-sharded entity, if any, that has been passed into this persistence
    // call
    AbstractMultiShardedPersistentEntity multiShardedEntity = null;

    for (PersistentEntity persistentEntity : entities) {
      // For Multi sharded persistent entities, check if all are multi-sharded and have the same
      // shard count and values
      if (AbstractMultiShardedPersistentEntity.class.isAssignableFrom(
          persistentEntity.getClass())) {
        multiShardedEntity = (AbstractMultiShardedPersistentEntity) persistentEntity;
        // check for validity of multi-sharded persistent entities
        checkValidityOfMultiShardedPersistentities(multiShardedEntity, entities);
      }
    }

    if (multiShardedEntity != null) {
      // data is valid. Iterate through shards and invoke the persistence call on the delegate
      // once for each retrieved shard hint
      for (String shardHint : multiShardedEntity.getShardHints()) {
        // set the returned shards one at a time and make persistence calls on the delegate
        for (PersistentEntity entityToPersist : entities) {
          AbstractMultiShardedPersistentEntity shardedEntityToPersist =
              (AbstractMultiShardedPersistentEntity) entityToPersist;
          shardedEntityToPersist.setShardHint(shardHint);
        }
        checkAndPopulateShardedEntityContextHolder(entities);
        entities =
            this.persistenceDelegate.makePersistent(entities, findSuitableProviders(entities));
        // unset the context using the first entity
        checkAndUnsetShardedEntityContextHolder(entities[0]);
      }
      // return the persisted multi-sharded entities
      return entities;
    }

    // none of the passed in entities are multi-sharded. Proceed to deal with single sharded (or
    // none) entities
    checkAndPopulateShardedEntityContextHolder(entities);
    entities = this.persistenceDelegate.makePersistent(entities, findSuitableProviders(entities));
    // unset the context using the first entity
    checkAndUnsetShardedEntityContextHolder(entities[0]);
    return entities;
  }
 /**
  * Helper method to set the first of specified PersistentEntity instances into the
  * ShardedEntityContextHolder if it is of type ShardedPersistentEntity. Also checks to see if all
  * the ShardedPersistentEntity instances belong to the same shard i.e. {@link
  * ShardedPersistentEntity#getShardHint()} returns the same value. Throws a PersistenceException
  * otherwise. This defensive check is done to avoid indeterministic transaction boundary behavior
  * when shards are mapped to multiple database instances.
  *
  * @param entities the PersistentEntity instances, the first of which is to be set into the
  *     ShardedEntityContextHolder if it is of type ShardedPersistentEntity
  * @throws PersistenceException in case the shard hints of specified ShardedPersistentEntity
  *     instances do not match
  */
 private void checkAndPopulateShardedEntityContextHolder(PersistentEntity[] entities)
     throws PersistenceException {
   if (entities.length != 0) {
     // check to see if sharded and non-sharded entities are part of the same call
     ShardedEntity shardedEntity = null;
     PersistentEntity nonShardedEntity = null;
     for (PersistentEntity entity : entities) {
       if (ShardedEntity.class.isAssignableFrom(entity.getClass())) {
         shardedEntity = (ShardedEntity) entity;
       } else {
         nonShardedEntity = entity;
       }
     }
     if (shardedEntity != null && nonShardedEntity != null) {
       throw new PersistenceException(
           "Attempt to use sharded and non-sharded persistent entities in the same call. "
               + "Mixed types are : "
               + shardedEntity.getClass().getName()
               + ","
               + nonShardedEntity.getClass().getName());
     }
     PersistentEntity firstEntity = entities[0];
     String shardHint =
         (ShardedEntity.class.isAssignableFrom(firstEntity.getClass()))
             ? ((ShardedEntity) firstEntity).getShardHint()
             : ShardedEntity.DEFAULT_SHARD;
     for (PersistentEntity entity : entities) {
       if (ShardedEntity.class.isAssignableFrom(entity.getClass())) {
         if (!((ShardedEntity) entity).getShardHint().equals(shardHint)) {
           throw new PersistenceException(
               "Shard hints do not match for the specified ShardedEntity instances."
                   + " Mismatched values are : "
                   + shardHint
                   + ","
                   + ((ShardedEntity) entity).getShardHint());
         }
       }
       // Set the PersistentEntity into the ShardedEntityContextHolder if the type is
       // ShardedPersistentEntity. Will be used in Datasource resolution
       if (ShardedEntity.class.isAssignableFrom(firstEntity.getClass())) {
         ShardedEntityContextHolder.setShardedEntity((ShardedEntity) firstEntity);
       }
     }
   }
 }
 /**
  * Helper method to validate multi-sharded persistent entities passed into a persistence call
  *
  * @param multiShardedEntity the AbstractMultiShardedPersistentEntity to be used for validation
  * @param entities the list of PersistentEntity to be validated for multi-shard persistence
  * @throws PersistenceException in case of errors in count or value of shard-hints of the
  *     persistent entities being validated
  */
 private void checkValidityOfMultiShardedPersistentities(
     AbstractMultiShardedPersistentEntity multiShardedEntity, PersistentEntity[] entities)
     throws PersistenceException {
   for (PersistentEntity listPersistentEntity : entities) {
     if (!AbstractMultiShardedPersistentEntity.class.isAssignableFrom(
         listPersistentEntity.getClass())) {
       // attempt to mix multi sharded persistent entity with other types in same persistence call
       throw new PersistenceException(
           "Attempt to persist Multi-sharded persistent entity with other types. Multi-sharded entity found of type : ("
               + multiShardedEntity.getEntityName()
               + ")"
               + multiShardedEntity.getClass().getName()
               + " .One of the other types is : ("
               + listPersistentEntity.getEntityName()
               + ")"
               + listPersistentEntity.getClass().getName());
     }
     // now check if the shard hint count and value match, else throw an exception
     AbstractMultiShardedPersistentEntity listMultiShardedEntity =
         (AbstractMultiShardedPersistentEntity) listPersistentEntity;
     if (multiShardedEntity.getShardHints().length
         != listMultiShardedEntity.getShardHints().length) {
       throw new PersistenceException(
           "Multi-shard count mismatch between entities - ("
               + multiShardedEntity.getEntityName()
               + ")and("
               + listPersistentEntity.getEntityName()
               + ")");
     }
     // also check if the individual shard hint values match, else throw an exception
     for (int i = 0; i < multiShardedEntity.getShardHints().length; i++) {
       if (!multiShardedEntity.getShardHints()[i].equalsIgnoreCase(
           listMultiShardedEntity.getShardHints()[i])) {
         throw new PersistenceException(
             "Multi-shard values mismatch between entities - ("
                 + multiShardedEntity.getEntityName()
                 + ")and("
                 + listPersistentEntity.getEntityName()
                 + ")");
       }
     }
   }
 }
 /** Helper method to locate the PersistenceProvider for the specified PersistentEntity */
 private PersistenceProvider findSuitableProvider(PersistentEntity entity)
     throws PersistenceException {
   return findSuitableProvider(entity.getClass());
 }