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.Model;
import io.ebean.RawSqlBuilder;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.config.EncryptKey;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.NamingConvention;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.dbplatform.DbIdentity;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
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.plugin.BeanType;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.InternalConfiguration;
import io.ebeaninternal.server.core.Message;
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.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.deploy.parse.ReadAnnotations;
import io.ebeaninternal.server.deploy.parse.TransientProperties;
import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
import io.ebeaninternal.xmlmapping.model.XmEbean;
import io.ebeaninternal.xmlmapping.model.XmEntity;
import io.ebeaninternal.xmlmapping.model.XmNamedQuery;
import io.ebeaninternal.xmlmapping.model.XmRawSql;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import io.ebeanservice.docstore.api.DocStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Creates BeanDescriptors.
 */
public class BeanDescriptorManager implements BeanDescriptorMap {

  private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);

  private static final BeanDescComparator beanDescComparator = new BeanDescComparator();

  private final ReadAnnotations readAnnotations;

  private final TransientProperties transientProperties;

  /**
   * Helper to derive inheritance information.
   */
  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 ServerConfig serverConfig;

  private final ChangeLogListener changeLogListener;

  private final ChangeLogRegister changeLogRegister;

  private final ChangeLogPrepare changeLogPrepare;

  private final DocStoreFactory docStoreFactory;

  private int entityBeanCount;

  private final boolean updateChangesOnly;

  private final BootupClasses bootupClasses;

  private final String serverName;

  private Map, DeployBeanInfo> deployInfoMap = new HashMap<>();

  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 List> immutableDescriptorList;

  private final DbIdentity dbIdentity;

  private final DataSource dataSource;

  private final DatabasePlatform databasePlatform;

  private final SpiCacheManager cacheManager;

  private final BackgroundExecutor backgroundExecutor;

  private final int dbSequenceBatchSize;

  private final EncryptKeyManager encryptKeyManager;

  private final IdBinderFactory idBinderFactory;

  private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;

  private final boolean eagerFetchLobs;

  private final String asOfViewSuffix;

  /**
   * 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<>();

  /**
   * Create for a given database dbConfig.
   */
  public BeanDescriptorManager(InternalConfiguration config) {

    this.serverConfig = config.getServerConfig();
    this.serverName = InternString.intern(serverConfig.getName());
    this.cacheManager = config.getCacheManager();
    this.docStoreFactory = config.getDocStoreFactory();
    this.dbSequenceBatchSize = serverConfig.getDatabaseSequenceBatchSize();
    this.backgroundExecutor = config.getBackgroundExecutor();
    this.dataSource = serverConfig.getDataSource();
    this.encryptKeyManager = serverConfig.getEncryptKeyManager();
    this.databasePlatform = serverConfig.getDatabasePlatform();
    this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
    this.eagerFetchLobs = serverConfig.isEagerFetchLobs();

    this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
    String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig);
    this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig.isDisableL2Cache());
    this.bootupClasses = config.getBootupClasses();
    this.createProperties = config.getDeployCreateProperties();
    this.namingConvention = serverConfig.getNamingConvention();
    this.dbIdentity = config.getDatabasePlatform().getDbIdentity();
    this.deplyInherit = config.getDeployInherit();
    this.deployUtil = config.getDeployUtil();

    this.beanManagerFactory = new BeanManagerFactory(config.getDatabasePlatform());

    this.updateChangesOnly = serverConfig.isUpdateChangesOnly();

    this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory();
    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());
  }

  /**
   * Return the AsOfViewSuffix based on the DbHistorySupport.
   */
  private String getAsOfViewSuffix(DatabasePlatform databasePlatform, ServerConfig serverConfig) {

    DbHistorySupport historySupport = databasePlatform.getHistorySupport();
    // with historySupport returns a simple view suffix or the sql2011 as of timestamp suffix
    return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix());
  }

  /**
   * Return the versions between timestamp suffix based on the DbHistorySupport.
   */
  private String getVersionsBetweenSuffix(DatabasePlatform databasePlatform, ServerConfig serverConfig) {

    DbHistorySupport historySupport = databasePlatform.getHistorySupport();
    // with historySupport returns a simple view suffix or the sql2011 versions between timestamp suffix
    return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
  }

  @Override
  public ServerConfig getServerConfig() {
    return serverConfig;
  }

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

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

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

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

  public String getServerName() {
    return serverName;
  }

  public SpiCacheManager getCacheManager() {
    return cacheManager;
  }

  public NamingConvention getNamingConvention() {
    return namingConvention;
  }

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

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

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

  /**
   * Deploy returning the asOfTableMap (which is required by the SQL builders).
   */
  public Map deploy() {

    try {
      createListeners();
      readEntityDeploymentInitial();
      readXmlMapping();
      readEmbeddedDeployment();
      readEntityBeanTable();
      readEntityDeploymentAssociations();
      readInheritedIdGenerators();
      // creates the BeanDescriptors
      readEntityRelationships();

      List> list = new ArrayList<>(descMap.values());
      Collections.sort(list, beanDescComparator);
      immutableDescriptorList = Collections.unmodifiableList(list);

      initialiseAll();
      readForeignKeys();

      readTableToDescriptor();

      logStatus();

      deployInfoMap.clear();
      deployInfoMap = null;

      return asOfTableMap;

    } catch (RuntimeException e) {
      logger.error("Error in deployment", e);
      throw e;
    }
  }

  private void readXmlMapping() {

    try {
      ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();

      Enumeration resources = classLoader.getResources("ebean.xml");

      List mappings = new ArrayList<>();
      while (resources.hasMoreElements()) {
        URL url = resources.nextElement();
        InputStream is = url.openStream();
        mappings.add(XmlMappingReader.read(is));
        is.close();
      }

      for (XmEbean mapping : mappings) {
        List entityDeploy = mapping.getEntity();
        for (XmEntity deploy : entityDeploy) {
          readEntityMapping(classLoader, deploy);
        }
      }

    } catch (IOException e) {
      throw new RuntimeException("Error reading ebean.xml", e);
    }
  }

  private void readEntityMapping(ClassLoader classLoader, XmEntity entityDeploy) {

    String entityClassName = entityDeploy.getClazz();
    Class entityClass;
    try {
      entityClass = Class.forName(entityClassName, false, classLoader);
    } catch (Exception e) {
      logger.error("Could not load entity bean class " + entityClassName + " for ebean.xml entry");
      return;
    }

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

    } else {
      for (XmRawSql sql : entityDeploy.getRawSql()) {
        RawSqlBuilder builder = RawSqlBuilder.parse(sql.getQuery().getValue());
        for (XmColumnMapping columnMapping : sql.getColumnMapping()) {
          builder.columnMapping(columnMapping.getColumn(), columnMapping.getProperty());
        }
        for (XmAliasMapping aliasMapping : sql.getAliasMapping()) {
          builder.tableAliasMapping(aliasMapping.getAlias(), aliasMapping.getProperty());
        }
        info.addRawSql(sql.getName(), builder.create());
      }

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

  /**
   * Return the Encrypt key given the table and column name.
   */
  public EncryptKey getEncryptKey(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) {

    String tableName = tableIUD.getTableName().toLowerCase();
    List> normalBeanTypes = tableToDescMap.get(tableName);
    if (normalBeanTypes != null) {
      // 'normal' entity beans based on a "base table"
      for (BeanDescriptor normalBeanType : normalBeanTypes) {
        normalBeanType.cacheHandleBulkUpdate(tableIUD);
      }
    }
    List> viewBeans = tableToViewDescMap.get(tableName);
    if (viewBeans != null) {
      // entity beans based on a "view"
      for (BeanDescriptor viewBean : viewBeans) {
        viewBean.cacheHandleBulkUpdate(tableIUD);
      }
    }
  }

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

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

  /**
   * 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.
   * 

* This is generally used to maintain caches from table names. *

*/ private void readTableToDescriptor() { for (BeanDescriptor desc : descMap.values()) { String baseTable = desc.getBaseTable(); if (baseTable != null) { baseTable = baseTable.toLowerCase(); List> list = tableToDescMap.get(baseTable); if (list == null) { list = new ArrayList<>(1); tableToDescMap.put(baseTable, list); } list.add(desc); } if (desc.getEntityType() == 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.getDependentTables(); if (dependentTables != null && dependentTables.length > 0) { for (String depTable : dependentTables) { depTable = depTable.toLowerCase(); List> list = tableToViewDescMap.get(depTable); if (list == null) { list = new ArrayList<>(1); tableToViewDescMap.put(depTable, list); } list.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 // 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(asOfTableMap, draftTableMap); } // 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(asOfTableMap, asOfViewSuffix, draftTableMap); } // 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()) { BeanManager m = beanManagerFactory.create(d); beanManagerMap.put(d.getFullName(), m); checkForValidEmbeddedId(d); } } } private void checkForValidEmbeddedId(BeanDescriptor d) { IdBinder idBinder = d.getIdBinder(); if (idBinder != null && idBinder instanceof IdBinderEmbedded) { IdBinderEmbedded embId = (IdBinderEmbedded) idBinder; BeanDescriptor idBeanDescriptor = embId.getIdBeanDescriptor(); Class idType = idBeanDescriptor.getBeanType(); try { idType.getDeclaredMethod("hashCode"); idType.getDeclaredMethod("equals", Object.class); } catch (NoSuchMethodException e) { checkMissingHashCodeOrEquals(e, idType, d.getBeanType()); } } } 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> getBeanDescriptorList() { return immutableDescriptorList; } public BeanTable getBeanTable(Class type) { return beanTableMap.get(type); } @SuppressWarnings("unchecked") public BeanManager getBeanManager(Class entityType) { return (BeanManager) getBeanManager(entityType.getName()); } public BeanManager getBeanManager(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(); logger.debug("BeanPersistControllers[" + cc + "] BeanFinders[" + fc + "] BeanPersistListeners[" + lc + "] BeanQueryAdapters[" + qa + "] BeanPostLoaders[" + pl + "] BeanPostConstructors[" + pc + "]"); } private void logStatus() { logger.debug("Entities[{}]", entityBeanCount); } private BeanDescriptor createEmbedded(Class beanClass) { DeployBeanInfo info = getDeploy(beanClass); return new BeanDescriptor<>(this, info.getDescriptor()); } /** * Return the bean deploy info for the given class. */ @SuppressWarnings("unchecked") public DeployBeanInfo getDeploy(Class cls) { return (DeployBeanInfo) deployInfoMap.get(cls); } private void registerBeanDescriptor(BeanDescriptor desc) { descMap.put(desc.getBeanType().getName(), desc); if (desc.isDocStoreMapped()) { descQueueMap.put(desc.getDocStoreQueueId(), desc); } } /** * Read deployment information for all the embedded beans. */ private void readEmbeddedDeployment() { List> embeddedClasses = bootupClasses.getEmbeddables(); for (Class embeddedClass : embeddedClasses) { registerBeanDescriptor(createEmbedded(embeddedClass)); } } /** * 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); } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); readDeployAssociations(info); deployInfoMap.put(entityClass, 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); } } /** * 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); } @SuppressWarnings({"unchecked", "rawtypes"}) private void readEntityRelationships() { // We only perform 'circular' checks etc after we have // all the DeployBeanDescriptors created and in the map. for (DeployBeanInfo info : deployInfoMap.values()) { checkMappedBy(info); } for (DeployBeanInfo info : deployInfoMap.values()) { secondaryPropsJoins(info); } // Set inheritance info for (DeployBeanInfo info : deployInfoMap.values()) { setInheritanceInfo(info); } for (DeployBeanInfo info : deployInfoMap.values()) { registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor())); } } /** * Sets the inheritance info. ~EMG fix for join problem * * @param info the new 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.getFullBeanName() + ". 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) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { if (oneProp.getMappedBy() != null) { checkMappedByOneToOne(oneProp); } } } for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { if (manyProp.isManyToMany()) { checkMappedByManyToMany(manyProp); } else { checkMappedByOneToMany(info, manyProp); } } } } private DeployBeanDescriptor getTargetDescriptor(DeployBeanPropertyAssoc prop) { Class targetType = prop.getTargetType(); DeployBeanInfo info = deployInfoMap.get(targetType); if (info == null) { String msg = "Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName(); throw new PersistenceException(msg); } 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 = getTargetDescriptor(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 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); String m = "Implicitly found mappedBy for " + targetDesc + "." + prop; m += " by searching for [" + searchName + "] against " + matchSet; logger.debug(m); return true; } } } } // multiple options so should specify mappedBy property String msg = "Error on " + prop.getFullBeanName() + " 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); } /** * 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). *

*/ @SuppressWarnings({"unchecked", "rawtypes"}) private void makeUnidirectional(DeployBeanInfo info, DeployBeanPropertyAssocMany oneToMany) { DeployBeanDescriptor targetDesc = getTargetDescriptor(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.getFullBeanName() + ". @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(); // create the 'shadow' unidirectional property // which is put on the target descriptor DeployBeanPropertyAssocOne unidirectional = new DeployBeanPropertyAssocOne(targetDesc, owningType); unidirectional.setUndirectionalShadow(); unidirectional.setNullable(false); unidirectional.setDbRead(true); unidirectional.setDbInsertable(true); unidirectional.setDbUpdateable(false); targetDesc.setUnidirectional(unidirectional); // specify table and table alias... BeanTable beanTable = getBeanTable(owningType); unidirectional.setBeanTable(beanTable); unidirectional.setName(beanTable.getBaseTable()); info.setBeanJoinType(unidirectional, true); // define the TableJoin DeployTableJoin oneToManyJoin = oneToMany.getTableJoin(); if (!oneToManyJoin.hasJoinColumns()) { throw new RuntimeException("No join columns"); } // inverse of the oneToManyJoin DeployTableJoin unidirectionalJoin = unidirectional.getTableJoin(); unidirectionalJoin.setColumns(oneToManyJoin.columns(), true); } 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 = getTargetDescriptor(prop); DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { String m = "Error on " + prop.getFullBeanName(); m += " Can not find mappedBy property [" + targetDesc + "." + mappedBy + "] "; throw new PersistenceException(m); } if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { String m = "Error on " + prop.getFullBeanName(); m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; throw new PersistenceException(m); } DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; if (!mappedAssocOne.isOneToOne()) { String m = "Error on " + prop.getFullBeanName(); m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; throw new PersistenceException(m); } 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 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) { DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); if (targetDesc.isDraftableElement()) { // automatically turning on orphan removal and CascadeType.ALL prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS); prop.getCascadeInfo().setSaveDelete(true, true); } if (prop.getMappedBy() == null) { if (!findMappedBy(prop)) { makeUnidirectional(info, 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 DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { String m = "Error on " + prop.getFullBeanName(); m += " Can not find mappedBy property [" + mappedBy + "] "; m += "in [" + targetDesc + "]"; throw new PersistenceException(m); } if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { String m = "Error on " + prop.getFullBeanName(); m += ". mappedBy property [" + mappedBy + "]is not a ManyToOne?"; m += "in [" + targetDesc + "]"; throw new PersistenceException(m); } DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; 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()); } } /** * 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 (getTargetDescriptor(prop).isDraftable()) { prop.setIntersectionDraftTable(); } return; } // get the mappedBy property DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); if (mappedProp == null) { String m = "Error on " + prop.getFullBeanName(); m += " Can not find mappedBy property [" + mappedBy + "] "; m += "in [" + targetDesc + "]"; throw new PersistenceException(m); } if (!(mappedProp instanceof DeployBeanPropertyAssocMany)) { String m = "Error on " + prop.getFullBeanName(); m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; throw new PersistenceException(m); } DeployBeanPropertyAssocMany mappedAssocMany = (DeployBeanPropertyAssocMany) mappedProp; if (!mappedAssocMany.isManyToMany()) { String m = "Error on " + prop.getFullBeanName(); m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; throw new PersistenceException(m); } // define the relationships/joins on this side as the // reverse of the other mappedBy side ... // DeployTableJoin mappedJoin = mappedAssocMany.getTableJoin(); 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 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, serverConfig); desc.setUpdateChangesOnly(updateChangesOnly); 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, eagerFetchLobs); 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 createByteCode(desc); } /** * Set the Identity generation mechanism. */ private void setIdGeneration(DeployBeanDescriptor desc) { if (desc.getIdGenerator() != null) { // already assigned (So custom or UUID) return; } if (desc.propertiesId().isEmpty()) { // bean doesn't have an Id property if (desc.isBaseTableType() && desc.getBeanFinder() == null) { // expecting an id property logger.warn(Message.msg("deploy.nouid", desc.getFullName())); } return; } if (IdType.SEQUENCE.equals(desc.getIdType()) && !dbIdentity.isSupportsSequence()) { // explicit sequence but not supported by the DatabasePlatform logger.info("Explicit sequence on " + desc.getFullName() + " but not supported by DB Platform - ignored"); desc.setIdType(null); } if (IdType.IDENTITY.equals(desc.getIdType()) && !dbIdentity.isSupportsIdentity()) { // explicit identity but not supported by the DatabasePlatform logger.info("Explicit Identity on " + desc.getFullName() + " but not supported by DB Platform - ignored"); desc.setIdType(null); } if (desc.getIdType() == null) { if (desc.isPrimaryKeyCompoundOrNonNumeric()) { // assuming that this is a user supplied key like ISO country code or ISO currency code or lookup table code logger.debug("Expecting user defined identity on " + desc.getFullName() + " - not using db sequence or autoincrement"); desc.setIdType(IdType.EXTERNAL); return; } // use the default. IDENTITY or SEQUENCE. desc.setIdType(dbIdentity.getIdType()); desc.setIdTypePlatformDefault(); } if (desc.getBaseTable() == null) { // no base table so not going to set Identity // of sequence information return; } if (IdType.IDENTITY.equals(desc.getIdType())) { // used when getGeneratedKeys is not supported (SQL Server 2000) String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable()); desc.setSelectLastInsertedId(selectLastInsertedId); return; } String seqName = desc.getIdGeneratorName(); if (seqName != null) { logger.debug("explicit sequence " + seqName + " on " + desc.getFullName()); } else { String primaryKeyColumn = desc.getSinglePrimaryKeyColumn(); // use namingConvention to define sequence name seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn); } // create the sequence based IdGenerator desc.setIdGenerator(createSequenceIdGenerator(seqName)); } private PlatformIdGenerator createSequenceIdGenerator(String seqName) { return databasePlatform.createSequenceIdGenerator(backgroundExecutor, dataSource, seqName, dbSequenceBatchSize); } private void createByteCode(DeployBeanDescriptor deploy) { // check to see if the bean supports EntityBean interface // generate a subclass if required setEntityBeanClass(deploy); // use Code generation or Standard reflection to support // getter and setter methods setBeanReflect(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 setBeanReflect(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.getBeanType()); desc.setProperties(reflectProps.getProperties()); for (DeployBeanProperty prop : desc.propertiesAll()) { String propName = prop.getName(); Integer pos = reflectProps.getPropertyIndex(propName); if (pos == null) { if (isPersistentField(prop)) { throw new IllegalStateException("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.getAggregation(), desc)); } } } } /** * Return true if this is a persistent field (not transient or static). */ private boolean isPersistentField(DeployBeanProperty prop) { Field field = prop.getField(); 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) { boolean hasVersionProperty = false; List props = desc.propertiesBase(); for (DeployBeanProperty prop : props) { if (prop.isVersionColumn()) { hasVersionProperty = true; } } return hasVersionProperty; } private boolean hasEntityBeanInterface(Class beanClass) { Class[] interfaces = beanClass.getInterfaces(); for (Class anInterface : interfaces) { if (anInterface.equals(EntityBean.class)) { return true; } } return false; } /** * Test the bean type to see if it implements EntityBean interface already. */ private void setEntityBeanClass(DeployBeanDescriptor desc) { Class beanClass = desc.getBeanType(); if (!hasEntityBeanInterface(beanClass)) { throw new IllegalStateException("Bean " + beanClass + " is not enhanced?"); } // 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)) { // we got to the top of the inheritance return; } if (Model.class.equals(superclass)) { // top of the inheritance. Not enhancing Model at this stage return; } if (!EntityBean.class.isAssignableFrom(superclass)) { if (isMappedSuperWithNoProperties(superclass)) { // ok to stop and treat just the same as Object.class return; } throw new IllegalStateException("Super type " + superclass + " is 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) { // Attention: do not use AnnotationBase.findAnnotation(cls,...) here. MappedSuperclass annotation = beanClass.getAnnotation(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 getChangeLogPrepare() { return changeLogPrepare; } /** * Return the changeLogListener (that actually does the logging). */ public ChangeLogListener getChangeLogListener() { return changeLogListener; } /** * Comparator to sort the BeanDescriptors by name. */ private static final class BeanDescComparator implements Comparator>, Serializable { private static final long serialVersionUID = 1L; public int compare(BeanDescriptor o1, BeanDescriptor o2) { return o1.getName().compareTo(o2.getName()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy