Package org.hibernate.metamodel.source.annotations.entity

Source Code of org.hibernate.metamodel.source.annotations.entity.EntityClass

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.metamodel.source.annotations.entity;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.AccessType;
import javax.persistence.DiscriminatorType;
import javax.persistence.PersistenceException;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;

import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.PolymorphismType;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.binding.Caching;
import org.hibernate.metamodel.binding.CustomSQL;
import org.hibernate.metamodel.binding.InheritanceType;
import org.hibernate.metamodel.source.annotations.AnnotationBindingContext;
import org.hibernate.metamodel.source.annotations.HibernateDotNames;
import org.hibernate.metamodel.source.annotations.JPADotNames;
import org.hibernate.metamodel.source.annotations.JandexHelper;
import org.hibernate.metamodel.source.annotations.attribute.ColumnValues;
import org.hibernate.metamodel.source.annotations.attribute.FormulaValue;
import org.hibernate.metamodel.source.annotations.xml.PseudoJpaDotNames;
import org.hibernate.metamodel.source.binder.ConstraintSource;
import org.hibernate.metamodel.source.binder.JpaCallbackClass;
import org.hibernate.metamodel.source.binder.TableSource;

/**
* Represents an entity or mapped superclass configured via annotations/orm-xml.
*
* @author Hardy Ferentschik
*/
public class EntityClass extends ConfiguredClass {
  private final IdType idType;
  private final InheritanceType inheritanceType;

  private final String explicitEntityName;
  private final String customLoaderQueryName;
  private final List<String> synchronizedTableNames;
  private final int batchSize;

  private final TableSource primaryTableSource;
  private final Set<TableSource> secondaryTableSources;
  private final Set<ConstraintSource> constraintSources;

  private boolean isMutable;
  private boolean isExplicitPolymorphism;
  private OptimisticLockStyle optimisticLockStyle;
  private String whereClause;
  private String rowId;
  private Caching caching;
  private boolean isDynamicInsert;
  private boolean isDynamicUpdate;
  private boolean isSelectBeforeUpdate;
  private String customPersister;

  private CustomSQL customInsert;
  private CustomSQL customUpdate;
  private CustomSQL customDelete;

  private boolean isLazy;
  private String proxy;

  private ColumnValues discriminatorColumnValues;
  private FormulaValue discriminatorFormula;
  private Class<?> discriminatorType;
  private String discriminatorMatchValue;
  private boolean isDiscriminatorForced = true;
  private boolean isDiscriminatorIncludedInSql = true;

  private final List<JpaCallbackClass> jpaCallbacks;

  public EntityClass(
      ClassInfo classInfo,
      EntityClass parent,
      AccessType hierarchyAccessType,
      InheritanceType inheritanceType,
      AnnotationBindingContext context) {
    super( classInfo, hierarchyAccessType, parent, context );
    this.inheritanceType = inheritanceType;
    this.idType = determineIdType();
    boolean hasOwnTable = definesItsOwnTable();
    this.explicitEntityName = determineExplicitEntityName();
    this.constraintSources = new HashSet<ConstraintSource>();

    if ( hasOwnTable ) {
      AnnotationInstance tableAnnotation = JandexHelper.getSingleAnnotation(
          getClassInfo(),
          JPADotNames.TABLE
      );
      this.primaryTableSource = createTableSource( tableAnnotation );
    }
    else {
      this.primaryTableSource = null;
    }

    this.secondaryTableSources = createSecondaryTableSources();
    this.customLoaderQueryName = determineCustomLoader();
    this.synchronizedTableNames = determineSynchronizedTableNames();
    this.batchSize = determineBatchSize();
    this.jpaCallbacks = determineEntityListeners();

    processHibernateEntitySpecificAnnotations();
    processCustomSqlAnnotations();
    processProxyGeneration();
    processDiscriminator();
  }

  public ColumnValues getDiscriminatorColumnValues() {
    return discriminatorColumnValues;
  }

  public FormulaValue getDiscriminatorFormula() {
    return discriminatorFormula;
  }

  public Class<?> getDiscriminatorType() {
    return discriminatorType;
  }

  public IdType getIdType() {
    return idType;
  }

  public boolean isExplicitPolymorphism() {
    return isExplicitPolymorphism;
  }

  public boolean isMutable() {
    return isMutable;
  }

  public OptimisticLockStyle getOptimisticLockStyle() {
    return optimisticLockStyle;
  }

  public String getWhereClause() {
    return whereClause;
  }

  public String getRowId() {
    return rowId;
  }

  public Caching getCaching() {
    return caching;
  }

  public TableSource getPrimaryTableSource() {
    if ( definesItsOwnTable() ) {
      return primaryTableSource;
    }
    else {
      return ( (EntityClass) getParent() ).getPrimaryTableSource();
    }
  }

  public Set<TableSource> getSecondaryTableSources() {
    return secondaryTableSources;
  }

  public Set<ConstraintSource> getConstraintSources() {
    return constraintSources;
  }

  public String getExplicitEntityName() {
    return explicitEntityName;
  }

  public String getEntityName() {
    return getConfiguredClass().getSimpleName();
  }

  public boolean isDynamicInsert() {
    return isDynamicInsert;
  }

  public boolean isDynamicUpdate() {
    return isDynamicUpdate;
  }

  public boolean isSelectBeforeUpdate() {
    return isSelectBeforeUpdate;
  }

  public String getCustomLoaderQueryName() {
    return customLoaderQueryName;
  }

  public CustomSQL getCustomInsert() {
    return customInsert;
  }

  public CustomSQL getCustomUpdate() {
    return customUpdate;
  }

  public CustomSQL getCustomDelete() {
    return customDelete;
  }

  public List<String> getSynchronizedTableNames() {
    return synchronizedTableNames;
  }

  public String getCustomPersister() {
    return customPersister;
  }

  public boolean isLazy() {
    return isLazy;
  }

  public String getProxy() {
    return proxy;
  }

  public int getBatchSize() {
    return batchSize;
  }

  public boolean isEntityRoot() {
    return getParent() == null;
  }

  public boolean isDiscriminatorForced() {
    return isDiscriminatorForced;
  }

  public boolean isDiscriminatorIncludedInSql() {
    return isDiscriminatorIncludedInSql;
  }

  public String getDiscriminatorMatchValue() {
    return discriminatorMatchValue;
  }

  public List<JpaCallbackClass> getJpaCallbacks() {
    return jpaCallbacks;
  }

  private String determineExplicitEntityName() {
    final AnnotationInstance jpaEntityAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), JPADotNames.ENTITY
    );
    return JandexHelper.getValue( jpaEntityAnnotation, "name", String.class );
  }


  private boolean definesItsOwnTable() {
    return !InheritanceType.SINGLE_TABLE.equals( inheritanceType ) || isEntityRoot();
  }

  private IdType determineIdType() {
    List<AnnotationInstance> idAnnotations = findIdAnnotations( JPADotNames.ID );
    List<AnnotationInstance> embeddedIdAnnotations = findIdAnnotations( JPADotNames.EMBEDDED_ID );

    if ( !idAnnotations.isEmpty() && !embeddedIdAnnotations.isEmpty() ) {
      throw new MappingException(
          "@EmbeddedId and @Id cannot be used together. Check the configuration for " + getName() + "."
      );
    }

    if ( !embeddedIdAnnotations.isEmpty() ) {
      if ( embeddedIdAnnotations.size() == 1 ) {
        return IdType.EMBEDDED;
      }
      else {
        throw new AnnotationException( "Multiple @EmbeddedId annotations are not allowed" );
      }
    }

    if ( !idAnnotations.isEmpty() ) {
      return idAnnotations.size() == 1 ? IdType.SIMPLE : IdType.COMPOSED;
    }
    return IdType.NONE;
  }

  private List<AnnotationInstance> findIdAnnotations(DotName idAnnotationType) {
    List<AnnotationInstance> idAnnotationList = new ArrayList<AnnotationInstance>();
    if ( getClassInfo().annotations().containsKey( idAnnotationType ) ) {
      idAnnotationList.addAll( getClassInfo().annotations().get( idAnnotationType ) );
    }
    ConfiguredClass parent = getParent();
    while ( parent != null ) {
      if ( parent.getClassInfo().annotations().containsKey( idAnnotationType ) ) {
        idAnnotationList.addAll( parent.getClassInfo().annotations().get( idAnnotationType ) );
      }
      parent = parent.getParent();
    }
    return idAnnotationList;
  }

  private void processDiscriminator() {
    if ( !InheritanceType.SINGLE_TABLE.equals( inheritanceType ) ) {
      return;
    }

    final AnnotationInstance discriminatorValueAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), JPADotNames.DISCRIMINATOR_VALUE
    );
    if ( discriminatorValueAnnotation != null ) {
      this.discriminatorMatchValue = discriminatorValueAnnotation.value().asString();
    }

    final AnnotationInstance discriminatorColumnAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), JPADotNames.DISCRIMINATOR_COLUMN
    );

    final AnnotationInstance discriminatorFormulaAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(),
        HibernateDotNames.DISCRIMINATOR_FORMULA
    );


    Class<?> type = String.class; // string is the discriminator default
    if ( discriminatorFormulaAnnotation != null ) {
      String expression = JandexHelper.getValue( discriminatorFormulaAnnotation, "value", String.class );
      discriminatorFormula = new FormulaValue( getPrimaryTableSource().getExplicitTableName(), expression );
    }
    discriminatorColumnValues = new ColumnValues( null ); //(stliu) give null here, will populate values below
    discriminatorColumnValues.setNullable( false ); // discriminator column cannot be null
    if ( discriminatorColumnAnnotation != null ) {

      DiscriminatorType discriminatorType = Enum.valueOf(
          DiscriminatorType.class, discriminatorColumnAnnotation.value( "discriminatorType" ).asEnum()
      );
      switch ( discriminatorType ) {
        case STRING: {
          type = String.class;
          break;
        }
        case CHAR: {
          type = Character.class;
          break;
        }
        case INTEGER: {
          type = Integer.class;
          break;
        }
        default: {
          throw new AnnotationException( "Unsupported discriminator type: " + discriminatorType );
        }
      }

      discriminatorColumnValues.setName(
          JandexHelper.getValue(
              discriminatorColumnAnnotation,
              "name",
              String.class
          )
      );
      discriminatorColumnValues.setLength(
          JandexHelper.getValue(
              discriminatorColumnAnnotation,
              "length",
              Integer.class
          )
      );
      discriminatorColumnValues.setColumnDefinition(
          JandexHelper.getValue(
              discriminatorColumnAnnotation,
              "columnDefinition",
              String.class
          )
      );
    }
    discriminatorType = type;

    AnnotationInstance discriminatorOptionsAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.DISCRIMINATOR_OPTIONS
    );
    if ( discriminatorOptionsAnnotation != null ) {
      isDiscriminatorForced = discriminatorOptionsAnnotation.value( "force" ).asBoolean();
      isDiscriminatorIncludedInSql = discriminatorOptionsAnnotation.value( "insert" ).asBoolean();
    }
    else {
      isDiscriminatorForced = false;
      isDiscriminatorIncludedInSql = true;
    }
  }

  private void processHibernateEntitySpecificAnnotations() {
    final AnnotationInstance hibernateEntityAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.ENTITY
    );

    // see HHH-6400
    PolymorphismType polymorphism = PolymorphismType.IMPLICIT;
    if ( hibernateEntityAnnotation != null && hibernateEntityAnnotation.value( "polymorphism" ) != null ) {
      polymorphism = PolymorphismType.valueOf( hibernateEntityAnnotation.value( "polymorphism" ).asEnum() );
    }
    isExplicitPolymorphism = polymorphism == PolymorphismType.EXPLICIT;

    // see HHH-6401
    OptimisticLockType optimisticLockType = OptimisticLockType.VERSION;
    if ( hibernateEntityAnnotation != null && hibernateEntityAnnotation.value( "optimisticLock" ) != null ) {
      optimisticLockType = OptimisticLockType.valueOf(
          hibernateEntityAnnotation.value( "optimisticLock" )
              .asEnum()
      );
    }
    optimisticLockStyle = OptimisticLockStyle.valueOf( optimisticLockType.name() );

    final AnnotationInstance hibernateImmutableAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.IMMUTABLE
    );
    isMutable = hibernateImmutableAnnotation == null
        && hibernateEntityAnnotation != null
        && hibernateEntityAnnotation.value( "mutable" ) != null
        && hibernateEntityAnnotation.value( "mutable" ).asBoolean();


    final AnnotationInstance whereAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.WHERE
    );
    whereClause = whereAnnotation != null && whereAnnotation.value( "clause" ) != null ?
        whereAnnotation.value( "clause" ).asString() : null;

    final AnnotationInstance rowIdAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.ROW_ID
    );
    rowId = rowIdAnnotation != null && rowIdAnnotation.value() != null
        ? rowIdAnnotation.value().asString() : null;

    caching = determineCachingSettings();

    // see HHH-6397
    isDynamicInsert =
        hibernateEntityAnnotation != null
            && hibernateEntityAnnotation.value( "dynamicInsert" ) != null
            && hibernateEntityAnnotation.value( "dynamicInsert" ).asBoolean();

    // see HHH-6398
    isDynamicUpdate =
        hibernateEntityAnnotation != null
            && hibernateEntityAnnotation.value( "dynamicUpdate" ) != null
            && hibernateEntityAnnotation.value( "dynamicUpdate" ).asBoolean();


    // see HHH-6399
    isSelectBeforeUpdate =
        hibernateEntityAnnotation != null
            && hibernateEntityAnnotation.value( "selectBeforeUpdate" ) != null
            && hibernateEntityAnnotation.value( "selectBeforeUpdate" ).asBoolean();

    // Custom persister
    final String entityPersisterClass;
    final AnnotationInstance persisterAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.PERSISTER
    );
    if ( persisterAnnotation == null || persisterAnnotation.value( "impl" ) == null ) {
      if ( hibernateEntityAnnotation != null && hibernateEntityAnnotation.value( "persister" ) != null ) {
        entityPersisterClass = hibernateEntityAnnotation.value( "persister" ).asString();
      }
      else {
        entityPersisterClass = null;
      }
    }
    else {
      if ( hibernateEntityAnnotation != null && hibernateEntityAnnotation.value( "persister" ) != null ) {
        // todo : error?
      }
      entityPersisterClass = persisterAnnotation.value( "impl" ).asString();
    }
    this.customPersister = entityPersisterClass;
  }

  private Caching determineCachingSettings() {
    final AnnotationInstance hibernateCacheAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.CACHE
    );
    if ( hibernateCacheAnnotation != null ) {
      final org.hibernate.cache.spi.access.AccessType accessType = hibernateCacheAnnotation.value( "usage" ) == null
          ? getLocalBindingContext().getMappingDefaults().getCacheAccessType()
          : CacheConcurrencyStrategy.parse( hibernateCacheAnnotation.value( "usage" ).asEnum() )
          .toAccessType();
      return new Caching(
          hibernateCacheAnnotation.value( "region" ) == null
              ? getName()
              : hibernateCacheAnnotation.value( "region" ).asString(),
          accessType,
          hibernateCacheAnnotation.value( "include" ) != null
              && "all".equals( hibernateCacheAnnotation.value( "include" ).asString() )
      );
    }

    final AnnotationInstance jpaCacheableAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), JPADotNames.CACHEABLE
    );

    boolean cacheable = true; // true is the default
    if ( jpaCacheableAnnotation != null && jpaCacheableAnnotation.value() != null ) {
      cacheable = jpaCacheableAnnotation.value().asBoolean();
    }

    final boolean doCaching;
    switch ( getLocalBindingContext().getMetadataImplementor().getOptions().getSharedCacheMode() ) {
      case ALL: {
        doCaching = true;
        break;
      }
      case ENABLE_SELECTIVE: {
        doCaching = cacheable;
        break;
      }
      case DISABLE_SELECTIVE: {
        doCaching = jpaCacheableAnnotation == null || cacheable;
        break;
      }
      default: {
        // treat both NONE and UNSPECIFIED the same
        doCaching = false;
        break;
      }
    }

    if ( !doCaching ) {
      return null;
    }

    return new Caching(
        getName(),
        getLocalBindingContext().getMappingDefaults().getCacheAccessType(),
        true
    );
  }

  /**
   * todo see {@code Binder#createTable}
   *
   * @param tableAnnotation a annotation instance, either {@link javax.persistence.Table} or {@link javax.persistence.SecondaryTable}
   *
   * @return A table source for the specified annotation instance
   */
  private TableSource createTableSource(AnnotationInstance tableAnnotation) {
    String schema = null;
    String catalog = null;
    if ( tableAnnotation != null ) {
      schema = JandexHelper.getValue( tableAnnotation, "schema", String.class );
      catalog = JandexHelper.getValue( tableAnnotation, "catalog", String.class );
    }
    // process the table name
    String tableName = null;
    String logicalTableName = null;

    if ( tableAnnotation != null ) {
      logicalTableName = JandexHelper.getValue( tableAnnotation, "name", String.class );
      if ( StringHelper.isNotEmpty( logicalTableName ) ) {
        tableName = logicalTableName;
      }
      createUniqueConstraints( tableAnnotation, tableName );
    }

    TableSourceImpl tableSourceImpl;
    if ( tableAnnotation == null || JPADotNames.TABLE.equals( tableAnnotation.name() ) ) {
      // for the main table @Table we use 'null' as logical name
      tableSourceImpl = new TableSourceImpl( schema, catalog, tableName, null );
    }
    else {
      // for secondary tables a name must be specified which is used as logical table name
      tableSourceImpl = new TableSourceImpl( schema, catalog, tableName, logicalTableName );
    }
    return tableSourceImpl;
  }

  private Set<TableSource> createSecondaryTableSources() {
    Set<TableSource> secondaryTableSources = new HashSet<TableSource>();
    AnnotationInstance secondaryTables = JandexHelper.getSingleAnnotation(
        getClassInfo(),
        JPADotNames.SECONDARY_TABLES
    );
    AnnotationInstance secondaryTable = JandexHelper.getSingleAnnotation(
        getClassInfo(),
        JPADotNames.SECONDARY_TABLE
    );
    // collect all @secondaryTable annotations
    List<AnnotationInstance> secondaryTableAnnotations = new ArrayList<AnnotationInstance>();
    if ( secondaryTable != null ) {
      secondaryTableAnnotations.add(
          secondaryTable
      );
    }

    if ( secondaryTables != null ) {
      secondaryTableAnnotations.addAll(
          Arrays.asList(
              JandexHelper.getValue( secondaryTables, "value", AnnotationInstance[].class )
          )
      );
    }

    // create table sources
    for ( AnnotationInstance annotationInstance : secondaryTableAnnotations ) {
      secondaryTableSources.add( createTableSource( annotationInstance ) );
    }

    return secondaryTableSources;
  }


  private void createUniqueConstraints(AnnotationInstance tableAnnotation, String tableName) {
    AnnotationValue value = tableAnnotation.value( "uniqueConstraints" );
    if ( value == null ) {
      return;
    }

    AnnotationInstance[] uniqueConstraints = value.asNestedArray();
    for ( AnnotationInstance unique : uniqueConstraints ) {
      String name = unique.value( "name" ) == null ? null : unique.value( "name" ).asString();
      String[] columnNames = unique.value( "columnNames" ).asStringArray();
      UniqueConstraintSourceImpl uniqueConstraintSource =
          new UniqueConstraintSourceImpl(
              name, tableName, Arrays.asList( columnNames )
          );
      constraintSources.add( uniqueConstraintSource );
    }
  }

  private String determineCustomLoader() {
    String customLoader = null;
    // Custom sql loader
    final AnnotationInstance sqlLoaderAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.LOADER
    );
    if ( sqlLoaderAnnotation != null ) {
      customLoader = sqlLoaderAnnotation.value( "namedQuery" ).asString();
    }
    return customLoader;
  }

  private CustomSQL createCustomSQL(AnnotationInstance customSqlAnnotation) {
    if ( customSqlAnnotation == null ) {
      return null;
    }

    final String sql = customSqlAnnotation.value( "sql" ).asString();
    final boolean isCallable = customSqlAnnotation.value( "callable" ) != null
        && customSqlAnnotation.value( "callable" ).asBoolean();

    final ExecuteUpdateResultCheckStyle checkStyle = customSqlAnnotation.value( "check" ) == null
        ? isCallable
        ? ExecuteUpdateResultCheckStyle.NONE
        : ExecuteUpdateResultCheckStyle.COUNT
        : ExecuteUpdateResultCheckStyle.valueOf( customSqlAnnotation.value( "check" ).asEnum() );

    return new CustomSQL( sql, isCallable, checkStyle );
  }

  private void processCustomSqlAnnotations() {
    // Custom sql insert
    final AnnotationInstance sqlInsertAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.SQL_INSERT
    );
    customInsert = createCustomSQL( sqlInsertAnnotation );

    // Custom sql update
    final AnnotationInstance sqlUpdateAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.SQL_UPDATE
    );
    customUpdate = createCustomSQL( sqlUpdateAnnotation );

    // Custom sql delete
    final AnnotationInstance sqlDeleteAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.SQL_DELETE
    );
    customDelete = createCustomSQL( sqlDeleteAnnotation );
  }

  private List<String> determineSynchronizedTableNames() {
    final AnnotationInstance synchronizeAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.SYNCHRONIZE
    );
    if ( synchronizeAnnotation != null ) {
      final String[] tableNames = synchronizeAnnotation.value().asStringArray();
      return Arrays.asList( tableNames );
    }
    else {
      return Collections.emptyList();
    }
  }

  private void processProxyGeneration() {
    // Proxy generation
    final AnnotationInstance hibernateProxyAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.PROXY
    );
    if ( hibernateProxyAnnotation != null ) {
      isLazy = hibernateProxyAnnotation.value( "lazy" ) == null
          || hibernateProxyAnnotation.value( "lazy" ).asBoolean();
      if ( isLazy ) {
        final AnnotationValue proxyClassValue = hibernateProxyAnnotation.value( "proxyClass" );
        if ( proxyClassValue == null ) {
          proxy = getName();
        }
        else {
          proxy = proxyClassValue.asString();
        }
      }
      else {
        proxy = null;
      }
    }
    else {
      isLazy = true;
      proxy = getName();
    }
  }

  private int determineBatchSize() {
    final AnnotationInstance batchSizeAnnotation = JandexHelper.getSingleAnnotation(
        getClassInfo(), HibernateDotNames.BATCH_SIZE
    );
    return batchSizeAnnotation == null ? -1 : batchSizeAnnotation.value( "size" ).asInt();
  }

  private List<JpaCallbackClass> determineEntityListeners() {
    List<JpaCallbackClass> callbackClassList = new ArrayList<JpaCallbackClass>();

    // Bind default JPA entity listener callbacks (unless excluded), using superclasses first (unless excluded)
    if ( JandexHelper.getSingleAnnotation( getClassInfo(), JPADotNames.EXCLUDE_DEFAULT_LISTENERS ) == null ) {
      List<AnnotationInstance> defaultEntityListenerAnnotations = getLocalBindingContext().getIndex()
          .getAnnotations( PseudoJpaDotNames.DEFAULT_ENTITY_LISTENERS );
      for ( AnnotationInstance annotation : defaultEntityListenerAnnotations ) {
        for ( Type callbackClass : annotation.value().asClassArray() ) {
          String callbackClassName = callbackClass.name().toString();
          try {
            processDefaultJpaCallbacks( callbackClassName, callbackClassList );
          }
          catch ( PersistenceException error ) {
            throw new PersistenceException( error.getMessage() + "default entity listener " + callbackClassName );
          }
        }
      }
    }

    // Bind JPA entity listener callbacks, using superclasses first (unless excluded)
    List<AnnotationInstance> annotationList = getClassInfo().annotations().get( JPADotNames.ENTITY_LISTENERS );
    if ( annotationList != null ) {
      for ( AnnotationInstance annotation : annotationList ) {
        for ( Type callbackClass : annotation.value().asClassArray() ) {
          String callbackClassName = callbackClass.name().toString();
          try {
            processJpaCallbacks( callbackClassName, true, callbackClassList );
          }
          catch ( PersistenceException error ) {
            throw new PersistenceException( error.getMessage() + "entity listener " + callbackClassName );
          }
        }
      }
    }

    // Bind JPA entity.mapped superclass callbacks, using superclasses first (unless excluded)
    try {
      processJpaCallbacks( getName(), false, callbackClassList );
    }
    catch ( PersistenceException error ) {
      throw new PersistenceException(
          error.getMessage() + "entity/mapped superclass " + getClassInfo().name().toString()
      );
    }

    return callbackClassList;
  }

  private void processDefaultJpaCallbacks(String instanceCallbackClassName, List<JpaCallbackClass> jpaCallbackClassList) {
    ClassInfo callbackClassInfo = getLocalBindingContext().getClassInfo( instanceCallbackClassName );

    // Process superclass first if available and not excluded
    if ( JandexHelper.getSingleAnnotation( callbackClassInfo, JPADotNames.EXCLUDE_SUPERCLASS_LISTENERS ) != null ) {
      DotName superName = callbackClassInfo.superName();
      if ( superName != null ) {
        processDefaultJpaCallbacks( instanceCallbackClassName, jpaCallbackClassList );
      }
    }

    String callbackClassName = callbackClassInfo.name().toString();
    Map<Class<?>, String> callbacksByType = new HashMap<Class<?>, String>();
    createDefaultCallback(
        PrePersist.class, PseudoJpaDotNames.DEFAULT_PRE_PERSIST, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PreRemove.class, PseudoJpaDotNames.DEFAULT_PRE_REMOVE, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PreUpdate.class, PseudoJpaDotNames.DEFAULT_PRE_UPDATE, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PostLoad.class, PseudoJpaDotNames.DEFAULT_POST_LOAD, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PostPersist.class, PseudoJpaDotNames.DEFAULT_POST_PERSIST, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PostRemove.class, PseudoJpaDotNames.DEFAULT_POST_REMOVE, callbackClassName, callbacksByType
    );
    createDefaultCallback(
        PostUpdate.class, PseudoJpaDotNames.DEFAULT_POST_UPDATE, callbackClassName, callbacksByType
    );
    if ( !callbacksByType.isEmpty() ) {
      jpaCallbackClassList.add( new JpaCallbackClassImpl( instanceCallbackClassName, callbacksByType, true ) );
    }
  }

  private void processJpaCallbacks(String instanceCallbackClassName, boolean isListener, List<JpaCallbackClass> callbackClassList) {

    ClassInfo callbackClassInfo = getLocalBindingContext().getClassInfo( instanceCallbackClassName );

    // Process superclass first if available and not excluded
    if ( JandexHelper.getSingleAnnotation( callbackClassInfo, JPADotNames.EXCLUDE_SUPERCLASS_LISTENERS ) != null ) {
      DotName superName = callbackClassInfo.superName();
      if ( superName != null ) {
        processJpaCallbacks(
            instanceCallbackClassName,
            isListener,
            callbackClassList
        );
      }
    }

    Map<Class<?>, String> callbacksByType = new HashMap<Class<?>, String>();
    createCallback( PrePersist.class, JPADotNames.PRE_PERSIST, callbacksByType, callbackClassInfo, isListener );
    createCallback( PreRemove.class, JPADotNames.PRE_REMOVE, callbacksByType, callbackClassInfo, isListener );
    createCallback( PreUpdate.class, JPADotNames.PRE_UPDATE, callbacksByType, callbackClassInfo, isListener );
    createCallback( PostLoad.class, JPADotNames.POST_LOAD, callbacksByType, callbackClassInfo, isListener );
    createCallback( PostPersist.class, JPADotNames.POST_PERSIST, callbacksByType, callbackClassInfo, isListener );
    createCallback( PostRemove.class, JPADotNames.POST_REMOVE, callbacksByType, callbackClassInfo, isListener );
    createCallback( PostUpdate.class, JPADotNames.POST_UPDATE, callbacksByType, callbackClassInfo, isListener );
    if ( !callbacksByType.isEmpty() ) {
      callbackClassList.add( new JpaCallbackClassImpl( instanceCallbackClassName, callbacksByType, isListener ) );
    }
  }

  private void createDefaultCallback(Class callbackTypeClass,
                     DotName callbackTypeName,
                     String callbackClassName,
                     Map<Class<?>, String> callbacksByClass) {
    for ( AnnotationInstance callback : getLocalBindingContext().getIndex().getAnnotations( callbackTypeName ) ) {
      MethodInfo methodInfo = (MethodInfo) callback.target();
      validateMethod( methodInfo, callbackTypeClass, callbacksByClass, true );
      if ( methodInfo.declaringClass().name().toString().equals( callbackClassName ) ) {
        if ( methodInfo.args().length != 1 ) {
          throw new PersistenceException(
              String.format(
                  "Callback method %s must have exactly one argument defined as either Object or %s in ",
                  methodInfo.name(),
                  getEntityName()
              )
          );
        }
        callbacksByClass.put( callbackTypeClass, methodInfo.name() );
      }
    }
  }

  private void createCallback(Class callbackTypeClass,
                DotName callbackTypeName,
                Map<Class<?>, String> callbacksByClass,
                ClassInfo callbackClassInfo,
                boolean isListener) {
    Map<DotName, List<AnnotationInstance>> annotations = callbackClassInfo.annotations();
    List<AnnotationInstance> annotationInstances = annotations.get( callbackTypeName );
    if ( annotationInstances == null ) {
      return;
    }
    for ( AnnotationInstance callbackAnnotation : annotationInstances ) {
      MethodInfo methodInfo = (MethodInfo) callbackAnnotation.target();
      validateMethod( methodInfo, callbackTypeClass, callbacksByClass, isListener );
      callbacksByClass.put( callbackTypeClass, methodInfo.name() );
    }
  }

  private void validateMethod(MethodInfo methodInfo,
                Class callbackTypeClass,
                Map<Class<?>, String> callbacksByClass,
                boolean isListener) {
    if ( methodInfo.returnType().kind() != Kind.VOID ) {
      throw new PersistenceException( "Callback method " + methodInfo.name() + " must have a void return type in " );
    }
    if ( Modifier.isStatic( methodInfo.flags() ) || Modifier.isFinal( methodInfo.flags() ) ) {
      throw new PersistenceException( "Callback method " + methodInfo.name() + " must not be static or final in " );
    }
    Type[] argTypes = methodInfo.args();
    if ( isListener ) {
      if ( argTypes.length != 1 ) {
        throw new PersistenceException( "Callback method " + methodInfo.name() + " must have exactly one argument in " );
      }
      String argTypeName = argTypes[0].name().toString();
      if ( !argTypeName.equals( Object.class.getName() ) && !argTypeName.equals( getName() ) ) {
        throw new PersistenceException(
            "The argument for callback method " + methodInfo.name() +
                " must be defined as either Object or " + getEntityName() + " in "
        );
      }
    }
    else if ( argTypes.length != 0 ) {
      throw new PersistenceException( "Callback method " + methodInfo.name() + " must have no arguments in " );
    }
    if ( callbacksByClass.containsKey( callbackTypeClass ) ) {
      throw new PersistenceException(
          "Only one method may be annotated as a " + callbackTypeClass.getSimpleName() +
              " callback method in "
      );
    }
  }

  // Process JPA callbacks, in superclass-first order (unless superclasses are excluded), using default listeners first
  // (unless default listeners are excluded), then entity listeners, and finally the entity/mapped superclass itself
  private class JpaCallbackClassImpl implements JpaCallbackClass {

    private final Map<Class<?>, String> callbacksByType;
    private final String name;
    private final boolean isListener;

    private JpaCallbackClassImpl(String name,
                   Map<Class<?>, String> callbacksByType,
                   boolean isListener) {
      this.name = name;
      this.callbacksByType = callbacksByType;
      this.isListener = isListener;
    }

    @Override
    public String getCallbackMethod(Class<?> callbackType) {
      return callbacksByType.get( callbackType );
    }

    @Override
    public String getName() {
      return name;
    }

    @Override
    public boolean isListener() {
      return isListener;
    }
  }
}
TOP

Related Classes of org.hibernate.metamodel.source.annotations.entity.EntityClass

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.