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

io.ebeaninternal.server.deploy.BeanDescriptorManager Maven / Gradle / Ivy

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

import io.ebean.BackgroundExecutor;
import io.ebean.DatabaseBuilder;
import io.ebean.Model;
import io.ebean.RawSqlBuilder;
import io.ebean.annotation.ConstraintMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.config.*;
import io.ebean.config.dbplatform.*;
import io.ebean.core.type.ScalarType;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanType;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.InternalConfiguration;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.id.IdBinderEmbedded;
import io.ebeaninternal.server.deploy.id.IdBinderFactory;
import io.ebeaninternal.server.deploy.meta.*;
import io.ebeaninternal.server.deploy.parse.*;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapEntity;
import io.ebeaninternal.xmapping.api.XmapNamedQuery;
import io.ebeaninternal.xmapping.api.XmapRawSql;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import io.ebeanservice.docstore.api.DocStoreFactory;

import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static java.lang.System.Logger.Level.*;

/**
 * Creates BeanDescriptors.
 */
public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeManager {

  private static final System.Logger log = CoreLog.internal;

  private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
  public static final String JAVA_LANG_RECORD = "java.lang.Record";

  private final ReadAnnotations readAnnotations;
  private final TransientProperties transientProperties;
  private final DeployInherit deplyInherit;
  private final BeanPropertyAccess beanPropertyAccess = new EnhanceBeanPropertyAccess();
  private final DeployUtil deployUtil;
  private final PersistControllerManager persistControllerManager;
  private final PostLoadManager postLoadManager;
  private final PostConstructManager postConstructManager;
  private final BeanFinderManager beanFinderManager;
  private final PersistListenerManager persistListenerManager;
  private final BeanQueryAdapterManager beanQueryAdapterManager;
  private final NamingConvention namingConvention;
  private final DeployCreateProperties createProperties;
  private final BeanManagerFactory beanManagerFactory;
  private final DatabaseBuilder.Settings config;
  private final ChangeLogListener changeLogListener;
  private final ChangeLogRegister changeLogRegister;
  private final ChangeLogPrepare changeLogPrepare;
  private final DocStoreFactory docStoreFactory;
  private final MultiValueBind multiValueBind;
  private final TypeManager typeManager;
  private final BootupClasses bootupClasses;
  private final String serverName;
  private final List> elementDescriptors = new ArrayList<>();
  private final Map, BeanTable> beanTableMap = new HashMap<>();
  private final Map> descMap = new HashMap<>();
  private final Map> descQueueMap = new HashMap<>();
  private final Map> beanManagerMap = new HashMap<>();
  private final Map>> tableToDescMap = new HashMap<>();
  private final Map>> tableToViewDescMap = new HashMap<>();
  private final DbIdentity dbIdentity;
  private final DataSource dataSource;
  private final DatabasePlatform databasePlatform;
  private final SpiCacheManager cacheManager;
  private final BackgroundExecutor backgroundExecutor;
  private final EncryptKeyManager encryptKeyManager;
  private final IdBinderFactory idBinderFactory;
  private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;
  private final String asOfViewSuffix;
  private final boolean jacksonCorePresent;
  private final int queryPlanTTLSeconds;
  private final BindMaxLength bindMaxLength;
  private int entityBeanCount;
  private List> immutableDescriptorList;
  /**
   * Map of base tables to 'with history views' used to support 'as of' queries.
   */
  private final Map asOfTableMap = new HashMap<>();
  /**
   * Map of base tables to 'draft' tables.
   */
  private final Map draftTableMap = new HashMap<>();

  // temporary collections used during startup and then cleared
  private Map, DeployBeanInfo> deployInfoMap = new HashMap<>();
  private Set> embeddedIdTypes = new HashSet<>();
  private List> embeddedBeans = new ArrayList<>();

  /**
   * Create for a given database dbConfig.
   */
  public BeanDescriptorManager(InternalConfiguration config) {
    this.config = config.getConfig();
    this.serverName = InternString.intern(this.config.getName());
    this.cacheManager = config.getCacheManager();
    this.docStoreFactory = config.getDocStoreFactory();
    this.backgroundExecutor = config.getBackgroundExecutor();
    this.dataSource = this.config.getDataSource();
    this.encryptKeyManager = this.config.getEncryptKeyManager();
    this.databasePlatform = this.config.getDatabasePlatform();
    this.multiValueBind = config.getMultiValueBind();
    this.idBinderFactory = new IdBinderFactory(databasePlatform.idInExpandedForm(), multiValueBind);
    this.queryPlanTTLSeconds = this.config.getQueryPlanTTLSeconds();
    this.asOfViewSuffix = asOfViewSuffix(databasePlatform, this.config);
    String versionsBetweenSuffix = versionsBetweenSuffix(databasePlatform, this.config);
    this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, this.config);
    this.bootupClasses = config.getBootupClasses();
    this.createProperties = config.getDeployCreateProperties();
    this.namingConvention = this.config.getNamingConvention();
    this.dbIdentity = config.getDatabasePlatform().dbIdentity();
    this.deplyInherit = config.getDeployInherit();
    this.deployUtil = config.getDeployUtil();
    this.typeManager = deployUtil.typeManager();
    this.beanManagerFactory = new BeanManagerFactory(config.getDatabasePlatform());
    this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory(this.config);
    this.persistControllerManager = new PersistControllerManager(bootupClasses);
    this.postLoadManager = new PostLoadManager(bootupClasses);
    this.postConstructManager = new PostConstructManager(bootupClasses);
    this.persistListenerManager = new PersistListenerManager(bootupClasses);
    this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses);
    this.beanFinderManager = new BeanFinderManager(bootupClasses);
    this.transientProperties = new TransientProperties();
    this.changeLogPrepare = config.changeLogPrepare(bootupClasses.getChangeLogPrepare());
    this.changeLogListener = config.changeLogListener(bootupClasses.getChangeLogListener());
    this.changeLogRegister = config.changeLogRegister(bootupClasses.getChangeLogRegister());
    this.jacksonCorePresent = config.isJacksonCorePresent();
    this.bindMaxLength = initMaxLength();
  }

  BindMaxLength initMaxLength() {
    LengthCheck lengthCheck = this.config.getLengthCheck();
    switch (lengthCheck) {
      case OFF:
        return null;
      case UTF8:
        return BindMaxLength.ofUtf8();
      default:
        return BindMaxLength.ofStandard();
    }
  }

  @Override
  public boolean isJacksonCorePresent() {
    return jacksonCorePresent;
  }

  /**
   * Run periodic trim of query plans.
   */
  public void scheduleBackgroundTrim() {
    backgroundExecutor.scheduleWithFixedDelay(this::trimQueryPlans, 117L, 60L, TimeUnit.SECONDS);
  }

  private void trimQueryPlans() {
    long lastUsed = System.currentTimeMillis() - (queryPlanTTLSeconds * 1000L);
    for (BeanDescriptor descriptor : immutableDescriptorList) {
      if (!descriptor.isEmbedded()) {
        descriptor.trimQueryPlans(lastUsed);
      }
    }
  }

  @Override
  public ScalarType scalarType(String cast) {
    return typeManager.type(cast);
  }

  @Override
  public ScalarType scalarType(int jdbcType) {
    return typeManager.type(jdbcType);
  }

  /**
   * Return the AsOfViewSuffix based on the DbHistorySupport.
   */
  private String asOfViewSuffix(DatabasePlatform databasePlatform, DatabaseBuilder.Settings config) {
    DbHistorySupport historySupport = databasePlatform.historySupport();
    // with historySupport returns a simple view suffix or the sql2011 as of timestamp suffix
    return (historySupport == null) ? config.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(config.getAsOfViewSuffix());
  }

  /**
   * Return the versions between timestamp suffix based on the DbHistorySupport.
   */
  private String versionsBetweenSuffix(DatabasePlatform databasePlatform, DatabaseBuilder.Settings config) {
    DbHistorySupport historySupport = databasePlatform.historySupport();
    // with historySupport returns a simple view suffix or the sql2011 versions between timestamp suffix
    return (historySupport == null) ? config.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(config.getAsOfViewSuffix());
  }

  @Override
  public boolean isMultiValueSupported() {
    return multiValueBind.isSupported();
  }

  @Override
  public DatabaseBuilder.Settings config() {
    return config;
  }

  @Override
  public  DocStoreBeanAdapter createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor deploy) {
    return docStoreFactory.createAdapter(descriptor, deploy);
  }

  public BeanDescriptor descriptorByQueueId(String queueId) {
    return descQueueMap.get(queueId);
  }

  @Override
  public SpiBeanType beanType(Class entityType) {
    return descriptor(entityType);
  }

  @Override
  @SuppressWarnings("unchecked")
  public  BeanDescriptor descriptor(Class entityType) {
    return (BeanDescriptor) descMap.get(entityType.getName());
  }

  @SuppressWarnings("unchecked")
  public  BeanDescriptor descriptorByClassName(String entityClassName) {
    return (BeanDescriptor) descMap.get(entityClassName);
  }

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

  @Override
  public SpiCacheManager cacheManager() {
    return cacheManager;
  }

  @Override
  public NamingConvention namingConvention() {
    return namingConvention;
  }

  /**
   * Set the internal EbeanServer instance to all BeanDescriptors.
   */
  public void setEbeanServer(SpiEbeanServer internalEbean) {
    for (BeanDescriptor desc : immutableDescriptorList) {
      desc.setEbeanServer(internalEbean);
    }
  }

  @Override
  public IdBinder createIdBinder(BeanProperty idProperty) {
    return idBinderFactory.createIdBinder(idProperty);
  }

  /**
   * Return the map of base tables to draft tables.
   */
  public Map draftTableMap() {
    return draftTableMap;
  }

  /**
   * Deploy returning the asOfTableMap (which is required by the SQL builders).
   */
  public Map deploy(List mappings) {
    try {
      createListeners();
      readEntityDeploymentInitial();
      readXmlMapping(mappings);
      readEntityBeanTable();
      readEntityDeploymentAssociations();
      readInheritedIdGenerators();
      // creates the BeanDescriptors
      readEntityRelationships();
      List> list = new ArrayList<>(descMap.values());
      list.sort(beanDescComparator);
      immutableDescriptorList = Collections.unmodifiableList(list);
      initialiseAll();
      readForeignKeys();
      readTableToDescriptor();
      logStatus();

      // clear collections we no longer need
      embeddedIdTypes = null;
      embeddedBeans = null;
      deployInfoMap = null;
      return asOfTableMap;
    } catch (BeanNotEnhancedException e) {
      throw e;
    } catch (RuntimeException e) {
      log.log(ERROR, "Error in deployment", e);
      throw e;
    }
  }

  private void readXmlMapping(List mappings) {
    if (mappings != null) {
      ClassLoader classLoader = config.getClassLoadConfig().getClassLoader();
      for (XmapEbean mapping : mappings) {
        List entityDeploy = mapping.getEntity();
        for (XmapEntity deploy : entityDeploy) {
          readEntityMapping(classLoader, deploy);
        }
      }
    }
  }

  private void readEntityMapping(ClassLoader classLoader, XmapEntity entityDeploy) {
    String entityClassName = entityDeploy.getClazz();
    Class entityClass;
    try {
      entityClass = Class.forName(entityClassName, false, classLoader);
    } catch (Exception e) {
      log.log(ERROR, "Could not load entity bean class " + entityClassName + " for ebean.xml entry");
      return;
    }

    DeployBeanInfo info = deployInfoMap.get(entityClass);
    if (info == null) {
      log.log(ERROR, "No entity bean for ebean.xml entry " + entityClassName);

    } else {
      for (XmapRawSql sql : entityDeploy.getRawSql()) {
        RawSqlBuilder builder;
        try {
          builder = RawSqlBuilder.parse(sql.getQuery());
        } catch (RuntimeException e) {
          builder = RawSqlBuilder.unparsed(sql.getQuery());
        }

        for (Map.Entry columnMapping : sql.getColumnMapping().entrySet()) {
          builder.columnMapping(columnMapping.getKey(), columnMapping.getValue());
        }
        for (Map.Entry aliasMapping : sql.getAliasMapping().entrySet()) {
          builder.tableAliasMapping(aliasMapping.getKey(), aliasMapping.getValue());
        }
        info.addRawSql(sql.getName(), builder.create());
      }

      for (XmapNamedQuery namedQuery : entityDeploy.getNamedQuery()) {
        info.addNamedQuery(namedQuery.getName(), namedQuery.getQuery());
      }
    }
  }

  /**
   * Return the Encrypt key given the table and column name.
   */
  @Override
  public EncryptKey encryptKey(String tableName, String columnName) {
    return encryptKeyManager.getEncryptKey(tableName, columnName);
  }

  /**
   * For SQL based modifications we need to invalidate appropriate parts of the cache.
   */
  public void cacheNotify(TransactionEventTable.TableIUD tableIUD, CacheChangeSet changeSet) {
    String tableName = tableIUD.tableName().toLowerCase();
    List> normalBeanTypes = tableToDescMap.get(tableName);
    if (normalBeanTypes != null) {
      // 'normal' entity beans based on a "base table"
      for (BeanDescriptor normalBeanType : normalBeanTypes) {
        normalBeanType.cachePersistTableIUD(tableIUD, changeSet);
      }
    }
    List> viewBeans = tableToViewDescMap.get(tableName);
    if (viewBeans != null) {
      // entity beans based on a "view"
      for (BeanDescriptor viewBean : viewBeans) {
        viewBean.cachePersistTableIUD(tableIUD, changeSet);
      }
    }
  }

  /**
   * Return the BeanDescriptors mapped to the table.
   */
  public List> descriptors(String tableName) {
    return tableName == null ? Collections.emptyList() : tableToDescMap.get(tableName.toLowerCase());
  }

  /**
   * Return the BeanDescriptors mapped to the table.
   */
  public List> beanTypes(String tableName) {
    return tableToDescMap.get(tableName.toLowerCase());
  }

  @Override
  public boolean isTableManaged(String tableName) {
    return tableToDescMap.get(tableName.toLowerCase()) != null
      || tableToViewDescMap.get(tableName.toLowerCase()) != null;
  }

  /**
   * Invalidate entity beans based on views via their dependent tables.
   */
  public void processViewInvalidation(Set viewInvalidation) {
    for (String depTable : viewInvalidation) {
      List> list = tableToViewDescMap.get(depTable.toLowerCase());
      if (list != null) {
        for (BeanDescriptor desc : list) {
          desc.clearQueryCache();
        }
      }
    }
  }

  /**
   * Build a map of table names to BeanDescriptors.
   */
  private void readTableToDescriptor() {
    for (BeanDescriptor desc : descMap.values()) {
      String baseTable = desc.baseTable();
      if (baseTable != null) {
        baseTable = baseTable.toLowerCase();
        List> list = tableToDescMap.computeIfAbsent(baseTable, k -> new ArrayList<>(1));
        list.add(desc);
      }
      if (desc.entityType() == EntityType.VIEW && desc.isQueryCaching()) {
        // build map of tables to view entities dependent on those tables
        // for the purpose of invalidating appropriate query caches
        String[] dependentTables = desc.dependentTables();
        if (dependentTables != null) {
          for (String depTable : dependentTables) {
            depTable = depTable.toLowerCase();
            tableToViewDescMap.computeIfAbsent(depTable, k -> new ArrayList<>(1)).add(desc);
          }
        }
      }
    }
  }

  private void readForeignKeys() {
    for (BeanDescriptor d : descMap.values()) {
      d.initialiseFkeys();
    }
  }

  /**
   * Initialise all the BeanDescriptors.
   * 

* This occurs after all the BeanDescriptors have been created. This resolves * circular relationships between BeanDescriptors. *

* Also responsible for creating all the BeanManagers which contain the * persister, listener etc. */ private void initialiseAll() { // now that all the BeanDescriptors are in their map // we can initialise them which sorts out circular // dependencies for OneToMany and ManyToOne etc BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, draftTableMap, asOfViewSuffix); // PASS 1: // initialise the ID properties of all the beans // first (as they are needed to initialise the // associated properties in the second pass). for (BeanDescriptor d : descMap.values()) { d.initialiseId(initContext); } // PASS 2: // now initialise all the inherit info for (BeanDescriptor d : descMap.values()) { d.initInheritInfo(); } // PASS 3: // now initialise all the associated properties for (BeanDescriptor d : descMap.values()) { // also look for intersection tables with // associated history support and register them // into the asOfTableMap d.initialiseOther(initContext); } // PASS 4: // now initialise document mapping which needs target descriptors for (BeanDescriptor d : descMap.values()) { d.initialiseDocMapping(); } // create BeanManager for each non-embedded entity bean for (BeanDescriptor d : descMap.values()) { d.initLast(); if (!d.isEmbedded()) { beanManagerMap.put(d.fullName(), beanManagerFactory.create(d)); checkForValidEmbeddedId(d); } } } private void checkForValidEmbeddedId(BeanDescriptor d) { IdBinder idBinder = d.idBinder(); if (idBinder instanceof IdBinderEmbedded) { IdBinderEmbedded embId = (IdBinderEmbedded) idBinder; BeanDescriptor idBeanDescriptor = embId.descriptor(); Class idType = idBeanDescriptor.type(); try { idType.getDeclaredMethod("hashCode"); idType.getDeclaredMethod("equals", Object.class); } catch (NoSuchMethodException e) { checkMissingHashCodeOrEquals(e, idType, d.type()); } } } private void checkMissingHashCodeOrEquals(Exception source, Class idType, Class beanType) { String msg = "SERIOUS ERROR: The hashCode() and equals() methods *MUST* be implemented "; msg += "on Embedded bean " + idType + " as it is used as an Id for " + beanType; throw new PersistenceException(msg, source); } /** * Return true if there are 'view based entities' using l2 query caching and so need * to be invalidated based on changes to dependent tables. */ public boolean requiresViewEntityCacheInvalidation() { return !tableToViewDescMap.isEmpty(); } /** * Return an immutable list of all the BeanDescriptors. */ public List> descriptorList() { return immutableDescriptorList; } public BeanTable beanTable(Class type) { return beanTableMap.get(type); } /** * Return a BeanTable for an ElementCollection. */ public BeanTable createCollectionBeanTable(String fullTableName, Class targetType) { return new BeanTable(this, fullTableName, targetType); } @SuppressWarnings("unchecked") public BeanManager beanManager(Class entityType) { BeanManager mgr = (BeanManager) beanManager(entityType.getName()); if (mgr == null) { errorBeanNotRegistered(entityType); } return mgr; } private void errorBeanNotRegistered(Class entityType) { if (beanManagerMap.isEmpty()) { throw new PersistenceException(errNothingRegistered()); } else { throw new PersistenceException(errNotRegistered(entityType)); } } private String errNothingRegistered() { return "There are no registered entities. If using query beans, that generates EbeanEntityRegister.java into " + "generated sources and is service loaded. If using module-info.java, then probably missing 'provides io.ebean.config.EntityClassRegister with EbeanEntityRegister' clause."; } private String errNotRegistered(Class beanClass) { return "The type [" + beanClass + "] is not a registered entity?" + " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; } private BeanManager beanManager(String beanClassName) { return beanManagerMap.get(beanClassName); } /** * Create the BeanControllers, BeanFinders and BeanListeners. */ private void createListeners() { int qa = beanQueryAdapterManager.getRegisterCount(); int cc = persistControllerManager.getRegisterCount(); int pl = postLoadManager.getRegisterCount(); int pc = postConstructManager.getRegisterCount(); int lc = persistListenerManager.getRegisterCount(); int fc = beanFinderManager.getRegisterCount(); log.log(DEBUG, "BeanPersistControllers[{0}] BeanFinders[{1}] BeanPersistListeners[{2}] BeanQueryAdapters[{3}] BeanPostLoaders[{4}] BeanPostConstructors[{5}]", cc, fc, lc, qa, pl, pc); } private void logStatus() { log.log(DEBUG, "Entities[{0}]", entityBeanCount); } /** * Return the bean deploy info for the given class. */ @SuppressWarnings("unchecked") public DeployBeanInfo deploy(Class cls) { return (DeployBeanInfo) deployInfoMap.get(cls); } private void registerDescriptor(DeployBeanInfo info) { BeanDescriptor desc = new BeanDescriptor<>(this, info.getDescriptor()); descMap.put(desc.type().getName(), desc); if (desc.isDocStoreMapped()) { descQueueMap.put(desc.docStoreQueueId(), desc); } for (BeanPropertyAssocMany many : desc.propertiesMany()) { if (many.isElementCollection()) { elementDescriptors.add(many.elementDescriptor()); } } } /** * Read the initial deployment information for the entities. *

* This stops short of reading relationship meta data until after the * BeanTables have all been created. */ private void readEntityDeploymentInitial() { for (Class entityClass : bootupClasses.getEntities()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); deployInfoMap.put(entityClass, info); Class embeddedIdType = info.getEmbeddedIdType(); if (embeddedIdType != null) { embeddedIdTypes.add(embeddedIdType); } } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); deployInfoMap.put(entityClass, info); if (embeddedIdTypes.contains(entityClass)) { // register embeddedId types early - scalar properties only // and needed for creating BeanTables (id properties) registerEmbeddedBean(info); } else { // delay register of other embedded beans until after // the BeanTables have been created to support ManyToOne embeddedBeans.add(info); } } } private void registerEmbeddedBean(DeployBeanInfo info) { readDeployAssociations(info); registerDescriptor(info); } /** * Create the BeanTable information which has the base table and id. *

* This is determined prior to resolving relationship information. */ private void readEntityBeanTable() { for (DeployBeanInfo info : deployInfoMap.values()) { BeanTable beanTable = createBeanTable(info); beanTableMap.put(beanTable.getBeanType(), beanTable); } // register non-id embedded beans (after bean tables are created) for (DeployBeanInfo info : embeddedBeans) { registerEmbeddedBean(info); } } /** * Create the BeanTable information which has the base table and id. *

* This is determined prior to resolving relationship information. */ private void readEntityDeploymentAssociations() { for (DeployBeanInfo info : deployInfoMap.values()) { readDeployAssociations(info); } } private void readInheritedIdGenerators() { for (DeployBeanInfo info : deployInfoMap.values()) { DeployBeanDescriptor descriptor = info.getDescriptor(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null && !inheritInfo.isRoot()) { DeployBeanInfo rootBeanInfo = deployInfoMap.get(inheritInfo.getRoot().getType()); PlatformIdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator(); if (rootIdGen != null) { descriptor.setIdGenerator(rootIdGen); } } } } /** * Create the BeanTable from the deployment information gathered so far. */ private BeanTable createBeanTable(DeployBeanInfo info) { DeployBeanDescriptor deployDescriptor = info.getDescriptor(); DeployBeanTable beanTable = deployDescriptor.createDeployBeanTable(); return new BeanTable(beanTable, this); } private void readEntityRelationships() { // We only perform 'circular' checks etc after we have // all the DeployBeanDescriptors created and in the map. List> primaryKeyJoinCheck = new ArrayList<>(); for (DeployBeanInfo info : deployInfoMap.values()) { checkMappedBy(info, primaryKeyJoinCheck); } for (DeployBeanPropertyAssocOne prop : primaryKeyJoinCheck) { checkUniDirectionalPrimaryKeyJoin(prop); } for (DeployBeanInfo info : deployInfoMap.values()) { secondaryPropsJoins(info); } for (DeployBeanInfo info : deployInfoMap.values()) { setInheritanceInfo(info); } for (DeployBeanInfo info : deployInfoMap.values()) { if (!info.isEmbedded()) { registerDescriptor(info); } } } /** * Sets the inheritance info. */ private void setInheritanceInfo(DeployBeanInfo info) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { DeployBeanInfo assoc = deployInfoMap.get(oneProp.getTargetType()); if (assoc != null) { oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } } } for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { DeployBeanInfo assoc = deployInfoMap.get(manyProp.getTargetType()); if (assoc != null) { manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } } } } private void secondaryPropsJoins(DeployBeanInfo info) { DeployBeanDescriptor descriptor = info.getDescriptor(); for (DeployBeanProperty prop : descriptor.propertiesBase()) { if (prop.isSecondaryTable()) { String tableName = prop.getSecondaryTable(); // find a join to that table... DeployBeanPropertyAssocOne assocOne = descriptor.findJoinToTable(tableName); if (assocOne == null) { String msg = "Error with property " + prop+ ". Could not find a Relationship to table " + tableName + ". Perhaps you could use a @JoinColumn instead."; throw new RuntimeException(msg); } DeployTableJoin tableJoin = assocOne.getTableJoin(); prop.setSecondaryTableJoin(tableJoin, assocOne.getName()); } } } /** * Check the mappedBy attributes for properties on this descriptor. *

* This will read join information defined on the 'owning/other' side of the * relationship. It also does some extra work for unidirectional * relationships. */ private void checkMappedBy(DeployBeanInfo info, List> primaryKeyJoinCheck) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { if (oneProp.getMappedBy() != null) { checkMappedByOneToOne(oneProp); } else if (oneProp.isPrimaryKeyJoin()) { primaryKeyJoinCheck.add(oneProp); } } } for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { if (manyProp.isManyToMany()) { checkMappedByManyToMany(manyProp); } else { checkMappedByOneToMany(info, manyProp); } } } } private DeployBeanDescriptor targetDescriptor(DeployBeanPropertyAssoc prop) { Class targetType = prop.getTargetType(); DeployBeanInfo info = deployInfoMap.get(targetType); if (info == null) { throw new PersistenceException("Can not find descriptor [" + targetType + "] for " + prop); } return info.getDescriptor(); } /** * Check that the many property has either an implied mappedBy property or * mark it as unidirectional. */ private boolean findMappedBy(DeployBeanPropertyAssocMany prop) { // this is the entity bean type - that owns this property Class owningType = prop.getOwningType(); Set matchSet = new HashSet<>(); // get the bean descriptor that holds the mappedBy property DeployBeanDescriptor targetDesc = targetDescriptor(prop); List> ones = targetDesc.propertiesAssocOne(); for (DeployBeanPropertyAssocOne possibleMappedBy : ones) { Class possibleMappedByType = possibleMappedBy.getTargetType(); if (possibleMappedByType.equals(owningType)) { prop.setMappedBy(possibleMappedBy.getName()); matchSet.add(possibleMappedBy.getName()); } } if (matchSet.isEmpty()) { // this is a unidirectional relationship // ... that is no matching property on the 'detail' bean return false; } if (matchSet.size() == 1) { // all right with the world prop.clearTableJoin(); return true; } if (matchSet.size() == 2) { // try to find a match implicitly using a common naming convention // e.g. List loggedBugs; ... search for "logged" in matchSet String name = prop.getName(); // get the target type short name String targetType = prop.getTargetType().getName(); String shortTypeName = targetType.substring(targetType.lastIndexOf('.') + 1); // name includes (probably ends with) the target type short name? int p = name.indexOf(shortTypeName); if (p > 1) { // ok, get the 'interesting' part of the property name // That is the name without the target type String searchName = name.substring(0, p).toLowerCase(); // search for this in the possible matches for (String possibleMappedBy : matchSet) { String possibleLower = possibleMappedBy.toLowerCase(); if (possibleLower.contains(searchName)) { // we have a match prop.setMappedBy(possibleMappedBy); return true; } } } } // multiple options so should specify mappedBy property String msg = "Error on " + prop + " missing mappedBy."; msg += " There are [" + matchSet.size() + "] possible properties in " + targetDesc; msg += " that this association could be mapped to. Please specify one using "; msg += "the mappedBy attribute on @OneToMany."; throw new PersistenceException(msg); } private void makeOrderColumn(DeployBeanPropertyAssocMany oneToMany) { DeployBeanDescriptor targetDesc = targetDescriptor(oneToMany); DeployOrderColumn orderColumn = oneToMany.getOrderColumn(); final ScalarType scalarType = typeManager.type(Integer.class); DeployBeanProperty orderProperty = new DeployBeanProperty(targetDesc, Integer.class, scalarType, null); orderProperty.setName(DeployOrderColumn.LOGICAL_NAME); orderProperty.setDbColumn(orderColumn.getName()); orderProperty.setNullable(orderColumn.isNullable()); orderProperty.setDbInsertable(orderColumn.isInsertable()); orderProperty.setDbUpdateable(orderColumn.isUpdatable()); orderProperty.setDbRead(true); orderProperty.setOwningType(targetDesc.getBeanType()); final InheritInfo targetInheritInfo = targetDesc.getInheritInfo(); if (targetInheritInfo != null) { for (InheritInfo child : targetInheritInfo.getChildren()) { final DeployBeanDescriptor childDescriptor = deployInfoMap.get(child.getType()).getDescriptor(); childDescriptor.setOrderColumn(orderProperty); } } targetDesc.setOrderColumn(orderProperty); } /** * A OneToMany with no matching mappedBy property in the target so must be * unidirectional. *

* This means that inserts MUST cascade for this property. *

* Create a "Shadow"/Unidirectional property on the target. It is used with * inserts to set the foreign key value (e.g. inserts the foreign key value * into the order_id column on the order_lines table). */ private void makeUnidirectional(DeployBeanPropertyAssocMany oneToMany) { DeployBeanDescriptor targetDesc = targetDescriptor(oneToMany); Class owningType = oneToMany.getOwningType(); if (!oneToMany.getCascadeInfo().isSave()) { // The property MUST have persist cascading so that inserts work. Class targetType = oneToMany.getTargetType(); String msg = "Error on " + oneToMany + ". @OneToMany MUST have "; msg += "Cascade.PERSIST or Cascade.ALL because this is a unidirectional "; msg += "relationship. That is, there is no property of type " + owningType + " on " + targetType; throw new PersistenceException(msg); } // mark this property as unidirectional oneToMany.setUnidirectional(); // specify table and table alias... BeanTable beanTable = beanTable(owningType); // define the TableJoin DeployTableJoin oneToManyJoin = oneToMany.getTableJoin(); if (!oneToManyJoin.hasJoinColumns()) { throw new RuntimeException("No join columns found to satisfy the relationship " + oneToMany); } createUnidirectional(targetDesc, owningType, beanTable, oneToManyJoin); } /** * Create and add a Unidirectional property (for ElementCollection) which maps to the foreign key. */ public void createUnidirectional(DeployBeanDescriptor targetDesc, Class targetType, BeanTable beanTable, DeployTableJoin oneToManyJoin) { // create the 'shadow' unidirectional property // which is put on the target descriptor DeployBeanPropertyAssocOne unidirectional = new DeployBeanPropertyAssocOne<>(targetDesc, targetType); unidirectional.setUndirectionalShadow(); unidirectional.setNullable(false); unidirectional.setDbRead(true); unidirectional.setDbInsertable(true); unidirectional.setDbUpdateable(false); unidirectional.setBeanTable(beanTable); unidirectional.setName(beanTable.getBaseTable()); unidirectional.setJoinType(true); unidirectional.setJoinColumns(oneToManyJoin.columns(), true); targetDesc.setUnidirectional(unidirectional); } private void checkMappedByOneToOne(DeployBeanPropertyAssocOne prop) { // check that the mappedBy property is valid and read // its associated join information if it is available String mappedBy = prop.getMappedBy(); // get the mappedBy property DeployBeanDescriptor targetDesc = targetDescriptor(prop); DeployBeanPropertyAssocOne mappedAssocOne = mappedOneToOne(prop, mappedBy, targetDesc); DeployTableJoin tableJoin = prop.getTableJoin(); if (!tableJoin.hasJoinColumns()) { // define Join as the inverse of the mappedBy property DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin(); otherTableJoin.copyWithoutType(tableJoin, true, tableJoin.getTable()); } if (mappedAssocOne.isPrimaryKeyJoin()) { // bi-directional PrimaryKeyJoin ... mappedAssocOne.setPrimaryKeyJoin(false); prop.setPrimaryKeyExport(); addPrimaryKeyJoin(prop); } } private DeployBeanPropertyAssocOne mappedOneToOne(DeployBeanPropertyAssocOne prop, String mappedBy, DeployBeanDescriptor targetDesc) { DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { throw new PersistenceException("Error on " + prop + " Can not find mappedBy property " + targetDesc + "." + mappedBy); } if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { throw new PersistenceException("Error on " + prop + ". mappedBy property " + targetDesc + "." + mappedBy + " is not a OneToOne?"); } DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; if (!mappedAssocOne.isOneToOne()) { throw new PersistenceException("Error on " + prop + ". mappedBy property " + targetDesc + "." + mappedBy + " is not a OneToOne?"); } return mappedAssocOne; } private void checkUniDirectionalPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { if (prop.isPrimaryKeyJoin()) { // uni-directional PrimaryKeyJoin ... prop.setPrimaryKeyExport(); addPrimaryKeyJoin(prop); } } /** * If the property has mappedBy set then do two things. Make sure the mappedBy * property exists, and secondly read its join information. *

* We can use the join information from the mappedBy property and reverse it * for using in the OneToMany direction. */ private void checkMappedByOneToMany(DeployBeanInfo info, DeployBeanPropertyAssocMany prop) { if (prop.isElementCollection()) { // skip mapping check return; } DeployBeanDescriptor targetDesc = targetDescriptor(prop); if (targetDesc.isDraftableElement()) { // automatically turning on orphan removal and CascadeType.ALL prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS); prop.getCascadeInfo().setSaveDelete(true, true); } if (prop.hasOrderColumn()) { makeOrderColumn(prop); } if (prop.getMappedBy() == null) { // if we are doc store only we are done // this allows the use of @OneToMany in @DocStore - Entities if (info.getDescriptor().isDocStoreOnly()) { prop.setUnidirectional(); return; } if (!findMappedBy(prop)) { if (!prop.isO2mJoinTable()) { makeUnidirectional(prop); } return; } } // check that the mappedBy property is valid and read // its associated join information if it is available String mappedBy = prop.getMappedBy(); // get the mappedBy property DeployBeanPropertyAssocOne mappedAssocOne = mappedManyToOne(prop, targetDesc, mappedBy); DeployTableJoin tableJoin = prop.getTableJoin(); if (!tableJoin.hasJoinColumns()) { // define Join as the inverse of the mappedBy property DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin(); otherTableJoin.copyTo(tableJoin, true, tableJoin.getTable()); } PropertyForeignKey foreignKey = mappedAssocOne.getForeignKey(); if (foreignKey != null) { ConstraintMode onDelete = foreignKey.getOnDelete(); switch (onDelete) { case SET_DEFAULT: case SET_NULL: case CASCADE: { // turn off cascade delete when we are using the foreign // key constraint to cascade the delete or set null prop.getCascadeInfo().setDelete(false); } } } } private DeployBeanPropertyAssocOne mappedManyToOne(DeployBeanPropertyAssocMany prop, DeployBeanDescriptor targetDesc, String mappedBy) { DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { throw new PersistenceException("Error on " + prop + " Can not find mappedBy property " + mappedBy + " in " + targetDesc); } if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { throw new PersistenceException("Error on " + prop + ". mappedBy property " + mappedBy + " is not a ManyToOne? in " + targetDesc); } return (DeployBeanPropertyAssocOne) mappedProp; } /** * For mappedBy copy the joins from the other side. */ private void checkMappedByManyToMany(DeployBeanPropertyAssocMany prop) { // get the bean descriptor that holds the mappedBy property String mappedBy = prop.getMappedBy(); if (mappedBy == null) { if (targetDescriptor(prop).isDraftable()) { prop.setIntersectionDraftTable(); } return; } // get the mappedBy property DeployBeanDescriptor targetDesc = targetDescriptor(prop); DeployBeanPropertyAssocMany mappedAssocMany = mappedManyToMany(prop, mappedBy, targetDesc); // define the relationships/joins on this side as the // reverse of the other mappedBy side ... DeployTableJoin mappedIntJoin = mappedAssocMany.getIntersectionJoin(); DeployTableJoin mappendInverseJoin = mappedAssocMany.getInverseJoin(); String intTableName = mappedIntJoin.getTable(); DeployTableJoin tableJoin = prop.getTableJoin(); mappedIntJoin.copyTo(tableJoin, true, targetDesc.getBaseTable()); DeployTableJoin intJoin = new DeployTableJoin(); mappendInverseJoin.copyTo(intJoin, false, intTableName); prop.setIntersectionJoin(intJoin); DeployTableJoin inverseJoin = new DeployTableJoin(); mappedIntJoin.copyTo(inverseJoin, false, intTableName); prop.setInverseJoin(inverseJoin); if (targetDesc.isDraftable()) { prop.setIntersectionDraftTable(); } } private DeployBeanPropertyAssocMany mappedManyToMany(DeployBeanPropertyAssocMany prop, String mappedBy, DeployBeanDescriptor targetDesc) { DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { throw new PersistenceException("Error on " + prop + " Can not find mappedBy property " + mappedBy + " in " + targetDesc); } if (!(mappedProp instanceof DeployBeanPropertyAssocMany)) { throw new PersistenceException("Error on " + prop + ". mappedBy property " + targetDesc + "." + mappedBy + " is not a ManyToMany?"); } DeployBeanPropertyAssocMany mappedAssocMany = (DeployBeanPropertyAssocMany) mappedProp; if (!mappedAssocMany.isManyToMany()) { throw new PersistenceException("Error on " + prop + ". mappedBy property " + targetDesc + "." + mappedBy + " is not a ManyToMany?"); } return mappedAssocMany; } private void setBeanControllerFinderListener(DeployBeanDescriptor descriptor) { persistControllerManager.addPersistControllers(descriptor); postLoadManager.addPostLoad(descriptor); postConstructManager.addPostConstructListeners(descriptor); persistListenerManager.addPersistListeners(descriptor); beanQueryAdapterManager.addQueryAdapter(descriptor); beanFinderManager.addFindControllers(descriptor); if (changeLogRegister != null) { ChangeLogFilter changeFilter = changeLogRegister.getChangeFilter(descriptor.getBeanType()); if (changeFilter != null) { descriptor.setChangeLogFilter(changeFilter); } } } /** * Read the initial deployment information for a given bean type. */ private DeployBeanInfo createDeployBeanInfo(Class beanClass) { DeployBeanDescriptor desc = new DeployBeanDescriptor<>(this, beanClass, config); beanLifecycleAdapterFactory.addLifecycleMethods(desc); // set bean controller, finder and listener setBeanControllerFinderListener(desc); deplyInherit.process(desc); desc.checkInheritanceMapping(); createProperties.createProperties(desc); DeployBeanInfo info = new DeployBeanInfo<>(deployUtil, desc); readAnnotations.readInitial(info); return info; } private void readDeployAssociations(DeployBeanInfo info) { DeployBeanDescriptor desc = info.getDescriptor(); readAnnotations.readAssociations(info, this); if (EntityType.SQL == desc.getEntityType()) { desc.setBaseTable(null, null, null); } // mark transient properties transientProperties.process(desc); setScalarType(desc); if (!desc.isEmbedded()) { // Set IdGenerator or use DB Identity setIdGeneration(desc); // find the appropriate default concurrency mode setConcurrencyMode(desc); } // generate the byte code setAccessors(desc); } /** * Set the Identity generation mechanism. */ private void setIdGeneration(DeployBeanDescriptor desc) { if (desc.getIdGenerator() != null) { // already assigned (So custom or UUID) return; } if (desc.idProperty() == null) { return; } final DeployIdentityMode identityMode = desc.getIdentityMode(); if (identityMode.isSequence() && !dbIdentity.isSupportsSequence()) { // explicit sequence but not supported by the DatabasePlatform log.log(INFO, "Explicit sequence on {0} but not supported by DB Platform - ignored", desc.getFullName()); identityMode.setIdType(IdType.AUTO); } if (identityMode.isIdentity() && !dbIdentity.isSupportsIdentity()) { // explicit identity but not supported by the DatabasePlatform log.log(INFO, "Explicit Identity on {0} but not supported by DB Platform - ignored", desc.getFullName()); identityMode.setIdType(IdType.AUTO); } if (identityMode.isAuto()) { if (desc.isPrimaryKeyCompoundOrNonNumeric()) { identityMode.setIdType(IdType.EXTERNAL); return; } if (desc.isIdGeneratedValue() || config.isIdGeneratorAutomatic()) { // use IDENTITY or SEQUENCE based on platform identityMode.setPlatformType(dbIdentity.getIdType()); } else { // externally/application supplied Id values identityMode.setIdType(IdType.EXTERNAL); return; } } if (desc.getBaseTable() == null) { // no base table so not going to set Identity or sequence information return; } if (identityMode.isIdentity()) { // used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana) String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable()); String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable()); desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft); return; } if (identityMode.isSequence()) { String seqName = identityMode.getSequenceName(); if (seqName == null || seqName.isEmpty()) { String primaryKeyColumn = desc.getSinglePrimaryKeyColumn(); seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn); } int stepSize = desc.setIdentitySequenceBatchMode(databasePlatform.sequenceBatchMode()); desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize)); } } private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) { return databasePlatform.createSequenceIdGenerator(backgroundExecutor, dataSource, stepSize, seqName); } private void setAccessors(DeployBeanDescriptor deploy) { // check to see if the bean supports EntityBean interface // generate a subclass if required confirmEnhanced(deploy); // use Code generation or Standard reflection to support // getter and setter methods setPropertyAccessors(deploy); } /** * Set the Scalar Types on all the simple types. This is done AFTER transients * have been identified. This is because a non-transient field MUST have a * ScalarType. It is useful for transients to have ScalarTypes because then * they can be used in a SqlSelect query. *

* Enums are treated a bit differently in that they always have a ScalarType * as one is built for them. */ private void setScalarType(DeployBeanDescriptor deployDesc) { for (DeployBeanProperty prop : deployDesc.propertiesAll()) { if (!(prop instanceof DeployBeanPropertyAssoc)) { deployUtil.setScalarType(prop); } } } /** * Set BeanReflect BeanReflectGetter and BeanReflectSetter properties. *

* This sets the implementation of constructing entity beans and the setting * and getting of properties. It is generally faster to use code generation * rather than reflection to do this. */ private void setPropertyAccessors(DeployBeanDescriptor desc) { // Set the BeanReflectGetter and BeanReflectSetter that typically // use generated code. NB: Due to Bug 166 so now doing this for // abstract classes as well. BeanPropertiesReader reflectProps = new BeanPropertiesReader(desc.propertyNames()); for (DeployBeanProperty prop : desc.propertiesAll()) { String propName = prop.getName(); Integer pos = reflectProps.propertyIndex(propName); if (pos == null) { if (isPersistentField(prop)) { throw new IllegalStateException( "If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " + "Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType()); } } else { final int propertyIndex = pos; prop.setPropertyIndex(propertyIndex); prop.setGetter(beanPropertyAccess.getGetter(propertyIndex)); prop.setSetter(beanPropertyAccess.getSetter(propertyIndex)); if (prop.isAggregation()) { prop.setAggregationPrefix(DetermineAggPath.manyPath(prop.getRawAggregation(), desc)); } } } } /** * Return true if this is a persistent field (not transient or static). */ private boolean isPersistentField(DeployBeanProperty prop) { Field field = prop.getField(); if (field == null) { return false; } int modifiers = field.getModifiers(); return !(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) && !field.isAnnotationPresent(Transient.class); } /** * DevNote: It is assumed that Embedded can contain version properties. It is * also assumed that Embedded beans do NOT themselves contain Embedded beans * which contain version properties. */ private void setConcurrencyMode(DeployBeanDescriptor desc) { if (desc.getConcurrencyMode() != null) { // concurrency mode explicitly set during deployment return; } if (checkForVersionProperties(desc)) { desc.setConcurrencyMode(ConcurrencyMode.VERSION); } else { desc.setConcurrencyMode(ConcurrencyMode.NONE); } } /** * Search for version properties also including embedded beans. */ private boolean checkForVersionProperties(DeployBeanDescriptor desc) { for (DeployBeanProperty prop : desc.propertiesBase()) { if (prop.isVersionColumn()) { return true; } } return false; } private boolean hasEntityBeanInterface(Class beanClass) { for (Class anInterface : beanClass.getInterfaces()) { if (anInterface.equals(EntityBean.class)) { return true; } } return false; } /** * Test the bean type to see if it implements EntityBean interface already. */ private void confirmEnhanced(DeployBeanDescriptor desc) { Class beanClass = desc.getBeanType(); if (!hasEntityBeanInterface(beanClass)) { String msg = "Bean " + beanClass + " is not enhanced? Check packages specified in ebean.mf. If you are running in IDEA or " + "Eclipse check that the enhancement plugin is installed. See https://ebean.io/docs/trouble-shooting#not-enhanced"; throw new BeanNotEnhancedException(msg); } // the bean already implements EntityBean checkInheritedClasses(beanClass); entityBeanCount++; } /** * Check that the inherited classes are the same as the entity bean (aka all * enhanced or all dynamically subclassed). */ private void checkInheritedClasses(Class beanClass) { Class superclass = beanClass.getSuperclass(); if (Object.class.equals(superclass) || Model.class.equals(superclass) || JAVA_LANG_RECORD.equals(superclass.getName())) { // we got to the top of the inheritance return; } if (!EntityBean.class.isAssignableFrom(superclass)) { if (isMappedSuperWithNoProperties(superclass)) { // ok to stop and treat just the same as Object.class return; } throw new BeanNotEnhancedException("Super type " + superclass + " is not enhanced? Check the packages specified in ebean.mf See https://ebean.io/docs/trouble-shooting#not-enhanced"); } // recursively continue up the inheritance hierarchy checkInheritedClasses(superclass); } /** * Return true if this is a MappedSuperclass bean with no persistent properties. * If so it is ok for it not to be enhanced. */ private boolean isMappedSuperWithNoProperties(Class beanClass) { // do not search recursive here MappedSuperclass annotation = AnnotationUtil.get(beanClass, MappedSuperclass.class); if (annotation == null) { return false; } Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isTransient(field.getModifiers()) && !field.isAnnotationPresent(Transient.class)) { return false; } } return true; } /** * Return the changeLogPrepare (for setting user context into the ChangeSet * in the foreground thread). */ public ChangeLogPrepare changeLogPrepare() { return changeLogPrepare; } /** * Return the changeLogListener (that actually does the logging). */ public ChangeLogListener changeLogListener() { return changeLogListener; } private void addPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { String baseTable = prop.getDesc().getBaseTable(); DeployTableJoin inverse = prop.getTableJoin().createInverse(baseTable); TableJoin inverseJoin = new TableJoin(inverse, prop.getForeignKey()); DeployBeanInfo target = deployInfoMap.get(prop.getTargetType()); target.setPrimaryKeyJoin(inverseJoin); } /** * Create a DeployBeanDescriptor for an ElementCollection target. */ public DeployBeanDescriptor createDeployDescriptor(Class targetType) { return new DeployBeanDescriptor<>(this, targetType, config); } /** * Create a BeanDescriptor for an ElementCollection target. */ public BeanDescriptor createElementDescriptor(DeployBeanDescriptor elementDescriptor, ManyType manyType, boolean scalar) { ElementHelp elementHelp = elementHelper(manyType); if (manyType.isMap()) { if (scalar) { return new BeanDescriptorElementScalarMap<>(this, elementDescriptor, elementHelp); } else { return new BeanDescriptorElementEmbeddedMap<>(this, elementDescriptor, elementHelp); } } if (scalar) { return new BeanDescriptorElementScalar<>(this, elementDescriptor, elementHelp); } else { return new BeanDescriptorElementEmbedded<>(this, elementDescriptor, elementHelp); } } private ElementHelp elementHelper(ManyType manyType) { switch (manyType) { case LIST: return new ElementHelpList(); case SET: return new ElementHelpSet(); case MAP: return new ElementHelpMap(); default: throw new IllegalStateException("manyType unexpected " + manyType); } } public void visitMetrics(MetricVisitor visitor) { for (BeanDescriptor desc : immutableDescriptorList) { desc.visitMetrics(visitor); } for (BeanDescriptor desc : elementDescriptors) { desc.visitMetrics(visitor); } } public List queryPlanInit(QueryPlanInit request) { List list = new ArrayList<>(); for (BeanDescriptor desc : immutableDescriptorList) { desc.queryPlanInit(request, list); } return list; } public BindMaxLength bindMaxLength() { return bindMaxLength; } /** * Comparator to sort the BeanDescriptors by name. */ private static final class BeanDescComparator implements Comparator>, Serializable { private static final long serialVersionUID = 1L; @Override public int compare(BeanDescriptor o1, BeanDescriptor o2) { return o1.name().compareTo(o2.name()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy