package io.ebeaninternal.server.deploy; import io.ebean.OrderBy; import io.ebean.PersistenceContextScope; import io.ebean.Query; import io.ebean.RawSql; import io.ebean.SqlUpdate; import io.ebean.Transaction; import io.ebean.ValuePair; import io.ebean.annotation.DocStoreMode; import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; import io.ebean.bean.PersistenceContext; import io.ebean.bean.PersistenceContextUtil; import io.ebean.config.EncryptKey; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.IdType; import io.ebean.config.dbplatform.PlatformIdGenerator; import io.ebean.event.BeanFindController; import io.ebean.event.BeanPersistController; import io.ebean.event.BeanPersistListener; import io.ebean.event.BeanPostConstructListener; import io.ebean.event.BeanPostLoad; import io.ebean.event.BeanQueryAdapter; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeLogFilter; import io.ebean.event.changelog.ChangeType; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; import io.ebean.event.readaudit.ReadEvent; import io.ebean.meta.MetaBeanInfo; import io.ebean.meta.MetaQueryPlanStatistic; import io.ebean.plugin.BeanDocType; import io.ebean.plugin.BeanType; import io.ebean.plugin.ExpressionPath; import io.ebean.plugin.Property; import io.ebeaninternal.api.CQueryPlanKey; import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.api.LoadContext; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiUpdatePlan; import io.ebeaninternal.api.TransactionEventTable.TableIUD; import io.ebeaninternal.server.cache.CacheChangeSet; import io.ebeaninternal.server.cache.CachedBeanData; import io.ebeaninternal.server.cache.CachedManyIds; import io.ebeaninternal.server.core.CacheOptions; import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.core.DiffHelp; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.id.IdBinder; import io.ebeaninternal.server.deploy.id.ImportedId; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists; import io.ebeaninternal.server.el.ElComparator; import io.ebeaninternal.server.el.ElComparatorCompound; import io.ebeaninternal.server.el.ElComparatorProperty; import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.persist.DmlUtil; import io.ebeaninternal.server.query.CQueryPlan; import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.query.SplitName; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.text.json.ReadJson; import io.ebeaninternal.server.text.json.WriteJson; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.util.SortByClause; import io.ebeaninternal.util.SortByClauseParser; import io.ebeanservice.docstore.api.DocStoreBeanAdapter; import io.ebeanservice.docstore.api.DocStoreUpdateContext; import io.ebeanservice.docstore.api.DocStoreUpdates; import io.ebeanservice.docstore.api.mapping.DocMappingBuilder; import io.ebeanservice.docstore.api.mapping.DocPropertyMapping; import io.ebeanservice.docstore.api.mapping.DocPropertyType; import io.ebeanservice.docstore.api.mapping.DocumentMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.PersistenceException; import java.io.IOException; import java.lang.reflect.Modifier; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Describes Beans including their deployment information. */ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class); private final ConcurrentHashMap<String, SpiUpdatePlan> updatePlanCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<CQueryPlanKey, CQueryPlan> queryPlanCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ElPropertyValue> elCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ElPropertyDeploy> elDeployCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>(); private final Map<String, RawSql> namedRawSql; private final Map<String, String> namedQuery; public enum EntityType { ORM, EMBEDDED, VIEW, SQL, DOC } /** * The EbeanServer name. Same as the plugin name. */ private final String serverName; /** * The nature/type of this bean. */ private final EntityType entityType; /** * Type of Identity generation strategy used. */ private final IdType idType; private final boolean idTypePlatformDefault; private final PlatformIdGenerator idGenerator; /** * The database sequence name (optional). */ private final String sequenceName; private final int sequenceInitialValue; private final int sequenceAllocationSize; /** * SQL used to return last inserted id. Used for Identity columns where * getGeneratedKeys is not supported. */ private final String selectLastInsertedId; private final boolean autoTunable; /** * The concurrency mode for beans of this type. */ private final ConcurrencyMode concurrencyMode; private final IndexDefinition[] indexDefinitions; private final String[] dependentTables; /** * The base database table. */ private final String baseTable; private final String baseTableAsOf; private final String baseTableVersionsBetween; private final boolean historySupport; private final BeanProperty softDeleteProperty; private final boolean softDelete; private final String draftTable; /** * DB table comment. */ private final String dbComment; /** * Set to true if read auditing is on for this bean type. */ private final boolean readAuditing; private final boolean draftable; private final boolean draftableElement; private final BeanProperty unmappedJson; private final BeanProperty tenant; private final BeanProperty draft; private final BeanProperty draftDirty; /** * Map of BeanProperty Linked so as to preserve order. */ protected final LinkedHashMap<String, BeanProperty> propMap; /** * Map of DB column to property path (for nativeSql mapping). */ private final Map<String, String> columnPath = new HashMap<>(); /** * Map of related table to assoc property (for nativeSql mapping). */ private final Map<String, BeanPropertyAssoc<?>> tablePath = new HashMap<>(); /** * The type of bean this describes. */ private final Class<T> beanType; protected final Class<?> rootBeanType; /** * This is not sent to a remote client. */ private final BeanDescriptorMap owner; private final String[] properties; /** * Intercept pre post on insert,update, and delete . */ private volatile BeanPersistController persistController; private final BeanPostLoad beanPostLoad; private final BeanPostConstructListener beanPostConstructListener; /** * Listens for post commit insert update and delete events. */ private volatile BeanPersistListener persistListener; private final BeanQueryAdapter queryAdapter; /** * If set overrides the find implementation. Server side only. */ private final BeanFindController beanFinder; /** * Used for fine grain filtering for the change log. */ private final ChangeLogFilter changeLogFilter; /** * The table joins for this bean. */ private final TableJoin[] derivedTableJoins; /** * Inheritance information. Server side only. */ protected final InheritInfo inheritInfo; /** * Derived list of properties that make up the unique id. */ protected final BeanProperty idProperty; private final int idPropertyIndex; /** * Derived list of properties that are used for version concurrency checking. */ private final BeanProperty versionProperty; private final int versionPropertyIndex; private final BeanProperty whenModifiedProperty; private final BeanProperty whenCreatedProperty; /** * Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries. */ private final int[] unloadProperties; /** * Properties local to this type (not from a super type). */ private final BeanProperty[] propertiesLocal; /** * Scalar mutable properties (need to dirty check on update). */ private final BeanProperty[] propertiesMutable; private final BeanPropertyAssocOne<?> unidirectional; /** * list of properties that are Lists/Sets/Maps (Derived). */ private final BeanProperty[] propertiesNonMany; private final BeanPropertyAssocMany<?>[] propertiesMany; private final BeanPropertyAssocMany<?>[] propertiesManySave; private final BeanPropertyAssocMany<?>[] propertiesManyDelete; private final BeanPropertyAssocMany<?>[] propertiesManyToMany; /** * list of properties that are associated beans and not embedded (Derived). */ private final BeanPropertyAssocOne<?>[] propertiesOne; private final BeanPropertyAssocOne<?>[] propertiesOneImported; private final BeanPropertyAssocOne<?>[] propertiesOneImportedSave; private final BeanPropertyAssocOne<?>[] propertiesOneImportedDelete; //private final BeanPropertyAssocOne<?>[] propertiesOneExported; private final BeanPropertyAssocOne<?>[] propertiesOneExportedSave; private final BeanPropertyAssocOne<?>[] propertiesOneExportedDelete; /** * list of properties that are embedded beans. */ private final BeanPropertyAssocOne<?>[] propertiesEmbedded; /** * List of the scalar properties excluding id and secondary table properties. */ private final BeanProperty[] propertiesBaseScalar; private final BeanProperty[] propertiesTransient; /** * All non transient properties excluding the id properties. */ private final BeanProperty[] propertiesNonTransient; protected final BeanProperty[] propertiesIndex; /** * The bean class name or the table name for MapBeans. */ private final String fullName; /** * Flag used to determine if saves can be skipped. */ private boolean saveRecurseSkippable; /** * Flag used to determine if deletes can be skipped. */ private boolean deleteRecurseSkippable; private final EntityBean prototypeEntityBean; private final IdBinder idBinder; private String idBinderInLHSSql; private String idBinderIdSql; private String deleteByIdSql; private String deleteByIdInSql; private String whereIdInSql; private String softDeleteByIdSql; private String softDeleteByIdInSql; private final String name; private final String baseTableAlias; /** * If true then only changed properties get updated. */ private final boolean updateChangesOnly; private final boolean cacheSharableBeans; private final String docStoreQueueId; private final BeanDescriptorDraftHelp<T> draftHelp; private final BeanDescriptorCacheHelp<T> cacheHelp; private final BeanDescriptorJsonHelp<T> jsonHelp; private DocStoreBeanAdapter<T> docStoreAdapter; private DocumentMapping docMapping; private boolean docStoreEmbeddedInvalidation; private final String defaultSelectClause; private SpiEbeanServer ebeanServer; /** * Construct the BeanDescriptor. */ public BeanDescriptor(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) { this.owner = owner; this.serverName = owner.getServerName(); this.entityType = deploy.getEntityType(); this.properties = deploy.getProperties(); this.name = InternString.intern(deploy.getName()); this.baseTableAlias = "t0"; this.fullName = InternString.intern(deploy.getFullName()); this.beanType = deploy.getBeanType(); this.rootBeanType = PersistenceContextUtil.root(beanType); this.prototypeEntityBean = createPrototypeEntityBean(beanType); this.namedQuery = deploy.getNamedQuery(); this.namedRawSql = deploy.getNamedRawSql(); this.inheritInfo = deploy.getInheritInfo(); this.beanFinder = deploy.getBeanFinder(); this.persistController = deploy.getPersistController(); this.persistListener = deploy.getPersistListener(); this.beanPostConstructListener = deploy.getPostConstructListener(); this.beanPostLoad = deploy.getPostLoad(); this.queryAdapter = deploy.getQueryAdapter(); this.changeLogFilter = deploy.getChangeLogFilter(); this.defaultSelectClause = deploy.getDefaultSelectClause(); this.idType = deploy.getIdType(); this.idTypePlatformDefault = deploy.isIdTypePlatformDefault(); this.idGenerator = deploy.getIdGenerator(); this.sequenceName = deploy.getSequenceName(); this.sequenceInitialValue = deploy.getSequenceInitialValue(); this.sequenceAllocationSize = deploy.getSequenceAllocationSize(); this.selectLastInsertedId = deploy.getSelectLastInsertedId(); this.concurrencyMode = deploy.getConcurrencyMode(); this.updateChangesOnly = deploy.isUpdateChangesOnly(); this.indexDefinitions = deploy.getIndexDefinitions(); this.readAuditing = deploy.isReadAuditing(); this.draftable = deploy.isDraftable(); this.draftableElement = deploy.isDraftableElement(); this.historySupport = deploy.isHistorySupport(); this.draftTable = deploy.getDraftTable(); this.baseTable = InternString.intern(deploy.getBaseTable()); this.baseTableAsOf = deploy.getBaseTableAsOf(); this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween(); this.dependentTables = deploy.getDependentTables(); this.dbComment = deploy.getDbComment(); this.autoTunable = EntityType.ORM == entityType && (beanFinder == null); // helper object used to derive lists of properties DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy); this.softDeleteProperty = listHelper.getSoftDeleteProperty(); this.softDelete = (softDeleteProperty != null); this.idProperty = listHelper.getId(); this.versionProperty = listHelper.getVersionProperty(); this.unmappedJson = listHelper.getUnmappedJson(); this.tenant = listHelper.getTenant(); this.draft = listHelper.getDraft(); this.draftDirty = listHelper.getDraftDirty(); this.propMap = listHelper.getPropertyMap(); this.propertiesTransient = listHelper.getTransients(); this.propertiesNonTransient = listHelper.getNonTransients(); this.propertiesBaseScalar = listHelper.getBaseScalar(); this.propertiesEmbedded = listHelper.getEmbedded(); this.propertiesLocal = listHelper.getLocal(); this.propertiesMutable = listHelper.getMutable(); this.unidirectional = listHelper.getUnidirectional(); this.propertiesOne = listHelper.getOnes(); this.propertiesOneExportedSave = listHelper.getOneExportedSave(); this.propertiesOneExportedDelete = listHelper.getOneExportedDelete(); this.propertiesOneImported = listHelper.getOneImported(); this.propertiesOneImportedSave = listHelper.getOneImportedSave(); this.propertiesOneImportedDelete = listHelper.getOneImportedDelete(); this.propertiesMany = listHelper.getMany(); this.propertiesNonMany = listHelper.getNonMany(); this.propertiesManySave = listHelper.getManySave(); this.propertiesManyDelete = listHelper.getManyDelete(); this.propertiesManyToMany = listHelper.getManyToMany(); this.derivedTableJoins = listHelper.getTableJoin(); boolean noRelationships = propertiesOne.length + propertiesMany.length == 0; this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly(); this.cacheHelp = new BeanDescriptorCacheHelp<>(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported); this.jsonHelp = new BeanDescriptorJsonHelp<>(this); this.draftHelp = new BeanDescriptorDraftHelp<>(this); this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy); this.docStoreQueueId = docStoreAdapter.getQueueId(); // Check if there are no cascade save associated beans ( subject to change // in initialiseOther()). Note that if we are in an inheritance hierarchy // then we also need to check every BeanDescriptors in the InheritInfo as // well. We do that later in initialiseOther(). saveRecurseSkippable = (0 == (propertiesOneExportedSave.length + propertiesOneImportedSave.length + propertiesManySave.length)); // Check if there are no cascade delete associated beans (also subject to // change in initialiseOther()). deleteRecurseSkippable = (0 == (propertiesOneExportedDelete.length + propertiesOneImportedDelete.length + propertiesManyDelete.length)); // object used to handle Id values this.idBinder = owner.createIdBinder(idProperty); this.whenModifiedProperty = findWhenModifiedProperty(); this.whenCreatedProperty = findWhenCreatedProperty(); // derive the index position of the Id and Version properties if (Modifier.isAbstract(beanType.getModifiers())) { this.idPropertyIndex = -1; this.versionPropertyIndex = -1; this.unloadProperties = new int[0]; this.propertiesIndex = new BeanProperty[0]; } else { EntityBeanIntercept ebi = prototypeEntityBean._ebean_getIntercept(); this.idPropertyIndex = (idProperty == null) ? -1 : ebi.findProperty(idProperty.getName()); this.versionPropertyIndex = (versionProperty == null) ? -1 : ebi.findProperty(versionProperty.getName()); this.unloadProperties = derivePropertiesToUnload(prototypeEntityBean); this.propertiesIndex = new BeanProperty[ebi.getPropertyLength()]; for (int i = 0; i < propertiesIndex.length; i++) { propertiesIndex[i] = propMap.get(ebi.getProperty(i)); } } } /** * Derive an array of property positions for properties that are initialised in the constructor. * These properties need to be unloaded when populating beans for queries. */ private int[] derivePropertiesToUnload(EntityBean prototypeEntityBean) { boolean[] loaded = prototypeEntityBean._ebean_getIntercept().getLoaded(); int[] props = new int[loaded.length]; int pos = 0; // collect the positions of the properties initialised in the default constructor. for (int i = 0; i < loaded.length; i++) { if (loaded[i]) { props[pos++] = i; } } if (pos == 0) { // nothing set in the constructor return new int[0]; } // populate a smaller/minimal array int[] unload = new int[pos]; System.arraycopy(props, 0, unload, 0, pos); return unload; } /** * Create an entity bean that is used as a prototype/factory to create new instances. */ private EntityBean createPrototypeEntityBean(Class<T> beanType) { if (Modifier.isAbstract(beanType.getModifiers())) { return null; } try { return (EntityBean) beanType.newInstance(); } catch (Exception e) { throw new IllegalStateException("Error trying to create the prototypeEntityBean for " + beanType, e); } } /** * Return the ServerConfig. */ public ServerConfig getServerConfig() { return owner.getServerConfig(); } /** * Set the server. Primarily so that the Many's can lazy load. */ public void setEbeanServer(SpiEbeanServer ebeanServer) { this.ebeanServer = ebeanServer; for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) { // used for creating lazy loading lists etc aPropertiesMany.setLoader(ebeanServer); } } /** * Return the EbeanServer instance that owns this BeanDescriptor. */ public SpiEbeanServer getEbeanServer() { return ebeanServer; } /** * Return true if this is a "Doc Store only" entity bean. */ @Override public boolean isDocStoreOnly() { return EntityType.DOC == entityType; } /** * Return the type of this domain object. */ public EntityType getEntityType() { return entityType; } public String[] getProperties() { return properties; } /** * Initialise the Id properties first. * <p> * These properties need to be initialised prior to the association properties * as they are used to get the imported and exported properties. * </p> * * @param withHistoryTables map populated if @History is supported on this entity bean */ public void initialiseId(Map<String, String> withHistoryTables, Map<String, String> draftTables) { if (logger.isTraceEnabled()) { logger.trace("BeanDescriptor initialise " + fullName); } if (draftable) { draftTables.put(baseTable, draftTable); } if (historySupport) { // add mapping (used to swap out baseTable for asOf queries) withHistoryTables.put(baseTable, baseTableAsOf); } if (inheritInfo != null) { inheritInfo.setDescriptor(this); } if (isEmbedded()) { // initialise all the properties for (BeanProperty prop : propertiesAll()) { prop.initialise(); } } else { // initialise just the Id properties if (idProperty != null) { idProperty.initialise(); } } } /** * Initialise the exported and imported parts for associated properties. * * @param asOfTableMap the map of base tables to associated 'with history' tables * @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name * @param draftTableMap the map of base tables to associated 'draft' tables. */ public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix, Map<String, String> draftTableMap) { for (BeanPropertyAssocMany<?> aPropertiesManyToMany1 : propertiesManyToMany) { // register associated draft table for M2M intersection aPropertiesManyToMany1.registerDraftIntersectionTable(draftTableMap); } if (historySupport) { // history support on this bean so check all associated intersection tables // and if they are not excluded register the associated 'with history' table for (BeanPropertyAssocMany<?> aPropertiesManyToMany : propertiesManyToMany) { // register associated history table for M2M intersection if (!aPropertiesManyToMany.isExcludedFromHistory()) { TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin(); String intersectionTableName = intersectionTableJoin.getTable(); asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix); } } } if (!isEmbedded()) { // initialise all the non-id properties for (BeanProperty prop : propertiesAll()) { if (!prop.isId()) { prop.initialise(); } prop.registerColumn(this, null); } } if (unidirectional != null) { unidirectional.initialise(); } idBinder.initialise(); idBinderInLHSSql = idBinder.getBindIdInSql(baseTableAlias); idBinderIdSql = idBinder.getBindIdSql(baseTableAlias); String idBinderInLHSSqlNoAlias = idBinder.getBindIdInSql(null); String idEqualsSql = idBinder.getBindIdSql(null); deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql; whereIdInSql = " where " + idBinderInLHSSqlNoAlias + " "; deleteByIdInSql = "delete from " + baseTable + whereIdInSql; if (softDelete) { softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql; softDeleteByIdInSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idBinderInLHSSqlNoAlias + " "; } else { softDeleteByIdSql = null; softDeleteByIdInSql = null; } } void registerColumn(String dbColumn, String path) { columnPath.put(dbColumn.toLowerCase(), path); } void registerTable(String baseTable, BeanPropertyAssoc<?> assocProperty) { if (baseTable != null) { tablePath.put(baseTable.toLowerCase(), assocProperty); } } /** * Perform last initialisation for the descriptor. */ public void initLast() { docStoreEmbeddedInvalidation = docStoreAdapter.hasEmbeddedInvalidation(); } /** * Initialise the document mapping. */ @SuppressWarnings("unchecked") public void initialiseDocMapping() { for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) { aPropertiesMany.initialisePostTarget(); } if (inheritInfo != null && !inheritInfo.isRoot()) { docStoreAdapter = (DocStoreBeanAdapter<T>) inheritInfo.getRoot().desc().docStoreAdapter(); } docMapping = docStoreAdapter.createDocMapping(); docStoreAdapter.registerPaths(); cacheHelp.deriveNotifyFlags(); } public void initInheritInfo() { if (inheritInfo != null) { // need to check every BeanDescriptor in the inheritance hierarchy if (saveRecurseSkippable) { saveRecurseSkippable = inheritInfo.isSaveRecurseSkippable(); } if (deleteRecurseSkippable) { deleteRecurseSkippable = inheritInfo.isDeleteRecurseSkippable(); } } } public void merge(EntityBean bean, EntityBean existing) { EntityBeanIntercept fromEbi = bean._ebean_getIntercept(); EntityBeanIntercept toEbi = existing._ebean_getIntercept(); int propertyLength = toEbi.getPropertyLength(); String[] names = getProperties(); for (int i = 0; i < propertyLength; i++) { if (fromEbi.isLoadedProperty(i)) { BeanProperty property = getBeanProperty(names[i]); if (!toEbi.isLoadedProperty(i)) { Object val = property.getValue(bean); property.setValue(existing, val); } else if (property.isMany()) { property.merge(bean, existing); } } } } /** * Return the ReadAuditLogger for logging read audit events. */ public ReadAuditLogger getReadAuditLogger() { return ebeanServer.getReadAuditLogger(); } /** * Return the ReadAuditPrepare for preparing read audit events prior to logging. */ public ReadAuditPrepare getReadAuditPrepare() { return ebeanServer.getReadAuditPrepare(); } /** * Return true if this request should be included in the change log. */ public BeanChange getChangeLogBean(PersistRequestBean<T> request) { if (changeLogFilter == null) { return null; } PersistRequest.Type type = request.getType(); switch (type) { case INSERT: return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null; case UPDATE: case SOFT_DELETE: return changeLogFilter.includeUpdate(request) ? updateBeanChange(request) : null; case DELETE: return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) : null; default: throw new IllegalStateException("Unhandled request type " + type); } } /** * Return the bean change for a delete. */ private BeanChange deleteBeanChange(PersistRequestBean<T> request) { return newBeanChange(request.getBeanId(), ChangeType.DELETE, Collections.<String, ValuePair>emptyMap()); } /** * Return the bean change for an update. */ private BeanChange updateBeanChange(PersistRequestBean<T> request) { return newBeanChange(request.getBeanId(), ChangeType.UPDATE, diffFlatten(request.getEntityBeanIntercept().getDirtyValues())); } /** * Return the bean change for an insert. */ private BeanChange insertBeanChange(PersistRequestBean<T> request) { return newBeanChange(request.getBeanId(), ChangeType.INSERT, diffForInsert(request.getEntityBean())); } private BeanChange newBeanChange(Object id, ChangeType changeType, Map<String, ValuePair> values) { Object tenantId = ebeanServer.currentTenantId(); return new BeanChange(getBaseTable(), tenantId, id, changeType, values); } public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) { if (id != null) { return deleteById(id, softDelete); } else { return deleteByIdList(idList, softDelete); } } /** * Return the "where id in" sql (for use with UpdateQuery). */ public String getWhereIdInSql() { return whereIdInSql; } /** * Return the "delete by id" sql. */ public String getDeleteByIdInSql() { return deleteByIdInSql; } /** * Return SQL that can be used to delete a list of Id's without any optimistic * concurrency checking. */ private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) { String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql; StringBuilder sb = new StringBuilder(baseSql); String inClause = idBinder.getIdInValueExprDelete(idList.size()); sb.append(inClause); DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); for (Object anIdList : idList) { idBinder.bindId(delete, anIdList); } return delete; } /** * Return SQL that can be used to delete by Id without any optimistic * concurrency checking. */ private SqlUpdate deleteById(Object id, boolean softDelete) { String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql; DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql); Object[] bindValues = idBinder.getBindValues(id); for (Object bindValue : bindValues) { sqlDelete.addParameter(bindValue); } return sqlDelete; } /** * Add objects to ElPropertyDeploy etc. These are used so that expressions on * foreign keys don't require an extra join. */ public void add(BeanFkeyProperty fkey) { elDeployCache.put(fkey.getName(), fkey); } public void initialiseFkeys() { for (BeanPropertyAssocOne<?> aPropertiesOneImported : propertiesOneImported) { if (!aPropertiesOneImported.isFormula()) { aPropertiesOneImported.addFkey(); } } } /** * Return the cache options. */ public CacheOptions getCacheOptions() { return cacheHelp.getCacheOptions(); } /** * Return the Encrypt key given the BeanProperty. */ public EncryptKey getEncryptKey(BeanProperty p) { return owner.getEncryptKey(baseTable, p.getDbColumn()); } /** * Return the Encrypt key given the table and column name. */ public EncryptKey getEncryptKey(String tableName, String columnName) { return owner.getEncryptKey(tableName, columnName); } /** * Return true if this bean type has a default select clause that is not * simply select all properties. */ public boolean hasDefaultSelectClause() { return defaultSelectClause != null; } /** * Return the default select clause. */ public String getDefaultSelectClause() { return defaultSelectClause; } /** * Return true if this object is the root level object in its entity * inheritance. */ public boolean isInheritanceRoot() { return inheritInfo == null || inheritInfo.isRoot(); } /** * Return true if this type maps to a root type of a doc store document (not embedded or ignored). */ @Override public boolean isDocStoreMapped() { return docStoreAdapter.isMapped(); } /** * Return true if this bean type has embedded doc store invalidation. */ public boolean isDocStoreEmbeddedInvalidation() { return docStoreEmbeddedInvalidation; } /** * Return the queueId used to uniquely identify this type when queuing an index updateAdd. */ @Override public String getDocStoreQueueId() { return docStoreQueueId; } @Override public DocumentMapping getDocMapping() { return docMapping; } /** * Return the doc store helper for this bean type. */ @Override public BeanDocType<T> docStore() { return docStoreAdapter; } /** * Return doc store adapter for internal use for processing persist requests. */ public DocStoreBeanAdapter<T> docStoreAdapter() { return docStoreAdapter; } /** * Build the Document mapping recursively with the given prefix relative to the root of the document. */ public void docStoreMapping(final DocMappingBuilder mapping, final String prefix) { if (prefix != null && idProperty != null) { // id property not included in the idProperty.docStoreMapping(mapping, prefix); } if (inheritInfo != null) { String discCol = inheritInfo.getDiscriminatorColumn(); if (Types.VARCHAR == inheritInfo.getDiscriminatorType()) { mapping.add(new DocPropertyMapping(discCol, DocPropertyType.ENUM)); } else { mapping.add(new DocPropertyMapping(discCol, DocPropertyType.INTEGER)); } } for (BeanProperty prop : propertiesNonTransient) { prop.docStoreMapping(mapping, prefix); } if (inheritInfo != null) { inheritInfo.visitChildren(inheritInfo1 -> { for (BeanProperty localProperty : inheritInfo1.localProperties()) { localProperty.docStoreMapping(mapping, prefix); } }); } } /** * Return the root bean type if part of inheritance hierarchy. */ @Override public BeanType<?> root() { if (inheritInfo != null && !inheritInfo.isRoot()) { return inheritInfo.getRoot().desc(); } return this; } /** * Return the named ORM query. */ public String getNamedQuery(String name) { return namedQuery.get(name); } /** * Return the named RawSql query. */ public RawSql getNamedRawSql(String named) { return namedRawSql.get(named); } /** * Return the type of DocStoreMode that should occur for this type of persist request * given the transactions requested mode. */ public DocStoreMode getDocStoreMode(PersistRequest.Type persistType, DocStoreMode txnMode) { return docStoreAdapter.getMode(persistType, txnMode); } public void docStoreInsert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException { docStoreAdapter.insert(idValue, persistRequest, bulkUpdate); } public void docStoreUpdate(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException { docStoreAdapter.update(idValue, persistRequest, bulkUpdate); } /** * Check if this update invalidates an embedded part of a doc store document. */ public void docStoreUpdateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) { docStoreAdapter.updateEmbedded(request, docStoreUpdates); } public void docStoreDeleteById(Object idValue, DocStoreUpdateContext txn) throws IOException { docStoreAdapter.deleteById(idValue, txn); } public T publish(T draftBean, T liveBean) { return draftHelp.publish(draftBean, liveBean); } /** * Reset properties on the draft bean based on @DraftDirty and @DraftReset. */ public boolean draftReset(T draftBean) { return draftHelp.draftReset(draftBean); } /** * Return the draft dirty boolean property or null if there is not one assigned to this bean type. */ public BeanProperty getDraftDirty() { return draftDirty; } /** * Prepare the query for multi-tenancy check for document store only use. */ public void prepareQuery(SpiQuery<T> query) { if (tenant != null && !query.isNativeSql()) { Object tenantId = ebeanServer.currentTenantId(); if (tenantId != null) { query.where().eq(tenant.getName(), tenantId); } } if (isDocStoreOnly()) { query.setUseDocStore(true); } } /** * Return true if there is currently bean caching for this type of bean. */ @Override public boolean isBeanCaching() { return cacheHelp.isBeanCaching(); } /** * Return true if there is query caching for this type of bean. */ @Override public boolean isQueryCaching() { return cacheHelp.isQueryCaching(); } public boolean isManyPropCaching() { return isBeanCaching(); } /** * Return true if the persist request needs to notify the cache. */ public boolean isCacheNotify(PersistRequest.Type type, boolean publish) { if (draftable && !publish) { // no caching when editing draft beans return false; } return cacheHelp.isCacheNotify(type); } @Override public void clearBeanCache() { cacheHelp.beanCacheClear(); } /** * Clear the query cache. */ @Override public void clearQueryCache() { cacheHelp.queryCacheClear(); } /** * Get a query result from the query cache. */ public BeanCollection<T> queryCacheGet(Object id) { return cacheHelp.queryCacheGet(id); } /** * Put a query result into the query cache. */ public void queryCachePut(Object id, BeanCollection<T> query) { cacheHelp.queryCachePut(id, query); } /** * Add a query cache clear into the changeSet. */ public void queryCacheClear(CacheChangeSet changeSet) { cacheHelp.queryCacheClear(changeSet); } /** * Try to load the beanCollection from cache return true if successful. */ public boolean cacheManyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) { return cacheHelp.manyPropLoad(many, bc, parentId, readOnly); } /** * Put the beanCollection into the cache. */ public void cacheManyPropPut(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId) { cacheHelp.manyPropPut(many, bc, parentId); } /** * Update the bean collection entry in the cache. */ public void cacheManyPropPut(String name, Object parentId, CachedManyIds entry) { cacheHelp.cachePutManyIds(parentId, name, entry); } public void cacheManyPropRemove(String propertyName, Object parentId) { cacheHelp.manyPropRemove(propertyName, parentId); } public void cacheManyPropClear(String propertyName) { cacheHelp.manyPropClear(propertyName); } /** * Extract the raw cache data from the embedded bean. */ public CachedBeanData cacheEmbeddedBeanExtract(EntityBean bean) { return cacheHelp.beanExtractData(this, bean); } /** * Load the embedded bean (taking into account inheritance). */ public EntityBean cacheEmbeddedBeanLoad(CachedBeanData data, PersistenceContext context) { return cacheHelp.embeddedBeanLoad(data, context); } /** * Load the embedded bean as the root type. */ EntityBean cacheEmbeddedBeanLoadDirect(CachedBeanData data, PersistenceContext context) { return cacheHelp.embeddedBeanLoadDirect(data, context); } /** * Load the entity bean as the correct bean type. */ EntityBean cacheBeanLoadDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) { return cacheHelp.loadBeanDirect(id, readOnly, data, context); } /** * Put the bean into the cache. */ public void cacheBeanPut(T bean) { cacheBeanPut((EntityBean) bean); } /** * Put a bean into the bean cache (taking into account inheritance). */ public void cacheBeanPut(EntityBean bean) { cacheHelp.beanCachePut(bean); } /** * Put a bean into the cache as the correct type. */ void cacheBeanPutDirect(EntityBean bean) { cacheHelp.beanCachePutDirect(bean); } /** * Return a bean from the bean cache (or null). */ public T cacheBeanGet(Object id, Boolean readOnly, PersistenceContext context) { return cacheHelp.beanCacheGet(id, readOnly, context); } /** * Remove a bean from the cache given its Id. */ public void cacheHandleDeleteById(Object id) { cacheHelp.beanCacheRemove(id); } /** * Returns true if it managed to populate/load the bean from the cache. */ public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) { return cacheHelp.beanCacheLoad(bean, ebi, id, context); } /** * Returns true if it managed to populate/load the bean from the cache. */ public boolean cacheBeanLoad(EntityBeanIntercept ebi, PersistenceContext context) { EntityBean bean = ebi.getOwner(); Object id = getId(bean); return cacheBeanLoad(bean, ebi, id, context); } /** * Try to hit the cache using the natural key. */ public Object cacheNaturalKeyIdLookup(SpiQuery<T> query) { return cacheHelp.naturalKeyIdLookup(query); } public void cacheNaturalKeyPut(Object id, Object newKey) { cacheHelp.cacheNaturalKeyPut(id, newKey); } /** * Invalidate parts of cache due to SqlUpdate or external modification etc. */ public void cacheHandleBulkUpdate(TableIUD tableIUD) { cacheHelp.handleBulkUpdate(tableIUD); } /** * Handle a delete by id request adding an cache change into the changeSet. */ public void cacheHandleDeleteById(Object id, CacheChangeSet changeSet) { cacheHelp.handleDelete(id, changeSet); } /** * Remove a bean from the cache given its Id. */ public void cacheHandleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) { cacheHelp.handleDelete(id, deleteRequest, changeSet); } /** * Add the insert changes to the changeSet. */ public void cacheHandleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) { cacheHelp.handleInsert(insertRequest, changeSet); } /** * Add the update to the changeSet. */ public void cacheHandleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) { cacheHelp.handleUpdate(id, updateRequest, changeSet); } /** * Apply the update to the cache. */ public void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) { cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version); } /** * Prepare the read audit of a findFutureList() query. */ public void readAuditFutureList(SpiQuery<T> spiQuery) { if (isReadAuditing()) { ReadEvent event = new ReadEvent(fullName); // prepare in the foreground thread while we have the user context // information (query is processed/executed later in bg thread) readAuditPrepare(event); spiQuery.setFutureFetchAudit(event); } } /** * Write a bean read to the read audit log. */ public void readAuditBean(String queryKey, String bindLog, Object bean) { ReadEvent event = new ReadEvent(fullName, queryKey, bindLog, getIdForJson(bean)); readAuditPrepare(event); getReadAuditLogger().auditBean(event); } private void readAuditPrepare(ReadEvent event) { ReadAuditPrepare prepare = getReadAuditPrepare(); if (prepare != null) { prepare.prepare(event); } } /** * Write a many bean read to the read audit log. */ public void readAuditMany(String queryKey, String bindLog, List<Object> ids) { ReadEvent event = new ReadEvent(fullName, queryKey, bindLog, ids); readAuditPrepare(event); getReadAuditLogger().auditMany(event); } /** * Write a futureList many read to the read audit log. */ public void readAuditFutureMany(ReadEvent event) { // this has already been prepared (in foreground thread) getReadAuditLogger().auditMany(event); } /** * Return the base table alias. This is always the first letter of the bean name. */ public String getBaseTableAlias() { return baseTableAlias; } public void preAllocateIds(int batchSize) { if (idGenerator != null) { idGenerator.preAllocateIds(batchSize); } } public Object nextId(Transaction t) { if (idGenerator != null) { return idGenerator.nextId(t); } else { return null; } } public DeployPropertyParser createDeployPropertyParser() { return new DeployPropertyParser(this); } /** * Convert the logical orm update statement into sql by converting the bean * properties and bean name to database columns and table. */ public String convertOrmUpdateToSql(String ormUpdateStatement) { return new DeployUpdateParser(this).parse(ormUpdateStatement); } @Override public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) { return collectQueryPlanStatisticsInternal(reset, false); } @Override public List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) { return collectQueryPlanStatisticsInternal(reset, false); } public List<MetaQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) { List<MetaQueryPlanStatistic> list = new ArrayList<>(queryPlanCache.size()); for (CQueryPlan queryPlan : queryPlanCache.values()) { Snapshot snapshot = queryPlan.getSnapshot(reset); if (collectAll || snapshot.getExecutionCount() > 0) { list.add(snapshot); } } return list; } /** * Reset the statistics on all the query plans. */ public void clearQueryStatistics() { for (CQueryPlan queryPlan : queryPlanCache.values()) { queryPlan.resetStatistics(); } } /** * Execute the postLoad if a BeanPostLoad exists for this bean. */ public void postLoad(Object bean) { if (beanPostLoad != null) { beanPostLoad.postLoad(bean); } } public CQueryPlan getQueryPlan(CQueryPlanKey key) { return queryPlanCache.get(key); } public void putQueryPlan(CQueryPlanKey key, CQueryPlan plan) { queryPlanCache.put(key, plan); } /** * Get a UpdatePlan for a given hash. */ public SpiUpdatePlan getUpdatePlan(String key) { return updatePlanCache.get(key); } /** * Add a UpdatePlan to the cache with a given hash. */ public void putUpdatePlan(String key, SpiUpdatePlan plan) { updatePlanCache.put(key, plan); } /** * Return a Sql update statement to set the importedId value (deferred execution). */ public String getUpdateImportedIdSql(ImportedId prop) { return "update " + baseTable + " set " + prop.importedIdClause() + " where " + idBinder.getBindIdSql(null); } /** * Return true if updates should only include changed properties. Otherwise * all loaded properties are included in the update. */ public boolean isUpdateChangesOnly() { return updateChangesOnly; } /** * Return true if save does not recurse to other beans. That is return true if * there are no assoc one or assoc many beans that cascade save. */ public boolean isSaveRecurseSkippable() { return saveRecurseSkippable; } /** * Return true if delete does not recurse to other beans. That is return true * if there are no assoc one or assoc many beans that cascade delete. */ public boolean isDeleteRecurseSkippable() { return deleteRecurseSkippable; } /** * Return true if delete can use a single SQL statement. * <p> * This implies cascade delete does not continue depth wise and that this is no * associated L2 bean caching. */ public boolean isDeleteByStatement() { return deleteRecurseSkippable && !isBeanCaching(); } /** * Return the 'when modified' property if there is one defined. */ @Override public BeanProperty getWhenModifiedProperty() { return whenModifiedProperty; } /** * Return the 'when created' property if there is one defined. */ @Override public BeanProperty getWhenCreatedProperty() { return whenCreatedProperty; } /** * Find a property annotated with @WhenCreated or @CreatedTimestamp. */ private BeanProperty findWhenCreatedProperty() { for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) { if (aPropertiesBaseScalar.isGeneratedWhenCreated()) { return aPropertiesBaseScalar; } } return null; } /** * Find a property annotated with @WhenModified or @UpdatedTimestamp. */ private BeanProperty findWhenModifiedProperty() { for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) { if (aPropertiesBaseScalar.isGeneratedWhenModified()) { return aPropertiesBaseScalar; } } return null; } /** * Return the many property included in the query or null if one is not. */ public BeanPropertyAssocMany<?> getManyProperty(SpiQuery<?> query) { OrmQueryDetail detail = query.getDetail(); for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) { if (detail.includesPath(aPropertiesMany.getName())) { return aPropertiesMany; } } return null; } /** * Return a raw expression for 'where parent id in ...' clause. */ public String getParentIdInExpr(int parentIdSize, String rawWhere) { String inClause = idBinder.getIdInValueExpr(parentIdSize); return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause; } /** * Return the IdBinder which is helpful for handling the various types of Id. */ public IdBinder getIdBinder() { return idBinder; } /** * Return the sql for binding an id. This is the columns with table alias that * make up the id. */ public String getIdBinderIdSql() { return idBinderIdSql; } /** * Return the sql for binding id's using an IN clause. */ public String getIdBinderInLHSSql() { return idBinderInLHSSql; } /** * Bind the idValue to the preparedStatement. * <p> * This takes care of the various id types such as embedded beans etc. * </p> */ public void bindId(DataBind dataBind, Object idValue) throws SQLException { idBinder.bindId(dataBind, idValue); } /** * Return the id as an array of scalar bindable values. * <p> * This 'flattens' any EmbeddedId or multiple Id property cases. * </p> */ public Object[] getBindIdValues(Object idValue) { return idBinder.getBindValues(idValue); } @Override @SuppressWarnings("unchecked") public T createBean() { return (T) createEntityBean(true); } /** * Creates a new EntityBean. * The parameter <code>isNew</code> controls either this is a new bean (then * {@link BeanPostConstructListener#postCreate(Object)} will be invoked) or * a reference (then {@link BeanPostLoad#postLoad(Object)} will be invoked * on first access (lazy load) or immediately (eager load) */ public EntityBean createEntityBean(boolean isNew) { try { EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance(); if (beanPostConstructListener != null) { beanPostConstructListener.autowire(bean); // calls all registered listeners beanPostConstructListener.postConstruct(bean); // calls first the @PostConstruct method and then the listeners } if (unloadProperties.length > 0) { // 'unload' any properties initialised in the default constructor EntityBeanIntercept ebi = bean._ebean_getIntercept(); for (int unloadProperty : unloadProperties) { ebi.setPropertyUnloaded(unloadProperty); } } if (beanPostConstructListener != null && isNew) { beanPostConstructListener.postCreate(bean); // if bean is not new, postLoad will be executed later in the bean's lifecycle } return bean; } catch (Exception ex) { throw new PersistenceException(ex); } } /** * Creates a new entitybean without invoking {@link BeanPostConstructListener#postCreate(Object)} */ public EntityBean createEntityBean() { return createEntityBean(false); } /** * Create a reference bean based on the id. */ @SuppressWarnings("unchecked") public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) { if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) { CachedBeanData d = cacheHelp.beanCacheGetData(id); if (d != null) { Object shareableBean = d.getSharableBean(); if (shareableBean != null) { if (isReadAuditing()) { readAuditBean("ref", "", shareableBean); } return (T) shareableBean; } } } try { EntityBean eb = createEntityBean(); id = convertSetId(id, eb); EntityBeanIntercept ebi = eb._ebean_getIntercept(); if (disableLazyLoad) { ebi.setDisableLazyLoad(true); } else { ebi.setBeanLoader(ebeanServer); } ebi.setReference(idPropertyIndex); if (Boolean.TRUE == readOnly) { ebi.setReadOnly(true); } if (pc != null) { contextPut(pc, id, eb); ebi.setPersistenceContext(pc); } return (T) eb; } catch (Exception ex) { throw new PersistenceException(ex); } } /** * Return the bean property traversing the object graph and taking into * account inheritance. */ public BeanProperty getBeanPropertyFromPath(String path) { BeanDescriptor other = this; while (true) { String[] split = SplitName.splitBegin(path); if (split[1] == null) { return other._findBeanProperty(split[0]); } BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) other._findBeanProperty(split[0]); BeanDescriptor<?> targetDesc = assocProp.getTargetDescriptor(); path = split[1]; other = targetDesc; } } @Override public BeanType<?> getBeanTypeAtPath(String path) { return getBeanDescriptor(path); } /** * Return the BeanDescriptor for a given path of Associated One or Many beans. */ public BeanDescriptor<?> getBeanDescriptor(String path) { BeanDescriptor result = this; while (true) { if (path == null) { return result; } String[] splitBegin = SplitName.splitBegin(path); BeanProperty beanProperty = result.findBeanProperty(splitBegin[0]); if (beanProperty instanceof BeanPropertyAssoc<?>) { BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty; path = splitBegin[1]; result = assocProp.getTargetDescriptor(); } else { throw new PersistenceException("Invalid path " + path + " from " + result.getFullName()); } } } /** * Return the BeanDescriptor of another bean type. */ public <U> BeanDescriptor<U> getBeanDescriptor(Class<U> otherType) { return owner.getBeanDescriptor(otherType); } /** * Return the "shadow" property to support unidirectional relationships. * <p> * For bidirectional this is a real property on the bean. For unidirectional * relationships we have this 'shadow' property which is not externally * visible. * </p> */ public BeanPropertyAssocOne<?> getUnidirectional() { BeanDescriptor other = this; while (true) { if (other.unidirectional != null) { return other.unidirectional; } if (other.inheritInfo != null && !other.inheritInfo.isRoot()) { other = other.inheritInfo.getParent().desc(); continue; } return null; } } /** * Get a property value from a bean of this type. */ public Object getValue(EntityBean bean, String property) { return getBeanProperty(property).getValue(bean); } /** * Return true if this bean type should use IdGeneration. * <p> * If this is false and the Id is null it is assumed that a database auto * increment feature is being used to populate the id. * </p> */ public boolean isUseIdGenerator() { return idGenerator != null; } /** * Return bean class name. */ public String getDescriptorId() { return fullName; } /** * Return the class type this BeanDescriptor describes. */ @Override public Class<T> getBeanType() { return beanType; } /** * Return the bean class name this descriptor is used for. * <p> * If this BeanDescriptor is for a table then this returns the table name * instead. * </p> */ @Override public String getFullName() { return fullName; } /** * Return the short name of the entity bean. */ @Override public String getName() { return name; } /** * Summary description. */ @Override public String toString() { return fullName; } /** * Get the bean from the persistence context. */ public Object contextGet(PersistenceContext pc, Object id) { return pc.get(rootBeanType, id); } /** * Get the bean from the persistence context with delete check option. */ public PersistenceContext.WithOption contextGetWithOption(PersistenceContext pc, Object id) { return pc.getWithOption(rootBeanType, id); } /** * Put the bean into the persistence context. */ public void contextPut(PersistenceContext pc, Object id, Object bean) { pc.put(rootBeanType, id, bean); } /** * Put the bean into the persistence context if it is absent. */ public Object contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) { return pc.putIfAbsent(rootBeanType, id, localBean); } /** * Create a reference bean and put it in the persistence context (and return it). */ public Object contextRef(PersistenceContext pc, Boolean readOnly, boolean disableLazyLoad, Object id) { return createReference(readOnly, disableLazyLoad, id, pc); } /** * Clear a bean from the persistence context. */ public void contextClear(PersistenceContext pc, Object idValue) { pc.clear(rootBeanType, idValue); } /** * Delete a bean from the persistence context (such that we don't fetch it in the same transaction). */ public void contextDeleted(PersistenceContext pc, Object idValue) { pc.deleted(rootBeanType, idValue); } /** * Return the Id property name or null if no Id property exists. */ public String getIdName() { return (idProperty == null) ? null : idProperty.getName(); } /** * Helper method to return the unique property. If only one property makes up * the unique id then it's value is returned. If there is a concatenated * unique id then a Map is built with the keys being the names of the * properties that make up the unique id. */ public Object getId(EntityBean bean) { return (idProperty == null) ? null : idProperty.getValueIntercept(bean); } @Override public Object beanId(Object bean) { return getId((EntityBean) bean); } @Override public Object getBeanId(T bean) { return getId((EntityBean) bean); } /** * Return the Id value for the bean with embeddedId beans converted into maps. * <p> * The usage is to provide simple id types for JSON processing (for embeddedId's). * </p> */ public Object getIdForJson(Object bean) { return idBinder.getIdForJson((EntityBean) bean); } /** * Convert the idValue assuming embeddedId values are Maps. * <p> * The usage is to provide simple id types for JSON processing (for embeddedId's). * </p> */ public Object convertIdFromJson(Object idValue) { return idBinder.convertIdFromJson(idValue); } /** * Return the default order by that may need to be added if a many property is * included in the query. */ public String getDefaultOrderBy() { return idBinder.getDefaultOrderBy(); } /** * Convert the type of the idValue if required. */ public Object convertId(Object idValue) { return idBinder.convertId(idValue); } /** * Set the bean id value converting if necessary. */ @Override public void setBeanId(T bean, Object idValue) { idBinder.convertSetId(idValue, (EntityBean) bean); } /** * Convert and set the id value. * <p> * If the bean is not null, the id value is set to the id property of the bean * after it has been converted to the correct type. * </p> */ public Object convertSetId(Object idValue, EntityBean bean) { return idBinder.convertSetId(idValue, bean); } @Override public Property getProperty(String propName) { return findBeanProperty(propName); } /** * Get a BeanProperty by its name. */ public BeanProperty getBeanProperty(String propName) { return propMap.get(propName); } public void sort(List<T> list, String sortByClause) { ElComparator<T> comparator = getElComparator(sortByClause); list.sort(comparator); } public ElComparator<T> getElComparator(String propNameOrSortBy) { ElComparator<T> c = comparatorCache.computeIfAbsent(propNameOrSortBy, this::createComparator); return c; } /** * Register all the assoc many properties on this bean that are not populated with the load context. * <p> * This provides further lazy loading via the load context. * </p> */ public void lazyLoadRegister(String prefix, EntityBeanIntercept ebi, EntityBean bean, LoadContext loadContext) { // load the List/Set/Map proxy objects (deferred fetching of lists) BeanPropertyAssocMany<?>[] manys = propertiesMany(); for (BeanPropertyAssocMany<?> many : manys) { if (!ebi.isLoadedProperty(many.getPropertyIndex())) { BeanCollection<?> ref = many.createReferenceIfNull(bean); if (ref != null && !ref.isRegisteredWithLoadContext()) { String path = SplitName.add(prefix, many.getName()); loadContext.register(path, ref); } } } } /** * Return true if the lazy loading property is a Many in which case just * define a Reference for the collection and not invoke a query. */ public boolean lazyLoadMany(EntityBeanIntercept ebi) { int lazyLoadProperty = ebi.getLazyLoadPropertyIndex(); if (lazyLoadProperty == -1) { return false; } if (inheritInfo != null) { return descOf(ebi.getOwner().getClass()).lazyLoadMany(ebi, lazyLoadProperty); } return lazyLoadMany(ebi, lazyLoadProperty); } /** * Check for lazy loading of many property. */ private boolean lazyLoadMany(EntityBeanIntercept ebi, int lazyLoadProperty) { BeanProperty lazyLoadBeanProp = propertiesIndex[lazyLoadProperty]; if (lazyLoadBeanProp instanceof BeanPropertyAssocMany<?>) { BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) lazyLoadBeanProp; manyProp.createReference(ebi.getOwner()); ebi.setLoadedLazy(); return true; } return false; } /** * Return the correct BeanDescriptor based on the bean class type. */ BeanDescriptor<?> descOf(Class<?> type) { return inheritInfo.readType(type).desc(); } /** * Return a Comparator for local sorting of lists. * * @param sortByClause list of property names with optional ASC or DESC suffix. */ @SuppressWarnings("unchecked") private ElComparator<T> createComparator(String sortByClause) { SortByClause sortBy = SortByClauseParser.parse(sortByClause); if (sortBy.size() == 1) { // simple comparator for a single property return createPropertyComparator(sortBy.getProperties().get(0)); } // create a compound comparator based on the list of properties ElComparator<T>[] comparators = new ElComparator[sortBy.size()]; List<SortByClause.Property> sortProps = sortBy.getProperties(); for (int i = 0; i < sortProps.size(); i++) { SortByClause.Property sortProperty = sortProps.get(i); comparators[i] = createPropertyComparator(sortProperty); } return new ElComparatorCompound<>(comparators); } private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) { ElPropertyValue elGetValue = getElGetValue(sortProp.getName()); Boolean nullsHigh = sortProp.getNullsHigh(); if (nullsHigh == null) { nullsHigh = Boolean.TRUE; } return new ElComparatorProperty<>(elGetValue, sortProp.isAscending(), nullsHigh); } @Override public boolean isValidExpression(String propertyName) { try { return (getElGetValue(propertyName) != null); } catch (PersistenceException e) { return false; } } /** * Get an Expression language Value object. */ public ElPropertyValue getElGetValue(String propName) { ElPropertyValue elGetValue = elCache.get(propName); if (elGetValue != null) { return elGetValue; } elGetValue = buildElGetValue(propName, null, false); if (elGetValue != null) { elCache.put(propName, elGetValue); } return elGetValue; } @Override public ExpressionPath getExpressionPath(String path) { return getElGetValue(path); } /** * Similar to ElPropertyValue but also uses foreign key shortcuts. * <p> * The foreign key shortcuts means we can avoid unnecessary joins. * </p> */ public ElPropertyDeploy getElPropertyDeploy(String propName) { ElPropertyDeploy elProp = elDeployCache.get(propName); if (elProp != null) { return elProp; } if (!propName.contains(".")) { // No period means simple property and no need to look for // foreign key properties (in order to avoid an extra join) elProp = getElGetValue(propName); } else { elProp = buildElGetValue(propName, null, true); } if (elProp != null) { elDeployCache.put(propName, elProp); } return elProp; } protected ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, boolean propertyDeploy) { if (propertyDeploy && chain != null) { ElPropertyDeploy fk = elDeployCache.get(propName); if (fk instanceof BeanFkeyProperty) { // propertyDeploy chain for foreign key column return ((BeanFkeyProperty) fk).create(chain.getExpression(), chain.isContainsMany()); } } int basePos = propName.indexOf('.'); if (basePos > -1) { // nested or embedded property String baseName = propName.substring(0, basePos); String remainder = propName.substring(basePos + 1); BeanProperty assocProp = _findBeanProperty(baseName); if (assocProp == null) { return null; } return assocProp.buildElPropertyValue(propName, remainder, chain, propertyDeploy); } BeanProperty property = _findBeanProperty(propName); if (chain == null) { return property; } if (property == null) { throw new PersistenceException("No property found for [" + propName + "] in expression " + chain.getExpression()); } if (property.containsMany()) { chain.setContainsMany(); } return chain.add(property).build(); } /** * Return the property path given the db table and column. */ public String findBeanPath(String tableName, String columnName) { if (tableName.length() == 0 || tableName.equalsIgnoreCase(baseTable)) { return columnPath.get(columnName); } BeanPropertyAssoc<?> assocProperty = tablePath.get(tableName); if (assocProperty != null) { String relativePath = assocProperty.getTargetDescriptor().findBeanPath(tableName, columnName); if (relativePath != null) { return SplitName.add(assocProperty.getName(), relativePath); } } return null; } /** * Find a BeanProperty including searching the inheritance hierarchy. * <p> * This searches this BeanDescriptor and then searches further down the * inheritance tree (not up). * </p> */ public BeanProperty findBeanProperty(String propName) { int basePos = propName.indexOf('.'); if (basePos > -1) { // embedded property String baseName = propName.substring(0, basePos); return _findBeanProperty(baseName); } return _findBeanProperty(propName); } private BeanProperty _findBeanProperty(String propName) { BeanProperty prop = propMap.get(propName); if (prop == null && inheritInfo != null) { // search in sub types... return inheritInfo.findSubTypeProperty(propName); } return prop; } /** * Reset the many properties to empty state ready for reloading. */ public void resetManyProperties(Object dbBean) { EntityBean bean = (EntityBean) dbBean; for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) { if (aPropertiesMany.isCascadeRefresh()) { aPropertiesMany.resetMany(bean); } } } /** * Return the name of the server this BeanDescriptor belongs to. */ public String getServerName() { return serverName; } /** * Return true if this bean can cache sharable instances. * <p> * This means is has no relationships and has readOnly=true in its cache * options. * </p> */ public boolean isCacheSharableBeans() { return cacheSharableBeans; } /** * Return true if queries for beans of this type are auto tunable. */ public boolean isAutoTunable() { return autoTunable; } /** * Returns the Inheritance mapping information. This will be null if this type * of bean is not involved in any ORM inheritance mapping. */ public InheritInfo getInheritInfo() { return inheritInfo; } @Override public boolean hasInheritance() { return inheritInfo != null; } @Override public String getDiscColumn() { return inheritInfo.getDiscriminatorColumn(); } /** * Return the discriminator value for this bean type (or null when there is no inheritance). */ public String getDiscValue() { return inheritInfo == null ? null : inheritInfo.getDiscriminatorStringValue(); } @Override @SuppressWarnings("unchecked") public T createBeanUsingDisc(Object discValue) { return (T) inheritInfo.getType(discValue.toString()).desc().createBean(); } @Override public void addInheritanceWhere(SpiQuery<?> query) { if (inheritInfo != null && !inheritInfo.isRoot()) { query.where().eq(inheritInfo.getDiscriminatorColumn(), inheritInfo.getDiscriminatorValue()); } } /** * Return true if this is an embedded bean. */ public boolean isEmbedded() { return EntityType.EMBEDDED == entityType; } /** * Return the compound unique constraints. */ public IndexDefinition[] getIndexDefinitions() { return indexDefinitions; } /** * Return the beanListener. */ @Override public BeanPersistListener getPersistListener() { return persistListener; } /** * Return the beanFinder (Migrate over to getFindController). */ public BeanFindController getBeanFinder() { return beanFinder; } /** * Return the find controller (SPI interface). */ @Override public BeanFindController getFindController() { return beanFinder; } /** * Return the BeanQueryAdapter or null if none is defined. */ @Override public BeanQueryAdapter getQueryAdapter() { return queryAdapter; } /** * De-register the BeanPersistListener. */ public void deregister(BeanPersistListener listener) { // volatile read... BeanPersistListener currentListener = persistListener; if (currentListener != null) { if (currentListener instanceof ChainedBeanPersistListener) { // remove it from the existing chain persistListener = ((ChainedBeanPersistListener) currentListener).deregister(listener); } else if (currentListener.equals(listener)) { persistListener = null; } } } /** * De-register the BeanPersistController. */ public void deregister(BeanPersistController controller) { // volatile read... BeanPersistController currentController = persistController; if (currentController != null) { if (currentController instanceof ChainedBeanPersistController) { // remove it from the existing chain persistController = ((ChainedBeanPersistController) currentController).deregister(controller); } else if (currentController.equals(controller)) { persistController = null; } } } /** * Register the new BeanPersistController. */ public void register(BeanPersistListener newPersistListener) { if (newPersistListener.isRegisterFor(beanType)) { // volatile read... BeanPersistListener currentListener = persistListener; if (currentListener == null) { persistListener = newPersistListener; } else { if (currentListener instanceof ChainedBeanPersistListener) { // add it to the existing chain persistListener = ((ChainedBeanPersistListener) currentListener).register(newPersistListener); } else { // build new chain of the 2 persistListener = new ChainedBeanPersistListener(currentListener, newPersistListener); } } } } /** * Register the new BeanPersistController. */ public void register(BeanPersistController newController) { if (newController.isRegisterFor(beanType)) { // volatile read... BeanPersistController currentController = persistController; if (currentController == null) { persistController = newController; } else { if (currentController instanceof ChainedBeanPersistController) { // add it to the existing chain persistController = ((ChainedBeanPersistController) currentController).register(newController); } else { // build new chain of the 2 persistController = new ChainedBeanPersistController(currentController, newController); } } } } /** * Return the Controller. */ @Override public BeanPersistController getPersistController() { return persistController; } /** * Returns true if this bean is based on RawSql. */ public boolean isRawSqlBased() { return EntityType.SQL == entityType; } /** * Return the DB comment for the base table. */ public String getDbComment() { return dbComment; } /** * Return the dependent tables for a view based entity. * <p> * These tables * </p> */ public String[] getDependentTables() { return dependentTables; } /** * Return the base table. Only properties mapped to the base table are by * default persisted. */ @Override public String getBaseTable() { return baseTable; } /** * Return true if this type is a base table entity type. */ public boolean isBaseTable() { return baseTable != null && entityType == EntityType.ORM; } /** * Return the base table to use given the query temporal mode. */ public String getBaseTable(SpiQuery.TemporalMode mode) { switch (mode) { case DRAFT: return draftTable; case VERSIONS: return baseTableVersionsBetween; case AS_OF: return baseTableAsOf; default: return baseTable; } } /** * Return the associated draft table. */ public String getDraftTable() { return draftTable; } /** * Return true if read auditing is on this entity bean. */ public boolean isReadAuditing() { return readAuditing; } public boolean isSoftDelete() { return softDelete; } public void setSoftDeleteValue(EntityBean bean) { softDeleteProperty.setSoftDeleteValue(bean); } public String getSoftDeleteDbSet() { return softDeleteProperty.getSoftDeleteDbSet(); } public String getSoftDeletePredicate(String tableAlias) { return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias); } /** * Return true if this entity type is draftable. */ public boolean isDraftable() { return draftable; } /** * Return true if this entity type is a draftable element (child). */ public boolean isDraftableElement() { return draftableElement; } public void setUnmappedJson(EntityBean bean, Map<String, Object> unmappedProperties) { if (unmappedJson != null) { unmappedJson.setValueIntercept(bean, unmappedProperties); } } /** * Set the Tenant Id value to the bean. */ public void setTenantId(EntityBean entityBean, Object tenantId) { if (tenant != null) { tenant.setValue(entityBean, tenantId); } } /** * Set the draft to true for this entity bean instance. * This bean is being loaded via asDraft() query. */ public void setDraft(EntityBean entityBean) { if (draft != null) { draft.setValue(entityBean, true); } } /** * Return true if the bean is considered a 'draft' instance (not 'live'). */ public boolean isDraftInstance(EntityBean entityBean) { if (draft != null) { return Boolean.TRUE == draft.getValue(entityBean); } // no draft property - so return false return false; } /** * Return true if the bean is draftable and considered a 'live' instance. */ public boolean isLiveInstance(EntityBean entityBean) { if (draft != null) { return Boolean.FALSE == draft.getValue(entityBean); } // no draft property - so return false return false; } /** * If there is a @DraftDirty property set it's value on the bean. */ public void setDraftDirty(EntityBean entityBean, boolean value) { if (draftDirty != null) { // check to see if the dirty property has already // been set and if so do not set the value if (!entityBean._ebean_getIntercept().isChangedProperty(draftDirty.getPropertyIndex())) { draftDirty.setValueIntercept(entityBean, value); } } } /** * Optimise the draft query fetching any draftable element relationships. */ public void draftQueryOptimise(Query<T> query) { // use per query PersistenceContext to ensure fresh beans loaded query.setPersistenceContextScope(PersistenceContextScope.QUERY); draftHelp.draftQueryOptimise(query); } /** * Return true if this entity bean has history support. */ public boolean isHistorySupport() { return historySupport; } /** * Return the identity generation type. */ @Override public IdType getIdType() { return idType; } /** * Return true if the identity is the platform default (not explicitly set). */ public boolean isIdTypePlatformDefault() { return idTypePlatformDefault; } /** * Return the sequence name. */ @Override public String getSequenceName() { return sequenceName; } /** * Return the sequence initial value. */ public int getSequenceInitialValue() { return sequenceInitialValue; } /** * Return the sequence allocation size. */ public int getSequenceAllocationSize() { return sequenceAllocationSize; } /** * Return the SQL used to return the last inserted id. * <p> * This is only used with Identity columns and getGeneratedKeys is not * supported. * </p> */ public String getSelectLastInsertedId() { return selectLastInsertedId; } /** * Return the TableJoins. * <p> * For properties mapped to secondary tables rather than the base table. * </p> */ public TableJoin[] tableJoins() { return derivedTableJoins; } @Override public Collection<? extends Property> allProperties() { return propertiesAll(); } /** * Return a collection of all BeanProperty. This includes transient properties. */ public Collection<BeanProperty> propertiesAll() { return propMap.values(); } /** * Return the property that holds unmapped JSON content. */ public BeanProperty propertyUnmappedJson() { return unmappedJson; } /** * Return the non transient non id properties. */ public BeanProperty[] propertiesNonTransient() { return propertiesNonTransient; } /** * Return the transient properties. */ public BeanProperty[] propertiesTransient() { return propertiesTransient; } /** * Return the beans that are embedded. These share the base table with the * owner bean. */ public BeanPropertyAssocOne<?>[] propertiesEmbedded() { return propertiesEmbedded; } /** * Set the embedded owner on any embedded bean properties. */ public void setEmbeddedOwner(EntityBean bean) { for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) { aPropertiesEmbedded.setEmbeddedOwner(bean); } } @Override public BeanProperty getIdProperty() { return idProperty; } /** * Return true if this bean should be inserted rather than updated. * * @param ebi The entity bean intercept * @param insertMode true if the 'root request' was an insert rather than an update */ public boolean isInsertMode(EntityBeanIntercept ebi, boolean insertMode) { if (ebi.isLoaded()) { // must be an update as the bean is loaded return false; } if (idProperty.isEmbedded()) { // not using Id generator so just base on isLoaded() return !ebi.isLoaded(); } if (!hasIdValue(ebi.getOwner())) { // No Id property means it must be an insert return true; } // same as the 'root request' return insertMode; } public boolean isReference(EntityBeanIntercept ebi) { return ebi.isReference() || hasIdPropertyOnly(ebi); } public boolean hasIdPropertyOnly(EntityBeanIntercept ebi) { return ebi.hasIdOnly(idPropertyIndex); } public boolean hasIdValue(EntityBean bean) { return (idProperty != null && !DmlUtil.isNullOrZero(idProperty.getValue(bean))); } public boolean hasVersionProperty(EntityBeanIntercept ebi) { return versionPropertyIndex > -1 && ebi.isLoadedProperty(versionPropertyIndex); } /** * Set the version value returning it in primitive long form. */ @SuppressWarnings("unchecked") public long setVersion(EntityBean entityBean, Object versionValue) { versionProperty.setValueIntercept(entityBean, versionValue); return versionProperty.scalarType.asVersion(versionValue); } /** * Return the version value in primitive long form (if exists and set). */ @SuppressWarnings("unchecked") public long getVersion(EntityBean entityBean) { if (versionProperty == null) { return 0; } Object value = versionProperty.getValue(entityBean); return value == null ? 0 : versionProperty.scalarType.asVersion(value); } /** * Check for mutable scalar types and mark as dirty if necessary. */ public void checkMutableProperties(EntityBeanIntercept ebi) { for (BeanProperty beanProperty : propertiesMutable) { int propertyIndex = beanProperty.getPropertyIndex(); if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) { Object value = beanProperty.getValue(ebi.getOwner()); if (value == null || beanProperty.isDirtyValue(value)) { // mutable scalar value which is considered dirty so mark // it as such so that it is included in an update ebi.markPropertyAsChanged(propertyIndex); } } } } public ConcurrencyMode getConcurrencyMode(EntityBeanIntercept ebi) { if (!hasVersionProperty(ebi)) { return ConcurrencyMode.NONE; } else { return concurrencyMode; } } /** * Flatten the diff that comes from the entity bean intercept. */ Map<String, ValuePair> diffFlatten(Map<String, ValuePair> diff) { return DiffHelp.flatten(diff, this); } /** * Return a map of the differences between a and b. * <p> * A and B must be of the same type. B can be null, in which case the 'dirty * diff' of a is returned. * </p> * <p> * This intentionally does not include as OneToMany or ManyToMany properties. * </p> */ public Map<String, ValuePair> diffForInsert(EntityBean newBean) { Map<String, ValuePair> map = new LinkedHashMap<>(); diffForInsert(null, map, newBean); return map; } /** * Populate the diff for inserts with flattened non-null property values. */ public void diffForInsert(String prefix, Map<String, ValuePair> map, EntityBean newBean) { for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) { aPropertiesBaseScalar.diffForInsert(prefix, map, newBean); } for (BeanPropertyAssocOne<?> aPropertiesOne : propertiesOne) { aPropertiesOne.diffForInsert(prefix, map, newBean); } for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) { aPropertiesEmbedded.diffForInsert(prefix, map, newBean); } } /** * Return the diff comparing the bean values. */ public Map<String, ValuePair> diff(EntityBean newBean, EntityBean oldBean) { Map<String, ValuePair> map = new LinkedHashMap<>(); diff(null, map, newBean, oldBean); return map; } /** * Populate the diff for updates with flattened non-null property values. */ public void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, EntityBean oldBean) { for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) { aPropertiesBaseScalar.diff(prefix, map, newBean, oldBean); } for (BeanPropertyAssocOne<?> aPropertiesOne : propertiesOne) { aPropertiesOne.diff(prefix, map, newBean, oldBean); } for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) { aPropertiesEmbedded.diff(prefix, map, newBean, oldBean); } } /** * Appends the Id property to the OrderBy clause if it is not believed * to be already contained in the order by. * <p> * This is primarily used for paging queries to ensure that an order by clause is provided and that the order by * provides unique ordering of the rows (so that the paging is predicable). * </p> */ public void appendOrderById(SpiQuery<T> query) { if (idProperty != null && !idProperty.isEmbedded()) { OrderBy<T> orderBy = query.getOrderBy(); if (orderBy == null || orderBy.isEmpty()) { RawSql rawSql = query.getRawSql(); if (rawSql != null) { query.order(rawSql.getSql().getOrderBy()); } query.order().asc(idProperty.getName()); } else if (!orderBy.containsProperty(idProperty.getName())) { query.order().asc(idProperty.getName()); } } } /** * All the BeanPropertyAssocOne that are not embedded. These are effectively * joined beans. For ManyToOne and OneToOne associations. */ public BeanPropertyAssocOne<?>[] propertiesOne() { return propertiesOne; } /** * Returns ManyToOnes and OneToOnes on the imported owning side. * <p> * Excludes OneToOnes on the exported side. * </p> */ public BeanPropertyAssocOne<?>[] propertiesOneImported() { return propertiesOneImported; } /** * Imported Assoc Ones with cascade save true. */ public BeanPropertyAssocOne<?>[] propertiesOneImportedSave() { return propertiesOneImportedSave; } /** * Imported Assoc Ones with cascade delete true. */ public BeanPropertyAssocOne<?>[] propertiesOneImportedDelete() { return propertiesOneImportedDelete; } /** * Exported assoc ones with cascade save. */ public BeanPropertyAssocOne<?>[] propertiesOneExportedSave() { return propertiesOneExportedSave; } /** * Exported assoc ones with delete cascade. */ public BeanPropertyAssocOne<?>[] propertiesOneExportedDelete() { return propertiesOneExportedDelete; } /** * All Non Assoc Many's for this descriptor. */ public BeanProperty[] propertiesNonMany() { return propertiesNonMany; } /** * All Assoc Many's for this descriptor. */ public BeanPropertyAssocMany<?>[] propertiesMany() { return propertiesMany; } /** * Assoc Many's with save cascade. */ public BeanPropertyAssocMany<?>[] propertiesManySave() { return propertiesManySave; } /** * Assoc Many's with delete cascade. */ public BeanPropertyAssocMany<?>[] propertiesManyDelete() { return propertiesManyDelete; } /** * Assoc ManyToMany's. */ public BeanPropertyAssocMany<?>[] propertiesManyToMany() { return propertiesManyToMany; } /** * Return the first version property that exists on the bean. Returns null if * no version property exists on the bean. * <p> * Note that this DOES NOT find a version property on an embedded bean. * </p> */ public BeanProperty getVersionProperty() { return versionProperty; } /** * Scalar properties without the unique id or secondary table properties. */ public BeanProperty[] propertiesBaseScalar() { return propertiesBaseScalar; } /** * Return the properties local to this type for inheritance. */ public BeanProperty[] propertiesLocal() { return propertiesLocal; } public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps); } protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps); } public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { jsonHelp.jsonWrite(writeJson, bean, null); } public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException { jsonHelp.jsonWrite(writeJson, bean, key); } protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException { jsonHelp.jsonWriteProperties(writeJson, bean); } public T jsonRead(ReadJson jsonRead, String path) throws IOException { return jsonHelp.jsonRead(jsonRead, path); } protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException { return jsonHelp.jsonReadObject(jsonRead, path); } }