All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.ebeaninternal.server.deploy.parse.AnnotationAssocManys Maven / Gradle / Ivy

There is a newer version: 15.8.0
Show newest version
package io.ebeaninternal.server.deploy.parse;

import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.Where;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.core.type.ScalarType;
import io.ebean.util.CamelCaseHelper;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployOrderColumn;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.query.SqlJoinType;

import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.EnumType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.MapKey;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.OrderColumn;
import java.util.Set;

/**
 * Read the deployment annotation for Assoc Many beans.
 */
final class AnnotationAssocManys extends AnnotationAssoc {

  AnnotationAssocManys(DeployBeanInfo info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
    super(info, readConfig, factory);
  }

  /**
   * Parse the annotations.
   */
  @Override
  public void parse() {
    for (DeployBeanProperty prop : descriptor.propertiesAll()) {
      if (prop instanceof DeployBeanPropertyAssocMany) {
        read((DeployBeanPropertyAssocMany) prop);
      }
    }
  }

  private boolean readOrphanRemoval(OneToMany property) {
    try {
      return property.orphanRemoval();
    } catch (NoSuchMethodError e) {
      // Support old JPA API
      return false;
    }
  }

  private void read(DeployBeanPropertyAssocMany prop) {
    OneToMany oneToMany = get(prop, OneToMany.class);
    if (oneToMany != null) {
      readToOne(oneToMany, prop);
      if (readOrphanRemoval(oneToMany)) {
        prop.setOrphanRemoval();
        prop.setModifyListenMode(ModifyListenMode.REMOVALS);
        prop.getCascadeInfo().setDelete(true);
      }
      OrderColumn orderColumn = get(prop, OrderColumn.class);
      if (orderColumn != null) {
        // need to cascade as we set the order on cascade
        prop.setOrderColumn(new DeployOrderColumn(orderColumn));
        prop.setFetchOrderBy(DeployOrderColumn.LOGICAL_NAME);
        prop.getCascadeInfo().setType(CascadeType.ALL);
        prop.setModifyListenMode(ModifyListenMode.ALL);
      }
    }
    ManyToMany manyToMany = get(prop, ManyToMany.class);
    if (manyToMany != null) {
      readToMany(manyToMany, prop);
    }
    ElementCollection elementCollection = get(prop, ElementCollection.class);
    if (elementCollection != null) {
      readElementCollection(prop, elementCollection);
    }

    // for ManyToMany typically to disable foreign keys from intersection table
    DbForeignKey dbForeignKey = get(prop, DbForeignKey.class);
    if (dbForeignKey != null){
      prop.setForeignKey(new PropertyForeignKey(dbForeignKey));
    }

    if (get(prop, HistoryExclude.class) != null) {
      prop.setExcludedFromHistory();
    }
    OrderBy orderBy = get(prop, OrderBy.class);
    if (orderBy != null) {
      prop.setFetchOrderBy(orderBy.value());
    }
    MapKey mapKey = get(prop, MapKey.class);
    if (mapKey != null) {
      prop.setMapKey(mapKey.name());
    }
    Where where = prop.getMetaAnnotationWhere(platform);
    if (where != null) {
      prop.setExtraWhere(processFormula(where.clause()));
    }
    FetchPreference fetchPreference = get(prop, FetchPreference.class);
    if (fetchPreference != null) {
      prop.setFetchPreference(fetchPreference.value());
    }
    // check for manually defined joins
    BeanTable beanTable = prop.getBeanTable();

    Set joinColumns = annotationJoinColumns(prop);
    if (!joinColumns.isEmpty()) {
      prop.getTableJoin().addJoinColumn(util, true, joinColumns, beanTable);
    }

    JoinTable joinTable = get(prop, JoinTable.class);
    if (joinTable != null) {
      if (prop.isManyToMany()) {
        readJoinTable(joinTable, prop);
      } else {
        // OneToMany with @JoinTable
        prop.setO2mJoinTable();
        readJoinTable(joinTable, prop);
        manyToManyDefaultJoins(prop);
      }
    } else if (prop.isManyToMany()) {
      checkSelfManyToMany(prop);
    }
    if (prop.getMappedBy() != null) {
      // the join is derived by reversing the join information
      // from the mapped by property.
      // Refer BeanDescriptorManager.readEntityRelationships()
      return;
    }
    if (prop.isManyToMany()) {
      manyToManyDefaultJoins(prop);
      return;
    }
    if (!prop.getTableJoin().hasJoinColumns() && beanTable != null) {
      // use naming convention to define join (based on the bean name for this side of relationship)
      // A unidirectional OneToMany or OneToMany with no mappedBy property
      NamingConvention nc = factory.namingConvention();
      String fkeyPrefix = null;
      if (nc.isUseForeignKeyPrefix()) {
        fkeyPrefix = nc.getColumnFromProperty(descriptor.getBeanType(), descriptor.getName());
      }
      // Use the owning bean table to define the join
      BeanTable owningBeanTable = factory.beanTable(descriptor.getBeanType());
      owningBeanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), false, prop.getSqlFormulaSelect());
    }
  }

  private void checkSelfManyToMany(DeployBeanPropertyAssocMany prop) {
    if (prop.getTargetType().equals(descriptor.getBeanType())) {
      throw new IllegalStateException("@ManyToMany mapping for " + prop + " requires explicit @JoinTable with joinColumns & inverseJoinColumns. Refer issue #2157");
    }
  }

  @SuppressWarnings("unchecked")
  private void readElementCollection(DeployBeanPropertyAssocMany prop, ElementCollection elementCollection) {
    prop.setElementCollection();
    if (!elementCollection.targetClass().equals(void.class)) {
      prop.setTargetType(elementCollection.targetClass());
    }
    Column column = prop.getMetaAnnotation(Column.class);
    if (column != null) {
      prop.setDbColumn(column.name());
      prop.setDbLength(column.length());
      prop.setDbScale(column.scale());
    }

    CollectionTable collectionTable = get(prop, CollectionTable.class);
    String fullTableName = getFullTableName(collectionTable);
    if (fullTableName == null) {
      fullTableName = descriptor.getBaseTable()+"_"+ CamelCaseHelper.toUnderscoreFromCamel(prop.getName());
    }

    BeanTable localTable = factory.beanTable(descriptor.getBeanType());
    if (collectionTable != null) {
      prop.getTableJoin().addJoinColumn(util, true, collectionTable.joinColumns(), localTable);
    }
    if (!prop.getTableJoin().hasJoinColumns()) {
      BeanProperty localId = localTable.getIdProperty();
      if (localId != null) {
        // add foreign key based on convention
        String fkColName = namingConvention.getForeignKey(descriptor.getBaseTable(), localId.name());
        prop.getTableJoin().addJoinColumn(new DeployTableJoinColumn(localId.dbColumn(), fkColName));
      }
    }

    BeanTable beanTable = factory.createCollectionBeanTable(fullTableName, prop.getTargetType());
    prop.setBeanTable(beanTable);

    Class elementType = prop.getTargetType();

    DeployBeanDescriptor elementDescriptor = factory.createDeployDescriptor(elementType);
    elementDescriptor.setBaseTable(new TableName(fullTableName), readConfig.getAsOfViewSuffix(), readConfig.getVersionsBetweenSuffix());

    int sortOrder = 0;
    if (!prop.getManyType().isMap()) {
      elementDescriptor.setPropertyNames(new String[]{"value"});
    } else {
      elementDescriptor.setPropertyNames(new String[]{"key", "value"});
      String dbKeyColumn = "mkey";
      MapKeyColumn mapKeyColumn = get(prop, MapKeyColumn.class);
      if (mapKeyColumn != null) {
        dbKeyColumn = mapKeyColumn.name();
      }

      ScalarType keyScalarType = util.typeManager().type(prop.getMapKeyType());

      DeployBeanProperty keyProp = new DeployBeanProperty(elementDescriptor, elementType, keyScalarType, null);
      setElementProperty(keyProp, "key", dbKeyColumn, sortOrder++);
      elementDescriptor.addBeanProperty(keyProp);
      if (mapKeyColumn != null) {
        keyProp.setDbLength(mapKeyColumn.length());
        keyProp.setDbScale(mapKeyColumn.scale());
        keyProp.setUnique(mapKeyColumn.unique());
      }
    }

    ScalarType valueScalarType = util.typeManager().type(elementType);
    if (valueScalarType == null && elementType.isEnum()) {
      Class> enumClass = (Class>)elementType;
      valueScalarType = util.typeManager().enumType(enumClass, EnumType.STRING);
    }

    boolean scalar = true;
    if (valueScalarType == null) {
      // embedded value type
      scalar = false;
      DeployBeanPropertyAssocOne valueProp = new DeployBeanPropertyAssocOne<>(elementDescriptor, elementType);
      valueProp.setName("value");
      valueProp.setEmbedded();
      valueProp.setElementProperty();
      valueProp.setSortOrder(sortOrder++);
      elementDescriptor.addBeanProperty(valueProp);

    } else {
      // scalar value type
      DeployBeanProperty valueProp = new DeployBeanProperty(elementDescriptor, elementType, valueScalarType, null);
      setElementProperty(valueProp, "value", prop.getDbColumn(), sortOrder++);
      if (column != null) {
        valueProp.setDbLength(column.length());
        valueProp.setDbScale(column.scale());
      }
      Lob lob = get(prop, Lob.class);
      if (lob != null) {
        util.setLobType(valueProp);
      }
      elementDescriptor.addBeanProperty(valueProp);
    }

    elementDescriptor.setName(prop.toString());
    factory.createUnidirectional(elementDescriptor, prop.getOwningType(), beanTable, prop.getTableJoin());
    prop.setElementDescriptor(factory.createElementDescriptor(elementDescriptor, prop.getManyType(), scalar));
  }

  private void setElementProperty(DeployBeanProperty elementProp, String name, String dbColumn, int sortOrder) {
    if (dbColumn == null) {
      dbColumn = "value";
    }
    elementProp.setName(name);
    elementProp.setDbColumn(dbColumn);
    elementProp.setNullable(false);
    elementProp.setDbInsertable(true);
    elementProp.setDbUpdateable(true);
    elementProp.setDbRead(true);
    elementProp.setSortOrder(sortOrder);
    elementProp.setElementProperty();
  }

  /**
   * Define the joins for a ManyToMany relationship.
   * 

* This includes joins to the intersection table and from the intersection table * to the other side of the ManyToMany. *

*/ private void readJoinTable(JoinTable joinTable, DeployBeanPropertyAssocMany prop) { String intTableName = getFullTableName(joinTable); if (intTableName.isEmpty()) { BeanTable localTable = factory.beanTable(descriptor.getBeanType()); BeanTable otherTable = factory.beanTable(prop.getTargetType()); intTableName = getM2MJoinTableName(localTable, otherTable); } // set the intersection table DeployTableJoin intJoin = new DeployTableJoin(); intJoin.setTable(intTableName); // add the source to intersection join columns intJoin.addJoinColumn(util, true, joinTable.joinColumns(), prop.getBeanTable()); // set the intersection to dest table join columns DeployTableJoin destJoin = prop.getTableJoin(); destJoin.addJoinColumn(util, false, joinTable.inverseJoinColumns(), prop.getBeanTable()); intJoin.setType(SqlJoinType.OUTER); // reverse join from dest back to intersection DeployTableJoin inverseDest = destJoin.createInverse(intTableName); prop.setIntersectionJoin(intJoin); prop.setInverseJoin(inverseDest); } /** * Return the full table name */ private String getFullTableName(JoinTable joinTable) { return append(joinTable.catalog(), joinTable.schema(), joinTable.name()); } /** * Return the full table name */ private String getFullTableName(CollectionTable collectionTable) { if (collectionTable == null || collectionTable.name().isEmpty()) { return null; } return append(collectionTable.catalog(), collectionTable.schema(), collectionTable.name()); } /** * Return the full table name taking into account quoted identifiers. */ private String append(String catalog, String schema, String name) { return namingConvention.getTableName(catalog, schema, name); } /** * Define intersection table and foreign key columns for ManyToMany. *

* Some of these (maybe all) have been already defined via @JoinTable * and @JoinColumns etc. *

*/ private void manyToManyDefaultJoins(DeployBeanPropertyAssocMany prop) { String intTableName = null; DeployTableJoin intJoin = prop.getIntersectionJoin(); if (intJoin == null) { intJoin = new DeployTableJoin(); prop.setIntersectionJoin(intJoin); } else { // intersection table already defined (by @JoinTable) intTableName = intJoin.getTable(); } BeanTable localTable = factory.beanTable(descriptor.getBeanType()); BeanTable otherTable = factory.beanTable(prop.getTargetType()); final String localTableName = localTable.getUnqualifiedBaseTable(); final String otherTableName = otherTable.getUnqualifiedBaseTable(); if (intTableName == null) { // define intersection table name intTableName = getM2MJoinTableName(localTable, otherTable); intJoin.setTable(intTableName); intJoin.setType(SqlJoinType.OUTER); } DeployTableJoin destJoin = prop.getTableJoin(); if (intJoin.hasJoinColumns() && destJoin.hasJoinColumns()) { // already defined the foreign key columns etc return; } if (!intJoin.hasJoinColumns()) { // define foreign key columns BeanProperty localId = localTable.getIdProperty(); if (localId != null) { // add the source to intersection join columns String fkCol = namingConvention.deriveM2MColumn(localTableName, localId.dbColumn()); intJoin.addJoinColumn(new DeployTableJoinColumn(localId.dbColumn(), fkCol)); } } if (!destJoin.hasJoinColumns()) { // define inverse foreign key columns BeanProperty otherId = otherTable.getIdProperty(); if (otherId != null) { // set the intersection to dest table join columns String fkCol = namingConvention.deriveM2MColumn(otherTableName, otherId.dbColumn()); destJoin.addJoinColumn(new DeployTableJoinColumn(fkCol, otherId.dbColumn())); } } // reverse join from dest back to intersection DeployTableJoin inverseDest = destJoin.createInverse(intTableName); prop.setInverseJoin(inverseDest); } private void readToMany(ManyToMany propAnn, DeployBeanPropertyAssocMany manyProp) { manyProp.setMappedBy(propAnn.mappedBy()); manyProp.setFetchType(propAnn.fetch()); setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo()); setTargetType(propAnn.targetEntity(), manyProp); setBeanTable(manyProp); manyProp.setManyToMany(); manyProp.setModifyListenMode(ModifyListenMode.ALL); manyProp.getTableJoin().setType(SqlJoinType.OUTER); } private void readToOne(OneToMany propAnn, DeployBeanPropertyAssocMany manyProp) { manyProp.setMappedBy(propAnn.mappedBy()); manyProp.setFetchType(propAnn.fetch()); setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo()); setTargetType(propAnn.targetEntity(), manyProp); setBeanTable(manyProp); manyProp.getTableJoin().setType(SqlJoinType.OUTER); } private String getM2MJoinTableName(BeanTable lhsTable, BeanTable rhsTable) { TableName lhs = new TableName(lhsTable.getBaseTable()); TableName rhs = new TableName(rhsTable.getBaseTable()); TableName joinTable = namingConvention.getM2MJoinTableName(lhs, rhs); return joinTable.getQualifiedName(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy