mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#919 - io.ebean package initial
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Local interface to handle Embedded, Reference and Reference Exported
|
||||
* cases.
|
||||
*/
|
||||
abstract class AssocOneHelp {
|
||||
|
||||
protected final BeanPropertyAssocOne<?> property;
|
||||
|
||||
protected final BeanDescriptor<?> target;
|
||||
|
||||
AssocOneHelp(BeanPropertyAssocOne<?> property) {
|
||||
this.property = property;
|
||||
this.target = property.targetDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectively skip reading (the jdbc resultSet as already in the persistence context etc).
|
||||
*/
|
||||
void loadIgnore(DbReadContext ctx) {
|
||||
property.targetIdBinder.loadIgnore(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the bean.
|
||||
*/
|
||||
Object read(DbReadContext ctx) throws SQLException {
|
||||
|
||||
// Support for Inheritance hierarchy on exported OneToOne ?
|
||||
Object id = property.targetIdBinder.read(ctx);
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistenceContext pc = ctx.getPersistenceContext();
|
||||
Object existing = target.contextGet(pc, id);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
boolean disableLazyLoading = ctx.isDisableLazyLoading();
|
||||
Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
|
||||
if (!disableLazyLoading) {
|
||||
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read setting values into the bean.
|
||||
*/
|
||||
Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
Object val = read(ctx);
|
||||
if (bean != null) {
|
||||
property.setValue(bean, val);
|
||||
ctx.propagateState(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to the select clause.
|
||||
*/
|
||||
abstract void appendSelect(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
/**
|
||||
* Append to the from clause.
|
||||
*/
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
// nothing required here
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Helper for Embedded BeanPropertyAssocOne.
|
||||
*/
|
||||
final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
|
||||
public AssocOneHelpEmbedded(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
void loadIgnore(DbReadContext ctx) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].loadIgnore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
Object dbVal = read(ctx);
|
||||
if (bean != null) {
|
||||
// set back to the parent bean
|
||||
property.setValue(bean, dbVal);
|
||||
ctx.propagateState(dbVal);
|
||||
return dbVal;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Object read(DbReadContext ctx) throws SQLException {
|
||||
|
||||
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
|
||||
|
||||
boolean notNull = false;
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
Object value = property.embeddedProps[i].readSet(ctx, embeddedBean);
|
||||
if (value != null) {
|
||||
notNull = true;
|
||||
}
|
||||
}
|
||||
if (notNull) {
|
||||
ctx.propagateState(embeddedBean);
|
||||
return embeddedBean;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Helper for BeanPropertyAssocOne for OneToOne exported reference - not so common.
|
||||
*/
|
||||
class AssocOneHelpRefExported extends AssocOneHelp {
|
||||
|
||||
public AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append columns for foreign key columns.
|
||||
*/
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
|
||||
// set appropriate tableAlias for the exported id columns
|
||||
String relativePrefix = ctx.getRelativePrefix(property.getName());
|
||||
ctx.pushTableAlias(relativePrefix);
|
||||
property.targetIdBinder.appendSelect(ctx, subQuery);
|
||||
ctx.popTableAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
String relativePrefix = ctx.getRelativePrefix(property.getName());
|
||||
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Helper for BeanPropertyAssocOne imported reference but with inheritance.
|
||||
*/
|
||||
class AssocOneHelpRefInherit extends AssocOneHelp {
|
||||
|
||||
private final InheritInfo inherit;
|
||||
|
||||
AssocOneHelpRefInherit(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
this.inherit = property.targetInheritInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadIgnore(DbReadContext ctx) {
|
||||
property.targetIdBinder.loadIgnore(ctx);
|
||||
ctx.getDataReader().incrementPos(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and set a Reference bean.
|
||||
*/
|
||||
@Override
|
||||
Object read(DbReadContext ctx) throws SQLException {
|
||||
|
||||
// read discriminator to determine the type
|
||||
InheritInfo rowInheritInfo = inherit.readType(ctx);
|
||||
if (rowInheritInfo == null) {
|
||||
// ignore the id property
|
||||
property.targetIdBinder.loadIgnore(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
Object id = property.targetIdBinder.read(ctx);
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check transaction context to see if it already exists
|
||||
PersistenceContext pc = ctx.getPersistenceContext();
|
||||
BeanDescriptor<?> desc = rowInheritInfo.desc();
|
||||
Object existing = desc.contextGet(pc, id);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
// for inheritance hierarchy create the correct type for this row...
|
||||
boolean disableLazyLoading = ctx.isDisableLazyLoading();
|
||||
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
|
||||
if (disableLazyLoading) {
|
||||
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
// add join to support the discriminator column
|
||||
String relativePrefix = ctx.getRelativePrefix(property.name);
|
||||
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append columns for foreign key columns.
|
||||
*/
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
|
||||
if (!subQuery) {
|
||||
// add discriminator column
|
||||
String relativePrefix = ctx.getRelativePrefix(property.getName());
|
||||
String tableAlias = ctx.getTableAlias(relativePrefix);
|
||||
ctx.appendColumn(tableAlias, property.targetInheritInfo.getDiscriminatorColumn());
|
||||
}
|
||||
property.importedId.sqlAppend(ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
/**
|
||||
* Helper for BeanPropertyAssocOne imported reference - this is the common case.
|
||||
*/
|
||||
class AssocOneHelpRefSimple extends AssocOneHelp {
|
||||
|
||||
AssocOneHelpRefSimple(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append columns for foreign key columns.
|
||||
*/
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
property.importedId.sqlAppend(ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
|
||||
/**
|
||||
* Persist info for determining if save or delete should be performed.
|
||||
* <p>
|
||||
* This is set to associated Beans, Table joins and List.
|
||||
* </p>
|
||||
*/
|
||||
public class BeanCascadeInfo {
|
||||
|
||||
private boolean delete;
|
||||
|
||||
private boolean save;
|
||||
|
||||
private boolean refresh;
|
||||
|
||||
public void setTypes(CascadeType[] types) {
|
||||
for (CascadeType type : types) {
|
||||
setType(type);
|
||||
}
|
||||
}
|
||||
|
||||
private void setType(CascadeType type) {
|
||||
switch (type) {
|
||||
case ALL:
|
||||
save = true;
|
||||
delete = true;
|
||||
refresh = true;
|
||||
break;
|
||||
case REMOVE:
|
||||
delete = true;
|
||||
break;
|
||||
case REFRESH:
|
||||
refresh = true;
|
||||
break;
|
||||
case PERSIST:
|
||||
save = true;
|
||||
break;
|
||||
case MERGE:
|
||||
save = true;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected CascadeType " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if refresh should cascade.
|
||||
*/
|
||||
public boolean isRefresh() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if delete should cascade.
|
||||
*/
|
||||
public boolean isDelete() {
|
||||
return delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if delete should cascade.
|
||||
*/
|
||||
public void setDelete(boolean delete) {
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if save should cascade.
|
||||
*/
|
||||
public boolean isSave() {
|
||||
return save;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cascade save and delete settings.
|
||||
*/
|
||||
public void setSaveDelete(boolean save, boolean delete) {
|
||||
this.save = save;
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Helper functions for performing tasks on Lists Sets or Maps.
|
||||
*/
|
||||
public interface BeanCollectionHelp<T> {
|
||||
|
||||
/**
|
||||
* Set the EbeanServer that owns the configuration.
|
||||
*/
|
||||
void setLoader(BeanCollectionLoader loader);
|
||||
|
||||
/**
|
||||
* Return the mechanism to add beans to the underlying collection.
|
||||
* <p>
|
||||
* For Map's this needs to take the mapKey.
|
||||
* </p>
|
||||
*/
|
||||
BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
|
||||
|
||||
/**
|
||||
* Create an empty collection of the correct type without a parent bean.
|
||||
*/
|
||||
BeanCollection<T> createEmptyNoParent();
|
||||
|
||||
/**
|
||||
* Create an empty collection of the correct type.
|
||||
*/
|
||||
BeanCollection<T> createEmpty(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Add a bean to the List Set or Map.
|
||||
*/
|
||||
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
|
||||
|
||||
/**
|
||||
* Create a lazy loading proxy for a List Set or Map.
|
||||
*/
|
||||
BeanCollection<T> createReference(EntityBean parentBean);
|
||||
|
||||
/**
|
||||
* Refresh the List Set or Map.
|
||||
*/
|
||||
void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean);
|
||||
|
||||
/**
|
||||
* Apply the new refreshed BeanCollection to the appropriate property of the parent bean.
|
||||
*/
|
||||
void refresh(BeanCollection<?> bc, EntityBean parentBean);
|
||||
|
||||
/**
|
||||
* Write the collection out as json.
|
||||
*/
|
||||
void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Creates Helpers specific to the type of the property (List Set or Map).
|
||||
*/
|
||||
public class BeanCollectionHelpFactory {
|
||||
|
||||
static final BeanListHelp LIST_HELP = new BeanListHelp();
|
||||
|
||||
static final BeanSetHelp SET_HELP = new BeanSetHelp();
|
||||
|
||||
/**
|
||||
* Create the helper based on the many property.
|
||||
*/
|
||||
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> manyProperty) {
|
||||
|
||||
ManyType manyType = manyProperty.getManyType();
|
||||
switch (manyType) {
|
||||
case LIST:
|
||||
return new BeanListHelp<>(manyProperty);
|
||||
case SET:
|
||||
return new BeanSetHelp<>(manyProperty);
|
||||
case MAP:
|
||||
return new BeanMapHelp<>(manyProperty);
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + manyType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> BeanCollectionHelp<T> create(OrmQueryRequest<T> request) {
|
||||
|
||||
SpiQuery.Type manyType = request.getQuery().getType();
|
||||
|
||||
if (manyType.equals(SpiQuery.Type.LIST)) {
|
||||
return LIST_HELP;
|
||||
|
||||
} else if (manyType.equals(SpiQuery.Type.SET)) {
|
||||
return SET_HELP;
|
||||
|
||||
} else {
|
||||
BeanDescriptor<T> target = request.getBeanDescriptor();
|
||||
String mapKey = request.getQuery().getMapKey();
|
||||
return new BeanMapHelp<>(target, mapKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
|
||||
/**
|
||||
* Utility methods for BeanCollections.
|
||||
*/
|
||||
public class BeanCollectionUtil {
|
||||
|
||||
/**
|
||||
* Return the details of the collection or map taking care to avoid
|
||||
* unnecessary fetching of the data.
|
||||
*/
|
||||
public static Collection<?> getActualEntries(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
if (o instanceof BeanCollection<?>) {
|
||||
BeanCollection<?> bc = (BeanCollection<?>) o;
|
||||
if (!bc.isPopulated()) {
|
||||
return null;
|
||||
}
|
||||
// For maps this is a collection of Map.Entry, otherwise it
|
||||
// returns a collection of beans
|
||||
return bc.getActualEntries();
|
||||
}
|
||||
|
||||
if (o instanceof Map<?, ?>) {
|
||||
// yes, we want the entrySet (to set the keys)
|
||||
return ((Map<?, ?>) o).entrySet();
|
||||
|
||||
} else if (o instanceof Collection<?>) {
|
||||
return ((Collection<?>) o);
|
||||
}
|
||||
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,720 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
import io.ebeaninternal.server.cache.CachedBeanDataFromBean;
|
||||
import io.ebeaninternal.server.cache.CachedBeanDataToBean;
|
||||
import io.ebeaninternal.server.cache.CachedManyIds;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages the bean, query and collection caches.
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorCacheHelp.class);
|
||||
|
||||
private static final Logger queryLog = LoggerFactory.getLogger("org.avaje.ebean.cache.QUERY");
|
||||
private static final Logger beanLog = LoggerFactory.getLogger("org.avaje.ebean.cache.BEAN");
|
||||
private static final Logger manyLog = LoggerFactory.getLogger("org.avaje.ebean.cache.COLL");
|
||||
private static final Logger natLog = LoggerFactory.getLogger("org.avaje.ebean.cache.NATKEY");
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
private final SpiCacheManager cacheManager;
|
||||
|
||||
private final CacheOptions cacheOptions;
|
||||
|
||||
/**
|
||||
* Flag indicating this bean has no relationships.
|
||||
*/
|
||||
private final boolean cacheSharableBeans;
|
||||
|
||||
private final Class<?> beanType;
|
||||
|
||||
private final String cacheName;
|
||||
|
||||
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
|
||||
private final String naturalKeyProperty;
|
||||
|
||||
private final Supplier<ServerCache> beanCache;
|
||||
private final Supplier<ServerCache> naturalKeyCache;
|
||||
private final Supplier<ServerCache> queryCache;
|
||||
|
||||
/**
|
||||
* Set to true if all persist changes need to notify the cache.
|
||||
*/
|
||||
private boolean cacheNotifyOnAll;
|
||||
|
||||
/**
|
||||
* Set to true if delete changes need to notify cache.
|
||||
*/
|
||||
private boolean cacheNotifyOnDelete;
|
||||
|
||||
BeanDescriptorCacheHelp(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
|
||||
this.desc = desc;
|
||||
this.beanType = desc.rootBeanType;
|
||||
this.cacheName = beanType.getSimpleName();
|
||||
this.cacheManager = cacheManager;
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKeyProperty = cacheOptions.getNaturalKey();
|
||||
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
} else {
|
||||
this.queryCache = cacheManager.getQueryCache(beanType);
|
||||
}
|
||||
|
||||
if (cacheOptions.isEnableBeanCache()) {
|
||||
this.beanCache = cacheManager.getBeanCache(beanType);
|
||||
if (cacheOptions.getNaturalKey() != null) {
|
||||
this.naturalKeyCache = cacheManager.getNaturalKeyCache(beanType);
|
||||
} else {
|
||||
this.naturalKeyCache = null;
|
||||
}
|
||||
} else {
|
||||
this.beanCache = null;
|
||||
this.naturalKeyCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the cache notify flags.
|
||||
*/
|
||||
void deriveNotifyFlags() {
|
||||
cacheNotifyOnAll = (beanCache != null || queryCache != null);
|
||||
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (isBeanCaching() || isQueryCaching() || cacheNotifyOnAll || cacheNotifyOnDelete) {
|
||||
String notifyMode = cacheNotifyOnAll ? "All" : (cacheNotifyOnDelete ? "Delete" : "None");
|
||||
logger.debug("l2 caching on {} - beanCaching:{} queryCaching:{} notifyMode:{} ",
|
||||
desc.getFullName(), isBeanCaching(), isQueryCaching(), notifyMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an imported bi-directional relationship to a bea
|
||||
* that does have bean caching enabled.
|
||||
*/
|
||||
private boolean isNotifyOnDeletes() {
|
||||
for (BeanPropertyAssocOne<?> aPropertiesOneImported : propertiesOneImported) {
|
||||
if (aPropertiesOneImported.isCacheNotify()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the persist request needs to notify the cache.
|
||||
*/
|
||||
boolean isCacheNotify(PersistRequest.Type type) {
|
||||
return cacheNotifyOnAll
|
||||
|| cacheNotifyOnDelete && (type == PersistRequest.Type.DELETE || type == PersistRequest.Type.DELETE_PERMANENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is currently query caching for this type of bean.
|
||||
*/
|
||||
boolean isQueryCaching() {
|
||||
return queryCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is currently bean caching for this type of bean.
|
||||
*/
|
||||
boolean isBeanCaching() {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
CacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the query cache.
|
||||
*/
|
||||
void queryCacheClear() {
|
||||
if (queryCache != null) {
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" CLEAR {}", cacheName);
|
||||
}
|
||||
queryCache.get().clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query cache clear to the changeSet.
|
||||
*/
|
||||
void queryCacheClear(CacheChangeSet changeSet) {
|
||||
if (queryCache != null) {
|
||||
changeSet.addClearQuery(desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query result from the query cache.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
BeanCollection<T> queryCacheGet(Object id) {
|
||||
if (queryCache == null) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
BeanCollection<T> list = (BeanCollection<T>) queryCache.get().get(id);
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
if (list == null) {
|
||||
queryLog.debug(" GET {}({}) - cache miss", cacheName, id);
|
||||
} else {
|
||||
queryLog.debug(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query result into the query cache.
|
||||
*/
|
||||
void queryCachePut(Object id, BeanCollection<T> query) {
|
||||
if (queryCache == null) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" PUT {}({})", cacheName, id);
|
||||
}
|
||||
queryCache.get().put(id, query);
|
||||
}
|
||||
|
||||
|
||||
void manyPropRemove(String propertyName, Object parentId) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
|
||||
if (manyLog.isTraceEnabled()) {
|
||||
manyLog.trace(" REMOVE {}({}).{}", cacheName, parentId, propertyName);
|
||||
}
|
||||
collectionIdsCache.remove(parentId);
|
||||
}
|
||||
|
||||
void manyPropClear(String propertyName) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" CLEAR {}(*).{} ", cacheName, propertyName);
|
||||
}
|
||||
collectionIdsCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CachedManyIds for a given bean many property. Returns null if not in the cache.
|
||||
*/
|
||||
private CachedManyIds manyPropGet(Object parentId, String propertyName) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
|
||||
CachedManyIds entry = (CachedManyIds) collectionIdsCache.get(parentId);
|
||||
if (entry == null) {
|
||||
if (manyLog.isTraceEnabled()) {
|
||||
manyLog.trace(" GET {}({}).{} - cache miss", cacheName, parentId, propertyName);
|
||||
}
|
||||
} else if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" GET {}({}).{} - hit", cacheName, parentId, propertyName);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load the bean collection from cache return true if successful.
|
||||
*/
|
||||
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
|
||||
|
||||
CachedManyIds entry = manyPropGet(parentId, many.getName());
|
||||
if (entry == null) {
|
||||
// not in cache so return unsuccessful
|
||||
return false;
|
||||
}
|
||||
|
||||
Object ownerBean = bc.getOwnerBean();
|
||||
EntityBeanIntercept ebi = ((EntityBean) ownerBean)._ebean_getIntercept();
|
||||
PersistenceContext persistenceContext = ebi.getPersistenceContext();
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
|
||||
List<Object> idList = entry.getIdList();
|
||||
bc.checkEmptyLazyLoad();
|
||||
for (Object id : idList) {
|
||||
Object refBean = targetDescriptor.createReference(readOnly, false, id, persistenceContext);
|
||||
many.add(bc, (EntityBean) refBean);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the beanCollection into the cache.
|
||||
*/
|
||||
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
|
||||
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
cachePutManyIds(parentId, many.getName(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) {
|
||||
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName).get();
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry);
|
||||
}
|
||||
collectionIdsCache.put(parentId, entry);
|
||||
}
|
||||
|
||||
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
|
||||
List<Object> idList = new ArrayList<>();
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
|
||||
if (actualDetails == null) {
|
||||
return null;
|
||||
}
|
||||
for (Object bean : actualDetails) {
|
||||
idList.add(targetDescriptor.getId((EntityBean) bean));
|
||||
}
|
||||
return new CachedManyIds(idList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bean using the natural key lookup if available.
|
||||
*/
|
||||
Object naturalKeyIdLookup(SpiQuery<T> query) {
|
||||
|
||||
if (!isNaturalKeyCaching(query.isUseBeanCache())) {
|
||||
// no natural key caching for this query
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if it is a find by unique id (using the natural key)
|
||||
NaturalKeyBindParam keyBindParam = query.getNaturalKeyBindParam();
|
||||
if (keyBindParam == null || !isNaturalKey(keyBindParam.getName())) {
|
||||
// query is not appropriate
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to lookup the id using the natural key
|
||||
Object id = naturalKeyCache.get().get(keyBindParam.getValue());
|
||||
if (natLog.isTraceEnabled()) {
|
||||
natLog.trace(" LOOKUP {}({}) - id:{}", cacheName, keyBindParam.getValue(), id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean isNaturalKeyCaching(Boolean queryUseCache) {
|
||||
return naturalKeyCache != null && (queryUseCache == null || queryUseCache);
|
||||
}
|
||||
|
||||
private boolean isNaturalKey(String propName) {
|
||||
return propName != null && propName.equals(cacheOptions.getNaturalKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* For a bean built from the cache this sets up its persistence context for future lazy loading etc.
|
||||
*/
|
||||
private void setupContext(Object bean, PersistenceContext context) {
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
// Not using a loadContext for beans coming out of L2 cache
|
||||
// so that means no batch lazy loading for these beans
|
||||
EntityBean entityBean = (EntityBean) bean;
|
||||
EntityBeanIntercept ebi = entityBean._ebean_getIntercept();
|
||||
ebi.setPersistenceContext(context);
|
||||
Object id = desc.getId(entityBean);
|
||||
desc.contextPut(context, id, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the beanCache creating it if necessary.
|
||||
*/
|
||||
private ServerCache getBeanCache() {
|
||||
if (beanCache == null) {
|
||||
throw new IllegalStateException("No bean cache enabled for " + desc + ". Add the @Cache annotation.");
|
||||
}
|
||||
return beanCache.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the bean cache.
|
||||
*/
|
||||
void beanCacheClear() {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" CLEAR {}", cacheName);
|
||||
}
|
||||
beanCache.get().clear();
|
||||
}
|
||||
}
|
||||
|
||||
CachedBeanData beanExtractData(BeanDescriptor<?> targetDesc, EntityBean bean) {
|
||||
return CachedBeanDataFromBean.extract(targetDesc, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
void beanCachePut(EntityBean bean) {
|
||||
|
||||
if (desc.inheritInfo != null) {
|
||||
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
|
||||
} else {
|
||||
beanCachePutDirect(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, id, beanData);
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
|
||||
if (naturalKeyProperty != null) {
|
||||
Object naturalKey = beanData.getData(naturalKeyProperty);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
}
|
||||
naturalKeyCache.get().put(naturalKey, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
}
|
||||
|
||||
T beanCacheGet(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
T bean = beanCacheGetInternal(id, readOnly, context);
|
||||
if (bean != null) {
|
||||
setupContext(bean, context);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a bean from the bean cache.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private T beanCacheGetInternal(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(id);
|
||||
if (data == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
|
||||
Object bean = data.getSharableBean();
|
||||
if (bean != null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit shared bean", cacheName, id);
|
||||
}
|
||||
if (desc.isReadAuditing()) {
|
||||
desc.readAuditBean("l2", "", bean);
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
}
|
||||
|
||||
return (T) loadBean(id, readOnly, data, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the entity bean taking into account inheritance.
|
||||
*/
|
||||
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return loadBeanDirect(id, readOnly, data, context);
|
||||
} else {
|
||||
return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root BeanDescriptor for inheritance.
|
||||
*/
|
||||
private BeanDescriptor<?> rootDescriptor(String discValue) {
|
||||
return desc.inheritInfo.readType(discValue).desc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the entity bean from cache data given this is the root bean type.
|
||||
*/
|
||||
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
id = desc.convertSetId(id, bean);
|
||||
CachedBeanDataToBean.load(desc, bean, data, context);
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
// Not using a loadContext for beans coming out of L2 cache
|
||||
// so that means no batch lazy loading for these beans
|
||||
ebi.setBeanLoader(desc.getEbeanServer());
|
||||
if (Boolean.TRUE.equals(readOnly)) {
|
||||
ebi.setReadOnly(true);
|
||||
}
|
||||
ebi.setPersistenceContext(context);
|
||||
desc.contextPut(context, id, bean);
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
if (desc.isReadAuditing()) {
|
||||
desc.readAuditBean("l2", "", bean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the embedded bean checking for inheritance.
|
||||
*/
|
||||
EntityBean embeddedBeanLoad(CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return embeddedBeanLoadDirect(data, context);
|
||||
} else {
|
||||
return rootDescriptor(discValue).cacheEmbeddedBeanLoadDirect(data, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the embedded bean given this is the bean type.
|
||||
*/
|
||||
EntityBean embeddedBeanLoadDirect(CachedBeanData data, PersistenceContext context) {
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
CachedBeanDataToBean.load(desc, bean, data, context);
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
void beanCacheRemove(Object id) {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, id);
|
||||
}
|
||||
beanCache.get().remove(id);
|
||||
}
|
||||
for (BeanPropertyAssocOne<?> aPropertiesOneImported : propertiesOneImported) {
|
||||
aPropertiesOneImported.cacheClear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id);
|
||||
if (cacheData == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(ebi.getLazyLoadProperty())) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, id, ebi.getLazyLoadProperty());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, cacheData, context);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate cache changes to support delete by id.
|
||||
*/
|
||||
void handleDelete(Object id, CacheChangeSet changeSet) {
|
||||
if (beanCache != null) {
|
||||
changeSet.addBeanRemove(desc, id);
|
||||
}
|
||||
cacheDeleteImported(true, null, changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate cache changes to support delete bean.
|
||||
*/
|
||||
void handleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
changeSet.addBeanRemove(desc, id);
|
||||
}
|
||||
cacheDeleteImported(true, deleteRequest.getEntityBean(), changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate cache changes to support insert.
|
||||
*/
|
||||
void handleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
|
||||
queryCacheClear(changeSet);
|
||||
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
|
||||
changeSet.addBeanInsert(desc.getBaseTable());
|
||||
}
|
||||
|
||||
private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) {
|
||||
for (BeanPropertyAssocOne<?> aPropertiesOneImported : propertiesOneImported) {
|
||||
aPropertiesOneImported.cacheDelete(clear, entityBean, changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate changes to support update.
|
||||
*/
|
||||
void handleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
|
||||
|
||||
queryCacheClear(changeSet);
|
||||
|
||||
if (beanCache == null) {
|
||||
// query caching only
|
||||
return;
|
||||
}
|
||||
|
||||
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
|
||||
if (manyCollections != null) {
|
||||
for (BeanPropertyAssocMany<?> many : manyCollections) {
|
||||
Object details = many.getValue(updateRequest.getEntityBean());
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
changeSet.addManyPut(desc, many.getName(), id, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the bean itself was updated
|
||||
if (!updateRequest.isUpdatedManysOnly()) {
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
EntityBean bean = updateRequest.getEntityBean();
|
||||
boolean[] dirtyProperties = updateRequest.getDirtyProperties();
|
||||
for (int i = 0; i < dirtyProperties.length; i++) {
|
||||
if (dirtyProperties[i]) {
|
||||
BeanProperty property = desc.propertiesIndex[i];
|
||||
if (property.isCacheDataInclude()) {
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(desc, id, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate parts of cache due to SqlUpdate or external modification etc.
|
||||
*/
|
||||
void handleBulkUpdate(TableIUD tableIUD) {
|
||||
// inserts don't invalidate the bean cache
|
||||
if (tableIUD.isUpdateOrDelete()) {
|
||||
beanCacheClear();
|
||||
}
|
||||
// any change invalidates the query cache
|
||||
queryCacheClear();
|
||||
}
|
||||
|
||||
void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.get().put(newKey, id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(id);
|
||||
if (existingData != null) {
|
||||
long currentVersion = existingData.getVersion();
|
||||
if (version > 0 && version < currentVersion) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, id, currentVersion, version);
|
||||
}
|
||||
cache.remove(id);
|
||||
} else {
|
||||
if (version == 0) {
|
||||
version = currentVersion;
|
||||
}
|
||||
CachedBeanData newData = existingData.update(changes, version);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, id, changes);
|
||||
}
|
||||
cache.put(id, newData);
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = existingData.getData(naturalKeyProperty);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
}
|
||||
naturalKeyCache.get().remove(oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.Query;
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages draft entity beans.
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
public final class BeanDescriptorDraftHelp<T> {
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
private final BeanProperty draftDirty;
|
||||
|
||||
private final BeanProperty[] resetProperties;
|
||||
|
||||
public BeanDescriptorDraftHelp(BeanDescriptor<T> desc) {
|
||||
this.desc = desc;
|
||||
this.draftDirty = desc.getDraftDirty();
|
||||
this.resetProperties = resetProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that are reset on draft beans after publish.
|
||||
*/
|
||||
private BeanProperty[] resetProperties() {
|
||||
|
||||
List<BeanProperty> list = new ArrayList<>();
|
||||
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (BeanProperty prop : props) {
|
||||
if (prop.isDraftReset()) {
|
||||
list.add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new BeanProperty[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of all the 'reset properties' to null on the draft bean.
|
||||
*/
|
||||
public boolean draftReset(T draftBean) {
|
||||
|
||||
EntityBean draftEntityBean = (EntityBean) draftBean;
|
||||
|
||||
if (draftDirty != null) {
|
||||
// set @DraftDirty property to false
|
||||
draftDirty.setValueIntercept(draftEntityBean, false);
|
||||
}
|
||||
|
||||
// set to null on all @DraftReset properties
|
||||
for (BeanProperty resetProperty : resetProperties) {
|
||||
resetProperty.setValueIntercept(draftEntityBean, null);
|
||||
}
|
||||
|
||||
// return true if the bean is dirty (and should be persisted)
|
||||
return draftEntityBean._ebean_getIntercept().isDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer the values from the draftBean to the liveBean.
|
||||
* <p>
|
||||
* This will recursive transfer values to all @DraftableElement properties.
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T publish(T draftBean, T liveBean) {
|
||||
|
||||
if (liveBean == null) {
|
||||
liveBean = (T) desc.createEntityBean();
|
||||
}
|
||||
|
||||
EntityBean draft = (EntityBean) draftBean;
|
||||
EntityBean live = (EntityBean) liveBean;
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
idProperty.publish(draft, live);
|
||||
}
|
||||
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (BeanProperty prop : props) {
|
||||
prop.publish(draft, live);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (BeanPropertyAssocMany<?> aMany : many) {
|
||||
if (aMany.getTargetDescriptor().isDraftable()) {
|
||||
aMany.publishMany(draft, live);
|
||||
}
|
||||
}
|
||||
|
||||
return liveBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch draftable element relationships.
|
||||
*/
|
||||
public void draftQueryOptimise(Query<T> query) {
|
||||
|
||||
BeanPropertyAssocOne<?>[] one = desc.propertiesOne();
|
||||
for (BeanPropertyAssocOne<?> anOne : one) {
|
||||
if (anOne.getTargetDescriptor().isDraftableElement()) {
|
||||
query.fetch(anOne.getName());
|
||||
}
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (BeanPropertyAssocMany<?> aMany : many) {
|
||||
if (aMany.getTargetDescriptor().isDraftableElement()) {
|
||||
query.fetch(aMany.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.json.EJson;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
import io.ebeaninternal.server.text.json.WriteJson.WriteBean;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BeanDescriptorJsonHelp<T> {
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
private final InheritInfo inheritInfo;
|
||||
|
||||
public BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
|
||||
this.desc = desc;
|
||||
this.inheritInfo = desc.inheritInfo;
|
||||
}
|
||||
|
||||
public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException {
|
||||
|
||||
writeJson.writeStartObject(key);
|
||||
|
||||
if (inheritInfo == null) {
|
||||
jsonWriteProperties(writeJson, bean);
|
||||
|
||||
} else {
|
||||
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
|
||||
String discValue = localInheritInfo.getDiscriminatorStringValue();
|
||||
String discColumn = localInheritInfo.getDiscriminatorColumn();
|
||||
writeJson.gen().writeStringField(discColumn, discValue);
|
||||
|
||||
localInheritInfo.desc().jsonWriteProperties(writeJson, bean);
|
||||
}
|
||||
|
||||
writeJson.writeEndObject();
|
||||
}
|
||||
|
||||
protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException {
|
||||
|
||||
WriteBean writeBean = writeJson.createWriteBean(desc, bean);
|
||||
writeBean.write(writeJson);
|
||||
}
|
||||
|
||||
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
|
||||
|
||||
if (inheritInfo == null) {
|
||||
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
|
||||
} else {
|
||||
desc.descOf(bean.getClass()).jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
|
||||
}
|
||||
}
|
||||
|
||||
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
|
||||
|
||||
writeJson.writeStartObject(null);
|
||||
// render the dirty properties
|
||||
BeanProperty[] props = desc.propertiesNonTransient();
|
||||
for (BeanProperty prop : props) {
|
||||
if (dirtyProps[prop.getPropertyIndex()]) {
|
||||
prop.jsonWrite(writeJson, bean);
|
||||
}
|
||||
}
|
||||
writeJson.writeEndObject();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
|
||||
|
||||
JsonParser parser = jsonRead.getParser();
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
|
||||
// start object token read by Jackson already
|
||||
} else {
|
||||
// check for null or start object
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
return null;
|
||||
}
|
||||
if (JsonToken.START_OBJECT != token) {
|
||||
throw new JsonParseException("Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
|
||||
}
|
||||
}
|
||||
|
||||
if (desc.inheritInfo == null) {
|
||||
return jsonReadObject(jsonRead, path);
|
||||
}
|
||||
|
||||
// check for the discriminator value to determine the correct sub type
|
||||
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
|
||||
|
||||
if (parser.nextToken() != JsonToken.FIELD_NAME) {
|
||||
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
|
||||
throw new JsonParseException(msg, parser.getCurrentLocation());
|
||||
}
|
||||
|
||||
String propName = parser.getCurrentName();
|
||||
if (!propName.equalsIgnoreCase(discColumn)) {
|
||||
// just try to assume this is the correct bean type in the inheritance
|
||||
BeanProperty property = desc.getBeanProperty(propName);
|
||||
if (property != null) {
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
property.jsonRead(jsonRead, bean);
|
||||
return jsonReadProperties(jsonRead, bean, path);
|
||||
}
|
||||
String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?";
|
||||
throw new JsonParseException(msg, parser.getCurrentLocation());
|
||||
}
|
||||
|
||||
String discValue = parser.nextTextValue();
|
||||
return (T) inheritInfo.readType(discValue).desc().jsonReadObject(jsonRead, path);
|
||||
}
|
||||
|
||||
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
|
||||
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
return jsonReadProperties(readJson, bean, path);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
|
||||
|
||||
if (path != null) {
|
||||
readJson.pushPath(path);
|
||||
}
|
||||
|
||||
// unmapped properties, send to JsonReadBeanVisitor later
|
||||
Map<String, Object> unmappedProperties = null;
|
||||
|
||||
do {
|
||||
JsonParser parser = readJson.getParser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.FIELD_NAME == event) {
|
||||
String key = parser.getCurrentName();
|
||||
BeanProperty p = desc.getBeanProperty(key);
|
||||
if (p != null) {
|
||||
p.jsonRead(readJson, bean);
|
||||
} else {
|
||||
// read an unmapped property
|
||||
if (unmappedProperties == null) {
|
||||
unmappedProperties = new LinkedHashMap<>();
|
||||
}
|
||||
unmappedProperties.put(key, EJson.parse(parser));
|
||||
}
|
||||
|
||||
} else if (JsonToken.END_OBJECT == event) {
|
||||
break;
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation());
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
if (unmappedProperties != null) {
|
||||
desc.setUnmappedJson(bean, unmappedProperties);
|
||||
}
|
||||
Object contextBean = null;
|
||||
Object id = desc.beanId(bean);
|
||||
if (id != null) {
|
||||
// check if the bean has already been loaded
|
||||
contextBean = readJson.persistenceContextPutIfAbsent(id, bean, desc);
|
||||
}
|
||||
if (contextBean == null) {
|
||||
readJson.beanVisitor(bean, unmappedProperties);
|
||||
}
|
||||
if (path != null) {
|
||||
readJson.popPath();
|
||||
}
|
||||
return contextBean == null ? (T) bean : (T) contextBean;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.config.EncryptKey;
|
||||
import io.ebean.config.NamingConvention;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
|
||||
|
||||
/**
|
||||
* Provides a method to find a BeanDescriptor.
|
||||
* <p>
|
||||
* Used during deployment of to resolve relationships between beans.
|
||||
* </p>
|
||||
*/
|
||||
public interface BeanDescriptorMap {
|
||||
|
||||
/**
|
||||
* Return the name of the server/database.
|
||||
*/
|
||||
String getServerName();
|
||||
|
||||
/**
|
||||
* Return the ServerConfig.
|
||||
*/
|
||||
ServerConfig getServerConfig();
|
||||
|
||||
/**
|
||||
* Return the Cache Manager.
|
||||
*/
|
||||
SpiCacheManager getCacheManager();
|
||||
|
||||
/**
|
||||
* Return the naming convention.
|
||||
*/
|
||||
NamingConvention getNamingConvention();
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for a given class.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
|
||||
|
||||
/**
|
||||
* Return the Encrypt key given the table and column name.
|
||||
*/
|
||||
EncryptKey getEncryptKey(String tableName, String columnName);
|
||||
|
||||
/**
|
||||
* Create a IdBinder for this bean property.
|
||||
*/
|
||||
IdBinder createIdBinder(BeanProperty id);
|
||||
|
||||
/**
|
||||
* Create a doc store specific adapter for this bean type.
|
||||
*/
|
||||
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor<T> descriptor, DeployBeanDescriptor<T> deploy);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
public class BeanEmbeddedMeta {
|
||||
|
||||
|
||||
final BeanProperty[] properties;
|
||||
|
||||
public BeanEmbeddedMeta(BeanProperty[] properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties with over ridden mapping information.
|
||||
*/
|
||||
public BeanProperty[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
|
||||
/**
|
||||
* Creates BeanProperties for Embedded beans that have deployment information
|
||||
* such as the actual DB column name and table alias.
|
||||
*/
|
||||
public class BeanEmbeddedMetaFactory {
|
||||
|
||||
/**
|
||||
* Create BeanProperties for embedded beans using the deployment specific DB column name and table alias.
|
||||
*/
|
||||
public static BeanEmbeddedMeta create(BeanDescriptorMap owner, DeployBeanPropertyAssocOne<?> prop) {
|
||||
|
||||
// we can get a BeanDescriptor for an Embedded bean
|
||||
// and know that it is NOT recursive, as Embedded beans are
|
||||
// only allow to hold simple scalar types...
|
||||
BeanDescriptor<?> targetDesc = owner.getBeanDescriptor(prop.getTargetType());
|
||||
if (targetDesc == null) {
|
||||
String msg = "Could not find BeanDescriptor for " + prop.getTargetType()
|
||||
+ ". Perhaps the EmbeddedId class is not registered?";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
// deployment override information (column names)
|
||||
String columnPrefix = prop.getColumnPrefix();
|
||||
Map<String, String> propColMap = prop.getDeployEmbedded().getPropertyColumnMap();
|
||||
|
||||
BeanProperty[] sourceProperties = targetDesc.propertiesBaseScalar();
|
||||
BeanProperty[] embeddedProperties = new BeanProperty[sourceProperties.length];
|
||||
|
||||
for (int i = 0; i < sourceProperties.length; i++) {
|
||||
String propertyName = sourceProperties[i].getName();
|
||||
String dbColumn = propColMap.get(propertyName);
|
||||
if (dbColumn == null) {
|
||||
// dbColumn not overridden so take original
|
||||
dbColumn = sourceProperties[i].getDbColumn();
|
||||
if (columnPrefix != null) {
|
||||
dbColumn = columnPrefix + dbColumn;
|
||||
}
|
||||
}
|
||||
|
||||
BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn);
|
||||
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
|
||||
}
|
||||
|
||||
return new BeanEmbeddedMeta(embeddedProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation for BeanFinderFactory.
|
||||
*/
|
||||
public class BeanFinderManager {
|
||||
|
||||
final Logger logger = LoggerFactory.getLogger(BeanFinderManager.class);
|
||||
|
||||
private final List<BeanFindController> list;
|
||||
|
||||
public BeanFinderManager(BootupClasses bootupClasses) {
|
||||
list = bootupClasses.getBeanFindControllers();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanPersistController for a given entity type.
|
||||
*/
|
||||
public void addFindControllers(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
for (BeanFindController c : list) {
|
||||
if (c.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanFindController on[" + deployDesc.getFullName() + "] " + c.getClass().getName());
|
||||
deployDesc.setBeanFinder(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.StringParser;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
/**
|
||||
* Used to evaluate imported foreign keys so as to avoid unnecessary joins.
|
||||
*/
|
||||
public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
|
||||
private final String placeHolder;
|
||||
private final String prefix;
|
||||
private final String name;
|
||||
private final String dbColumn;
|
||||
private final boolean containsMany;
|
||||
|
||||
private final int deployOrder;
|
||||
|
||||
public BeanFkeyProperty(String name, String dbColumn, int deployOrder) {
|
||||
this(null, name, dbColumn, deployOrder, false);
|
||||
}
|
||||
|
||||
private BeanFkeyProperty(String prefix, String name, String dbColumn, int deployOrder, boolean containsMany) {
|
||||
this.prefix = prefix;
|
||||
this.name = name;
|
||||
this.dbColumn = dbColumn;
|
||||
this.deployOrder = deployOrder;
|
||||
this.containsMany = containsMany;
|
||||
this.placeHolder = calcPlaceHolder(prefix, dbColumn);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "prefix:" + prefix + " name:" + name + " dbColumn:" + dbColumn + " ph:" + placeHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getDeployOrder() {
|
||||
return deployOrder;
|
||||
}
|
||||
|
||||
private String calcPlaceHolder(String prefix, String dbColumn) {
|
||||
if (prefix != null) {
|
||||
return "${" + prefix + "}" + dbColumn;
|
||||
} else {
|
||||
return ROOT_ELPREFIX + dbColumn;
|
||||
}
|
||||
}
|
||||
|
||||
public BeanFkeyProperty create(String expression, boolean containsMany) {
|
||||
int len = expression.length() - name.length() - 1;
|
||||
String prefix = expression.substring(0, len);
|
||||
|
||||
return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder, containsMany);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false for keys.
|
||||
*/
|
||||
public boolean isDbEncrypted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false for keys.
|
||||
*/
|
||||
public boolean isLocalEncrypted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsFormulaWithJoin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false.
|
||||
*/
|
||||
public boolean containsMany() {
|
||||
return containsMany;
|
||||
}
|
||||
|
||||
public boolean containsManySince(String sinceProperty) {
|
||||
return containsMany();
|
||||
}
|
||||
|
||||
public String getDbColumn() {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getElName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null as not an AssocOne.
|
||||
*/
|
||||
public Object[] getAssocIdValues(EntityBean value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null as not an AssocOne.
|
||||
*/
|
||||
public String getAssocIdExpression(String prefix, String operator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null as not an AssocOne.
|
||||
*/
|
||||
public String getAssocIdInExpr(String prefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null as not an AssocOne.
|
||||
*/
|
||||
public String getAssocIdInValueExpr(int size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssocMany() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false as not an AssocOne.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAssocId() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssocProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getElPlaceholder(boolean encrypted) {
|
||||
return placeHolder;
|
||||
}
|
||||
|
||||
public String getElPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public boolean isDateTimeCapable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getJdbcType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public BeanProperty getBeanProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object parseDateTime(long systemTimeMillis) {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
public StringParser getStringParser() {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object value) {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pathSet(Object bean, Object value) {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object pathGet(Object bean) {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object pathGetNested(Object bean) {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.PostPersist;
|
||||
import javax.persistence.PostRemove;
|
||||
import javax.persistence.PostUpdate;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreRemove;
|
||||
import javax.persistence.PreUpdate;
|
||||
|
||||
import io.ebean.annotation.PostSoftDelete;
|
||||
import io.ebean.annotation.PreSoftDelete;
|
||||
import io.ebean.event.BeanPersistAdapter;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
import io.ebean.event.BeanPostConstructListener;
|
||||
import io.ebean.event.BeanPostLoad;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
|
||||
/**
|
||||
* Helper that looks for methods annotated with lifecycle events and registers an adapter for them.
|
||||
* <p>
|
||||
* This includes PrePersist, PostPersist, PreUpdate, PostUpdate, PreRemove, PostRemove and PostLoad
|
||||
* lifecycle events.
|
||||
* </p>
|
||||
*/
|
||||
class BeanLifecycleAdapterFactory {
|
||||
|
||||
/**
|
||||
* Register a BeanPersistController for methods annotated with lifecycle events.
|
||||
*/
|
||||
void addLifecycleMethods(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
Method[] methods = deployDesc.getBeanType().getMethods();
|
||||
|
||||
// look for annotated methods
|
||||
MethodsHolder methodHolder = new MethodsHolder();
|
||||
for (Method m : methods) {
|
||||
methodHolder.checkMethod(m);
|
||||
}
|
||||
|
||||
if (methodHolder.hasPersistMethods()) {
|
||||
// has pre/post persist annotated methods
|
||||
deployDesc.addPersistController(new PersistAdapter(new PersistMethodsHolder(methodHolder)));
|
||||
}
|
||||
|
||||
if (!methodHolder.postLoads.isEmpty()) {
|
||||
// has postLoad methods
|
||||
deployDesc.addPostLoad(new PostLoadAdapter(methodHolder.postLoads));
|
||||
}
|
||||
if (!methodHolder.postConstructs.isEmpty()) {
|
||||
// has postConstruct methods
|
||||
deployDesc.addPostConstructListener(new PostConstructAdapter(methodHolder.postConstructs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds Methods for the lifecycle events.s
|
||||
*/
|
||||
private static class MethodsHolder {
|
||||
|
||||
private boolean hasPersistMethods;
|
||||
private final List<Method> preInserts = new ArrayList<>();
|
||||
private final List<Method> postInserts = new ArrayList<>();
|
||||
private final List<Method> preUpdates = new ArrayList<>();
|
||||
private final List<Method> postUpdates = new ArrayList<>();
|
||||
private final List<Method> preDeletes = new ArrayList<>();
|
||||
private final List<Method> postDeletes = new ArrayList<>();
|
||||
private final List<Method> preSoftDeletes = new ArrayList<>();
|
||||
private final List<Method> postSoftDeletes = new ArrayList<>();
|
||||
private final List<Method> postLoads = new ArrayList<>();
|
||||
private final List<Method> postConstructs = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Has one of the pre or post insert update delete annotated methods.
|
||||
*/
|
||||
private boolean hasPersistMethods() {
|
||||
return hasPersistMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the method for all the annotations we are interested in.
|
||||
*/
|
||||
private void checkMethod(Method method) {
|
||||
if (method.isAnnotationPresent(PrePersist.class)) {
|
||||
preInserts.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostPersist.class)) {
|
||||
postInserts.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreUpdate.class)) {
|
||||
preUpdates.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostUpdate.class)) {
|
||||
postUpdates.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreRemove.class)) {
|
||||
preDeletes.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostRemove.class)) {
|
||||
postDeletes.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PreSoftDelete.class)) {
|
||||
preSoftDeletes.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostSoftDelete.class)) {
|
||||
postSoftDeletes.add(method);
|
||||
hasPersistMethods = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PostLoad.class)) {
|
||||
postLoads.add(method);
|
||||
}
|
||||
if (method.isAnnotationPresent(PostConstruct.class)) {
|
||||
postConstructs.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to covert List of Method into array (because we care about performance here).
|
||||
*/
|
||||
static Method[] toArray(List<Method> methodList) {
|
||||
return methodList.toArray(new Method[methodList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds Methods for the lifecycle events.s
|
||||
*/
|
||||
private static class PersistMethodsHolder {
|
||||
|
||||
private final Method[] preInserts;
|
||||
private final Method[] postInserts;
|
||||
private final Method[] preUpdates;
|
||||
private final Method[] postUpdates;
|
||||
private final Method[] preDeletes;
|
||||
private final Method[] postDeletes;
|
||||
private final Method[] preSoftDeletes;
|
||||
private final Method[] postSoftDeletes;
|
||||
|
||||
PersistMethodsHolder(MethodsHolder methodsHolder) {
|
||||
this.preInserts = toArray(methodsHolder.preInserts);
|
||||
this.preUpdates = toArray(methodsHolder.preUpdates);
|
||||
this.preDeletes = toArray(methodsHolder.preDeletes);
|
||||
this.preSoftDeletes = toArray(methodsHolder.preSoftDeletes);
|
||||
this.postInserts = toArray(methodsHolder.postInserts);
|
||||
this.postUpdates = toArray(methodsHolder.postUpdates);
|
||||
this.postDeletes = toArray(methodsHolder.postDeletes);
|
||||
this.postSoftDeletes = toArray(methodsHolder.postSoftDeletes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BeanPersistAdapter using reflection to invoke lifecycle methods.
|
||||
*/
|
||||
private static class PersistAdapter extends BeanPersistAdapter {
|
||||
|
||||
private final PersistMethodsHolder methodHolder;
|
||||
|
||||
private PersistAdapter(PersistMethodsHolder methodHolder) {
|
||||
this.methodHolder = methodHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// Not used
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invoke(Method method, Object bean) {
|
||||
try {
|
||||
method.invoke(bean);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new PersistenceException("Error invoking lifecycle method", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void invoke(Method[] methods, BeanPersistRequest<?> request) {
|
||||
for (Method method : methods) {
|
||||
invoke(method, request.getBean());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preDelete(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.preDeletes, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preSoftDelete(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.preSoftDeletes, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.preInserts, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.preUpdates, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDelete(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.postDeletes, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSoftDelete(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.postSoftDeletes, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInsert(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.postInserts, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(BeanPersistRequest<?> request) {
|
||||
invoke(methodHolder.postUpdates, request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BeanPostLoad using reflection to invoke lifecycle methods.
|
||||
*/
|
||||
private static class PostLoadAdapter implements BeanPostLoad {
|
||||
|
||||
private final Method[] postLoadMethods;
|
||||
|
||||
private PostLoadAdapter(List<Method> postLoadMethods) {
|
||||
this.postLoadMethods = toArray(postLoadMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// Not used
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invoke(Method method, Object bean) {
|
||||
try {
|
||||
method.invoke(bean);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new PersistenceException("Error invoking lifecycle method", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postLoad(Object bean) {
|
||||
for (Method postLoadMethod : postLoadMethods) {
|
||||
invoke(postLoadMethod, bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PostConstructAdapter using reflection to invoke lifecycle methods.
|
||||
*/
|
||||
private static class PostConstructAdapter implements BeanPostConstructListener {
|
||||
|
||||
private final Method[] postConstructMethods;
|
||||
|
||||
private PostConstructAdapter(List<Method> postConstructMethods) {
|
||||
this.postConstructMethods = toArray(postConstructMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// Not used
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invoke(Method method, Object bean) {
|
||||
try {
|
||||
method.invoke(bean);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new PersistenceException("Error invoking lifecycle method", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postConstruct(Object bean) {
|
||||
for (Method postConstructMethod : postConstructMethods) {
|
||||
invoke(postConstructMethod, bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autowire(Object bean) {
|
||||
// autowire is done by global PostConstructListener only
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreate(Object bean) {
|
||||
// postCreate is done by global PostConstructListener only
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.common.BeanList;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
|
||||
/**
|
||||
* Helper object for dealing with Lists.
|
||||
*/
|
||||
public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
private final String propertyName;
|
||||
|
||||
private BeanCollectionLoader loader;
|
||||
|
||||
public BeanListHelp(BeanPropertyAssocMany<T> many) {
|
||||
this.many = many;
|
||||
this.targetDescriptor = many.getTargetDescriptor();
|
||||
this.propertyName = many.getName();
|
||||
}
|
||||
|
||||
public BeanListHelp() {
|
||||
this.many = null;
|
||||
this.targetDescriptor = null;
|
||||
this.propertyName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoader(BeanCollectionLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal add bypassing any modify listening.
|
||||
*/
|
||||
@Override
|
||||
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
|
||||
if (withCheck) {
|
||||
collection.internalAddWithCheck(bean);
|
||||
} else {
|
||||
collection.internalAdd(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
|
||||
if (bc instanceof BeanList<?>) {
|
||||
|
||||
BeanList<?> bl = (BeanList<?>) bc;
|
||||
if (bl.getActualList() == null) {
|
||||
bl.setActualList(new ArrayList<>());
|
||||
}
|
||||
return bl;
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmptyNoParent() {
|
||||
return new BeanList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmpty(EntityBean parentBean) {
|
||||
BeanList<T> beanList = new BeanList<>(loader, parentBean, propertyName);
|
||||
if (many != null) {
|
||||
beanList.setModifyListening(many.getModifyListenMode());
|
||||
}
|
||||
return beanList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createReference(EntityBean parentBean) {
|
||||
|
||||
BeanList<T> beanList = new BeanList<>(loader, parentBean, propertyName);
|
||||
beanList.setModifyListening(many.getModifyListenMode());
|
||||
return beanList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
|
||||
|
||||
BeanList<?> newBeanList = (BeanList<?>) server.findList(query, t);
|
||||
refresh(newBeanList, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(BeanCollection<?> bc, EntityBean parentBean) {
|
||||
|
||||
BeanList<?> newBeanList = (BeanList<?>) bc;
|
||||
|
||||
List<?> currentList = (List<?>) many.getValue(parentBean);
|
||||
|
||||
newBeanList.setModifyListening(many.getModifyListenMode());
|
||||
|
||||
if (currentList == null) {
|
||||
// the currentList is null? Not really expecting this...
|
||||
many.setValue(parentBean, newBeanList);
|
||||
|
||||
} else if (currentList instanceof BeanList<?>) {
|
||||
// normally this case, replace just the underlying list
|
||||
BeanList<?> currentBeanList = (BeanList<?>) currentList;
|
||||
currentBeanList.setActualList(newBeanList.getActualList());
|
||||
currentBeanList.setModifyListening(many.getModifyListenMode());
|
||||
|
||||
} else {
|
||||
// replace the entire list with the BeanList
|
||||
many.setValue(parentBean, newBeanList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException {
|
||||
|
||||
List<?> list;
|
||||
if (collection instanceof BeanCollection<?>) {
|
||||
BeanList<?> beanList = (BeanList<?>) collection;
|
||||
if (!beanList.isPopulated()) {
|
||||
if (explicitInclude) {
|
||||
// invoke lazy loading as collection
|
||||
// is explicitly included in the output
|
||||
beanList.size();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
list = beanList.getActualList();
|
||||
} else {
|
||||
list = (List<?>) collection;
|
||||
}
|
||||
|
||||
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
|
||||
ctx.beginAssocMany(name);
|
||||
for (Object aList : list) {
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean) aList);
|
||||
}
|
||||
ctx.endAssocMany();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.persist.BeanPersister;
|
||||
|
||||
/**
|
||||
* Holds the BeanDescriptor and its associated BeanPersister.
|
||||
*/
|
||||
public class BeanManager<T> {
|
||||
|
||||
private final BeanPersister persister;
|
||||
|
||||
private final BeanDescriptor<T> descriptor;
|
||||
|
||||
public BeanManager(BeanDescriptor<T> descriptor, BeanPersister persister) {
|
||||
this.descriptor = descriptor;
|
||||
this.persister = persister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated BeanPersister.
|
||||
*/
|
||||
public BeanPersister getBeanPersister() {
|
||||
return persister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor.
|
||||
*/
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.server.persist.BeanPersisterFactory;
|
||||
import io.ebeaninternal.server.persist.dml.DmlBeanPersisterFactory;
|
||||
|
||||
/**
|
||||
* Creates BeanManagers.
|
||||
*/
|
||||
public class BeanManagerFactory {
|
||||
|
||||
final BeanPersisterFactory persisterFactory;
|
||||
|
||||
public BeanManagerFactory(DatabasePlatform dbPlatform) {
|
||||
persisterFactory = new DmlBeanPersisterFactory(dbPlatform);
|
||||
}
|
||||
|
||||
public <T> BeanManager<T> create(BeanDescriptor<T> desc) {
|
||||
|
||||
return new BeanManager<>(desc, persisterFactory.create(desc));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.common.BeanMap;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Helper specifically for dealing with Maps.
|
||||
*/
|
||||
public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
private final String propertyName;
|
||||
private final BeanProperty beanProperty;
|
||||
private BeanCollectionLoader loader;
|
||||
|
||||
/**
|
||||
* When created for a given query that will return a map.
|
||||
*/
|
||||
public BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
|
||||
this.targetDescriptor = targetDescriptor;
|
||||
this.beanProperty = targetDescriptor.getBeanProperty(mapKey);
|
||||
this.many = null;
|
||||
this.propertyName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* When help is attached to a specific many property.
|
||||
*/
|
||||
public BeanMapHelp(BeanPropertyAssocMany<T> many) {
|
||||
this.many = many;
|
||||
this.targetDescriptor = many.getTargetDescriptor();
|
||||
this.propertyName = many.getName();
|
||||
this.beanProperty = targetDescriptor.getBeanProperty(many.getMapKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoader(BeanCollectionLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
|
||||
if (mapKey == null) {
|
||||
mapKey = many.getMapKey();
|
||||
}
|
||||
BeanProperty beanProp = targetDescriptor.getBeanProperty(mapKey);
|
||||
|
||||
if (bc instanceof BeanMap<?, ?>) {
|
||||
BeanMap<Object, Object> bm = (BeanMap<Object, Object>) bc;
|
||||
Map<Object, Object> actualMap = bm.getActualMap();
|
||||
if (actualMap == null) {
|
||||
actualMap = new LinkedHashMap<>();
|
||||
bm.setActualMap(actualMap);
|
||||
}
|
||||
return new Adder(beanProp, actualMap);
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
}
|
||||
|
||||
static class Adder implements BeanCollectionAdd {
|
||||
|
||||
private final BeanProperty beanProperty;
|
||||
|
||||
private final Map<Object, Object> map;
|
||||
|
||||
Adder(BeanProperty beanProperty, Map<Object, Object> map) {
|
||||
this.beanProperty = beanProperty;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public void addEntityBean(EntityBean bean) {
|
||||
Object keyValue = beanProperty.getValue(bean);
|
||||
map.put(keyValue, bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmptyNoParent() {
|
||||
return new BeanMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmpty(EntityBean ownerBean) {
|
||||
|
||||
BeanMap<?,T> beanMap = new BeanMap<>(loader, ownerBean, propertyName);
|
||||
if (many != null) {
|
||||
beanMap.setModifyListening(many.getModifyListenMode());
|
||||
}
|
||||
return beanMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
|
||||
|
||||
if (bean == null) {
|
||||
((BeanMap<?, ?>) collection).internalPutNull();
|
||||
} else {
|
||||
Object keyValue = beanProperty.getValueIntercept(bean);
|
||||
BeanMap<?, ?> map = ((BeanMap<?, ?>) collection);
|
||||
map.internalPutWithCheck(keyValue, bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public BeanCollection<T> createReference(EntityBean parentBean) {
|
||||
|
||||
BeanMap beanMap = new BeanMap(loader, parentBean, propertyName);
|
||||
if (many != null) {
|
||||
beanMap.setModifyListening(many.getModifyListenMode());
|
||||
}
|
||||
return beanMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
|
||||
BeanMap<?, ?> newBeanMap = (BeanMap<?, ?>) server.findMap(query, t);
|
||||
refresh(newBeanMap, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(BeanCollection<?> bc, EntityBean parentBean) {
|
||||
|
||||
BeanMap<?, ?> newBeanMap = (BeanMap<?, ?>) bc;
|
||||
Map<?, ?> current = (Map<?, ?>) many.getValue(parentBean);
|
||||
|
||||
newBeanMap.setModifyListening(many.getModifyListenMode());
|
||||
if (current == null) {
|
||||
// the currentMap is null? Not really expecting this...
|
||||
many.setValue(parentBean, newBeanMap);
|
||||
|
||||
} else if (current instanceof BeanMap<?, ?>) {
|
||||
// normally this case, replace just the underlying list
|
||||
BeanMap<?, ?> currentBeanMap = (BeanMap<?, ?>) current;
|
||||
currentBeanMap.setActualMap(newBeanMap.getActualMap());
|
||||
currentBeanMap.setModifyListening(many.getModifyListenMode());
|
||||
|
||||
} else {
|
||||
// replace the entire set
|
||||
many.setValue(parentBean, newBeanMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException {
|
||||
|
||||
Map<?, ?> map;
|
||||
if (collection instanceof BeanCollection<?>) {
|
||||
BeanMap<?, ?> bc = (BeanMap<?, ?>) collection;
|
||||
if (!bc.isPopulated()) {
|
||||
if (explicitInclude) {
|
||||
// invoke lazy loading as collection
|
||||
// is explicitly included in the output
|
||||
bc.size();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
map = bc.getActualMap();
|
||||
} else {
|
||||
map = (Map<?, ?>) collection;
|
||||
}
|
||||
|
||||
if (!map.isEmpty() || ctx.isIncludeEmpty()) {
|
||||
ctx.beginAssocMany(name);
|
||||
for (Entry<?, ?> entry : map.entrySet()) {
|
||||
//FIXME: json write map key ...
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean) entry.getValue());
|
||||
}
|
||||
ctx.endAssocMany();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,409 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.PathProperties;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedIdEmbedded;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedIdSimple;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
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.support.DocStructure;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base for properties mapped to an associated bean, list, set or map.
|
||||
*/
|
||||
public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssoc.class);
|
||||
|
||||
/**
|
||||
* The descriptor of the target. This MUST be initialised after construction
|
||||
* so as to avoid a dependency loop between BeanDescriptors.
|
||||
*/
|
||||
BeanDescriptor<T> targetDescriptor;
|
||||
|
||||
IdBinder targetIdBinder;
|
||||
|
||||
InheritInfo targetInheritInfo;
|
||||
|
||||
String targetIdProperty;
|
||||
|
||||
/**
|
||||
* Derived list of exported property and matching foreignKey
|
||||
*/
|
||||
protected ExportedProperty[] exportedProperties;
|
||||
|
||||
/**
|
||||
* Persist settings.
|
||||
*/
|
||||
final BeanCascadeInfo cascadeInfo;
|
||||
|
||||
/**
|
||||
* Join between the beans.
|
||||
*/
|
||||
final TableJoin tableJoin;
|
||||
|
||||
/**
|
||||
* The type of the joined bean.
|
||||
*/
|
||||
final Class<T> targetType;
|
||||
|
||||
/**
|
||||
* The join table information.
|
||||
*/
|
||||
final BeanTable beanTable;
|
||||
|
||||
final String mappedBy;
|
||||
|
||||
final String docStoreDoc;
|
||||
|
||||
final String extraWhere;
|
||||
|
||||
boolean saveRecurseSkippable;
|
||||
|
||||
/**
|
||||
* Construct the property.
|
||||
*/
|
||||
public BeanPropertyAssoc(BeanDescriptor<?> descriptor, DeployBeanPropertyAssoc<T> deploy) {
|
||||
super(descriptor, deploy);
|
||||
this.extraWhere = InternString.intern(deploy.getExtraWhere());
|
||||
this.beanTable = deploy.getBeanTable();
|
||||
this.mappedBy = InternString.intern(deploy.getMappedBy());
|
||||
this.docStoreDoc = deploy.getDocStoreDoc();
|
||||
this.tableJoin = new TableJoin(deploy.getTableJoin());
|
||||
|
||||
this.targetType = deploy.getTargetType();
|
||||
this.cascadeInfo = deploy.getCascadeInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise post construction.
|
||||
*/
|
||||
@Override
|
||||
public void initialise() {
|
||||
// this *MUST* execute after the BeanDescriptor is
|
||||
// put into the map to stop infinite recursion
|
||||
targetDescriptor = descriptor.getBeanDescriptor(targetType);
|
||||
if (!isTransient) {
|
||||
targetIdBinder = targetDescriptor.getIdBinder();
|
||||
targetInheritInfo = targetDescriptor.getInheritInfo();
|
||||
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
|
||||
|
||||
if (!targetIdBinder.isComplexId()) {
|
||||
targetIdProperty = targetIdBinder.getIdProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ElPropertyValue for a *ToOne or *ToMany.
|
||||
*/
|
||||
protected ElPropertyValue createElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) {
|
||||
|
||||
// associated or embedded bean
|
||||
BeanDescriptor<?> embDesc = getTargetDescriptor();
|
||||
|
||||
if (chain == null) {
|
||||
chain = new ElPropertyChainBuilder(isEmbedded(), propName);
|
||||
}
|
||||
chain.add(this);
|
||||
if (containsMany()) {
|
||||
chain.setContainsMany();
|
||||
}
|
||||
return embDesc.buildElGetValue(remainder, chain, propertyDeploy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table join with table alias based on prefix.
|
||||
*/
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table join with explicit table alias.
|
||||
*/
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
|
||||
return tableJoin.addJoin(joinType, a1, a2, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false.
|
||||
*/
|
||||
public boolean isScalar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mappedBy property.
|
||||
* This will be null on the owning side.
|
||||
*/
|
||||
public String getMappedBy() {
|
||||
return mappedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id property of the target entity type.
|
||||
* <p>
|
||||
* This will return null for multiple Id properties.
|
||||
* </p>
|
||||
*/
|
||||
public String getTargetIdProperty() {
|
||||
return targetIdProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor of the target.
|
||||
*/
|
||||
public BeanDescriptor<T> getTargetDescriptor() {
|
||||
return targetDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if REFRESH should cascade.
|
||||
*/
|
||||
public boolean isCascadeRefresh() {
|
||||
return cascadeInfo.isRefresh();
|
||||
}
|
||||
|
||||
public boolean isSaveRecurseSkippable(Object bean) {
|
||||
|
||||
return saveRecurseSkippable && bean instanceof EntityBean && !((EntityBean) bean)._ebean_getIntercept().isNewOrDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if save can be skipped for unmodified bean(s) of this
|
||||
* property.
|
||||
* <p>
|
||||
* That is, if a bean of this property is unmodified we don't need to
|
||||
* saveRecurse because none of its associated beans have cascade save set to
|
||||
* true.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isSaveRecurseSkippable() {
|
||||
return saveRecurseSkippable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the unique id properties are all not null for this bean.
|
||||
*/
|
||||
public boolean hasId(EntityBean bean) {
|
||||
|
||||
BeanDescriptor<?> targetDesc = getTargetDescriptor();
|
||||
BeanProperty idProp = targetDesc.getIdProperty();
|
||||
if (idProp != null) {
|
||||
Object value = idProp.getValue(bean);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// all the unique properties are non-null
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the target.
|
||||
* <p>
|
||||
* This is the class of the associated bean, or beans contained in a list,
|
||||
* set or map.
|
||||
* </p>
|
||||
*/
|
||||
public Class<?> getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an extra clause to add to the query for loading or joining
|
||||
* to this bean type.
|
||||
*/
|
||||
public String getExtraWhere() {
|
||||
return extraWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the elastic search doc for this embedded property.
|
||||
*/
|
||||
public String getDocStoreDoc() {
|
||||
return docStoreDoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if and how the associated bean is included in the doc store document.
|
||||
*/
|
||||
@Override
|
||||
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
|
||||
|
||||
String embeddedDoc = getDocStoreDoc();
|
||||
if (embeddedDoc == null) {
|
||||
// not annotated so use include by default
|
||||
// which is *ToOne included and *ToMany excluded
|
||||
if (includeByDefault) {
|
||||
docStoreIncludeByDefault(docStructure.doc());
|
||||
}
|
||||
} else {
|
||||
// explicitly annotated to be included
|
||||
if (embeddedDoc.isEmpty()) {
|
||||
embeddedDoc = "*";
|
||||
}
|
||||
// add in a nested way
|
||||
PathProperties embDoc = PathProperties.parse(embeddedDoc);
|
||||
docStructure.addNested(name, embDoc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the property in the document store by default.
|
||||
*/
|
||||
protected void docStoreIncludeByDefault(PathProperties pathProps) {
|
||||
pathProps.addToPath(null, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
|
||||
|
||||
if (mapping.includesPath(prefix, name)) {
|
||||
String fullName = SplitName.add(prefix, name);
|
||||
|
||||
DocPropertyType type = isMany() ? DocPropertyType.LIST : DocPropertyType.OBJECT;
|
||||
DocPropertyMapping nested = new DocPropertyMapping(name, type);
|
||||
|
||||
mapping.push(nested);
|
||||
targetDescriptor.docStoreMapping(mapping, fullName);
|
||||
mapping.pop();
|
||||
|
||||
if (!nested.getChildren().isEmpty()) {
|
||||
mapping.add(nested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this association is updateable.
|
||||
*/
|
||||
public boolean isUpdateable() {
|
||||
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isUpdateable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this association is insertable.
|
||||
*/
|
||||
public boolean isInsertable() {
|
||||
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the join to use for the bean.
|
||||
*/
|
||||
public TableJoin getTableJoin() {
|
||||
return tableJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persist info.
|
||||
*/
|
||||
public BeanCascadeInfo getCascadeInfo() {
|
||||
return cascadeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the list of imported property. Matches BeanProperty from the target
|
||||
* descriptor back to local database columns in the TableJoin.
|
||||
*/
|
||||
protected ImportedId createImportedId(BeanPropertyAssoc<?> owner, BeanDescriptor<?> target, TableJoin join) {
|
||||
|
||||
BeanProperty idProp = target.getIdProperty();
|
||||
BeanProperty[] others = target.propertiesBaseScalar();
|
||||
|
||||
if (descriptor.isRawSqlBased()) {
|
||||
String dbColumn = owner.getDbColumn();
|
||||
return new ImportedIdSimple(owner, dbColumn, null, idProp, 0);
|
||||
}
|
||||
|
||||
TableJoinColumn[] cols = join.columns();
|
||||
|
||||
if (idProp == null) {
|
||||
return null;
|
||||
}
|
||||
if (!idProp.isEmbedded()) {
|
||||
// simple single scalar id
|
||||
if (cols.length != 1) {
|
||||
String msg = "No Imported Id column for [" + idProp + "] in table [" + join.getTable() + "]";
|
||||
logger.error(msg);
|
||||
return null;
|
||||
} else {
|
||||
BeanProperty[] idProps = {idProp};
|
||||
return createImportedScalar(owner, cols[0], idProps, others);
|
||||
}
|
||||
} else {
|
||||
// embedded id
|
||||
BeanPropertyAssocOne<?> embProp = (BeanPropertyAssocOne<?>) idProp;
|
||||
BeanProperty[] embBaseProps = embProp.getTargetDescriptor().propertiesBaseScalar();
|
||||
ImportedIdSimple[] scalars = createImportedList(owner, cols, embBaseProps, others);
|
||||
|
||||
return new ImportedIdEmbedded(owner, embProp, scalars);
|
||||
}
|
||||
}
|
||||
|
||||
private ImportedIdSimple[] createImportedList(BeanPropertyAssoc<?> owner, TableJoinColumn[] cols, BeanProperty[] props, BeanProperty[] others) {
|
||||
|
||||
ArrayList<ImportedIdSimple> list = new ArrayList<>();
|
||||
|
||||
for (TableJoinColumn col : cols) {
|
||||
list.add(createImportedScalar(owner, col, props, others));
|
||||
}
|
||||
|
||||
return ImportedIdSimple.sort(list);
|
||||
}
|
||||
|
||||
private ImportedIdSimple createImportedScalar(BeanPropertyAssoc<?> owner, TableJoinColumn col, BeanProperty[] props, BeanProperty[] others) {
|
||||
|
||||
String matchColumn = col.getForeignDbColumn();
|
||||
String localColumn = col.getLocalDbColumn();
|
||||
String localSqlFormula = col.getLocalSqlFormula();
|
||||
|
||||
for (int j = 0; j < props.length; j++) {
|
||||
if (props[j].getDbColumn().equalsIgnoreCase(matchColumn)) {
|
||||
return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < others.length; j++) {
|
||||
if (others[j].getDbColumn().equalsIgnoreCase(matchColumn)) {
|
||||
return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length);
|
||||
}
|
||||
}
|
||||
|
||||
String msg = "Error with the Join on [" + getFullBeanName()
|
||||
+ "]. Could not find the local match for [" + matchColumn + "] "//in table["+searchTable+"]?"
|
||||
+ " Perhaps an error in a @JoinColumn";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
protected void bindWhereParentId(List<Object> bindValues, Object parentId) {
|
||||
|
||||
if (exportedProperties.length == 1) {
|
||||
bindValues.add(parentId);
|
||||
|
||||
} else {
|
||||
EntityBean parent = (EntityBean) parentId;
|
||||
for (ExportedProperty exportedProperty : exportedProperties) {
|
||||
Object embVal = exportedProperty.getValue(parent);
|
||||
bindValues.add(embVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Help BeanPropertyAssocMany with JSON processing.
|
||||
*/
|
||||
public class BeanPropertyAssocManyJsonHelp {
|
||||
|
||||
/**
|
||||
* The associated many property.
|
||||
*/
|
||||
private final BeanPropertyAssocMany<?> many;
|
||||
|
||||
/**
|
||||
* Helper used to read json for transient 'many' properties.
|
||||
*/
|
||||
private final BeanPropertyAssocManyJsonTransient jsonTransient;
|
||||
|
||||
/**
|
||||
* Construct for the owning many property.
|
||||
*/
|
||||
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
|
||||
this.many = many;
|
||||
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the JSON for this property.
|
||||
*/
|
||||
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
if (!this.many.jsonDeserialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonParser parser = readJson.getParser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == event) {
|
||||
return;
|
||||
}
|
||||
if (JsonToken.START_ARRAY != event) {
|
||||
throw new JsonParseException("Unexpected token " + event + " - expecting start_array ", parser.getCurrentLocation());
|
||||
}
|
||||
|
||||
if (many.isTransient()) {
|
||||
jsonReadTransientUsingObjectMapper(readJson, parentBean);
|
||||
return;
|
||||
}
|
||||
|
||||
BeanCollection<?> collection = many.createEmpty(parentBean);
|
||||
BeanCollectionAdd add = many.getBeanCollectionAdd(collection, null);
|
||||
do {
|
||||
EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(readJson, many.name);
|
||||
if (detailBean == null) {
|
||||
// read the entire array
|
||||
break;
|
||||
}
|
||||
add.addEntityBean(detailBean);
|
||||
|
||||
if (parentBean != null && many.childMasterProperty != null) {
|
||||
// bind detail bean back to master via mappedBy property
|
||||
many.childMasterProperty.setValue(detailBean, parentBean);
|
||||
}
|
||||
} while (true);
|
||||
|
||||
many.setValue(parentBean, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Transient property using Jackson ObjectMapper.
|
||||
*/
|
||||
private void jsonReadTransientUsingObjectMapper(ReadJson readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
if (jsonTransient == null) {
|
||||
throw new IllegalStateException("Jackson ObjectMapper is required to read this Transient property "+many.getFullBeanName());
|
||||
}
|
||||
jsonTransient.jsonReadUsingObjectMapper(many, readJson, parentBean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
import com.fasterxml.jackson.databind.type.MapType;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Helper used to read transient many properties using Jackson ObjectMapper.
|
||||
*/
|
||||
public class BeanPropertyAssocManyJsonTransient {
|
||||
|
||||
/**
|
||||
* Use Jackson ObjectMapper to read the transient 'many' property.
|
||||
*/
|
||||
public void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, ReadJson readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
ObjectMapper mapper = readJson.getObjectMapper();
|
||||
|
||||
ManyType manyType = many.getManyType();
|
||||
|
||||
Object value;
|
||||
if (manyType.isMap()) {
|
||||
// read map using Jackson object mapper with unknown key type
|
||||
TypeFactory typeFactory = mapper.getTypeFactory();
|
||||
JavaType target = typeFactory.constructType(many.getTargetType());
|
||||
MapType jacksonType = typeFactory.constructMapType(LinkedHashMap.class, TypeFactory.unknownType(), target);
|
||||
value = mapper.readValue(readJson.getParser(), jacksonType);
|
||||
|
||||
} else {
|
||||
// read list or set using Jackson object mapper
|
||||
CollectionType jacksonType = mapper.getTypeFactory().constructCollectionType(manyType.getCollectionType(), many.getTargetType());
|
||||
value = mapper.readValue(readJson.getParser(), jacksonType);
|
||||
}
|
||||
|
||||
many.setValue(parentBean, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Property mapped to a joined bean.
|
||||
*/
|
||||
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
|
||||
private final boolean oneToOne;
|
||||
|
||||
private final boolean oneToOneExported;
|
||||
|
||||
private final boolean importedPrimaryKey;
|
||||
|
||||
private AssocOneHelp localHelp;
|
||||
|
||||
protected final BeanProperty[] embeddedProps;
|
||||
|
||||
private final HashMap<String, BeanProperty> embeddedPropsMap;
|
||||
|
||||
/**
|
||||
* The information for Imported foreign Keys.
|
||||
*/
|
||||
protected ImportedId importedId;
|
||||
|
||||
private String deleteByParentIdSql;
|
||||
private String deleteByParentIdInSql;
|
||||
|
||||
private BeanPropertyAssocMany<?> relationshipProperty;
|
||||
|
||||
/**
|
||||
* Create based on deploy information of an EmbeddedId.
|
||||
*/
|
||||
public BeanPropertyAssocOne(BeanDescriptorMap owner, DeployBeanPropertyAssocOne<T> deploy) {
|
||||
this(owner, null, deploy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the property.
|
||||
*/
|
||||
public BeanPropertyAssocOne(BeanDescriptorMap owner, BeanDescriptor<?> descriptor,
|
||||
DeployBeanPropertyAssocOne<T> deploy) {
|
||||
|
||||
super(descriptor, deploy);
|
||||
|
||||
importedPrimaryKey = deploy.isImportedPrimaryKey();
|
||||
oneToOne = deploy.isOneToOne();
|
||||
oneToOneExported = deploy.isOneToOneExported();
|
||||
|
||||
if (embedded) {
|
||||
// Overriding of the columns and use table alias of owning BeanDescriptor
|
||||
BeanEmbeddedMeta overrideMeta = BeanEmbeddedMetaFactory.create(owner, deploy);
|
||||
embeddedProps = overrideMeta.getProperties();
|
||||
embeddedPropsMap = new HashMap<>();
|
||||
for (BeanProperty embeddedProp : embeddedProps) {
|
||||
embeddedPropsMap.put(embeddedProp.getName(), embeddedProp);
|
||||
}
|
||||
|
||||
} else {
|
||||
embeddedProps = null;
|
||||
embeddedPropsMap = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialise() {
|
||||
super.initialise();
|
||||
localHelp = createHelp(embedded, oneToOneExported);
|
||||
|
||||
if (!isTransient) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (embedded) {
|
||||
// no imported or exported information
|
||||
} else if (!oneToOneExported) {
|
||||
importedId = createImportedId(this, targetDescriptor, tableJoin);
|
||||
if (importedId.isScalar()) {
|
||||
// limit JoinColumn mapping to the @Id / primary key
|
||||
TableJoinColumn[] columns = tableJoin.columns();
|
||||
String foreignJoinColumn = columns[0].getForeignDbColumn();
|
||||
String foreignIdColumn = targetDescriptor.getIdProperty().getDbColumn();
|
||||
if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) {
|
||||
throw new PersistenceException("Mapping limitation - @JoinColumn on " + getFullBeanName() + " needs to map to a primary key as per Issue #529 "
|
||||
+ " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
exportedProperties = createExported();
|
||||
|
||||
String delStmt = "delete from " + targetDescriptor.getBaseTable() + " where ";
|
||||
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false);
|
||||
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value as an entity bean.
|
||||
*/
|
||||
public EntityBean getValueAsEntityBean(EntityBean owner) {
|
||||
return (EntityBean) getValue(owner);
|
||||
}
|
||||
|
||||
void setRelationshipProperty(BeanPropertyAssocMany<?> relationshipProperty) {
|
||||
this.relationshipProperty = relationshipProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this relationship needs to maintain/update L2 cache.
|
||||
*/
|
||||
boolean isCacheNotify() {
|
||||
return targetDescriptor.isBeanCaching() && relationshipProperty != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the L2 relationship cache for this property.
|
||||
*/
|
||||
void cacheClear() {
|
||||
if (isCacheNotify()) {
|
||||
targetDescriptor.cacheManyPropClear(relationshipProperty.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear part of the L2 relationship cache for this property.
|
||||
*/
|
||||
void cacheDelete(boolean clear, EntityBean bean, CacheChangeSet changeSet) {
|
||||
|
||||
if (isCacheNotify()) {
|
||||
if (clear) {
|
||||
changeSet.addManyClear(targetDescriptor, relationshipProperty.getName());
|
||||
} else {
|
||||
Object assocBean = getValue(bean);
|
||||
if (assocBean != null) {
|
||||
Object parentId = targetDescriptor.getId((EntityBean) assocBean);
|
||||
if (parentId != null) {
|
||||
changeSet.addManyRemove(targetDescriptor, relationshipProperty.getName(), parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) {
|
||||
|
||||
if (embedded) {
|
||||
BeanProperty embProp = embeddedPropsMap.get(remainder);
|
||||
if (embProp == null) {
|
||||
String msg = "Embedded Property " + remainder + " not found in " + getFullBeanName();
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
if (chain == null) {
|
||||
chain = new ElPropertyChainBuilder(true, propName);
|
||||
}
|
||||
chain.add(this);
|
||||
return chain.add(embProp).build();
|
||||
}
|
||||
|
||||
return createElPropertyValue(propName, remainder, chain, propertyDeploy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElPlaceholder(boolean encrypted) {
|
||||
return encrypted ? elPlaceHolderEncrypted : elPlaceHolder;
|
||||
}
|
||||
|
||||
public SqlUpdate deleteByParentId(Object parentId, List<Object> parentIdist) {
|
||||
if (parentId != null) {
|
||||
return deleteByParentId(parentId);
|
||||
} else {
|
||||
return deleteByParentIdList(parentIdist);
|
||||
}
|
||||
}
|
||||
|
||||
private SqlUpdate deleteByParentIdList(List<Object> parentIdist) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append(deleteByParentIdInSql);
|
||||
|
||||
String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size());
|
||||
sb.append(inClause);
|
||||
|
||||
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
|
||||
for (Object aParentIdist : parentIdist) {
|
||||
targetIdBinder.bindId(delete, aParentIdist);
|
||||
}
|
||||
|
||||
return delete;
|
||||
}
|
||||
|
||||
private SqlUpdate deleteByParentId(Object parentId) {
|
||||
|
||||
DefaultSqlUpdate delete = new DefaultSqlUpdate(deleteByParentIdSql);
|
||||
if (exportedProperties.length == 1) {
|
||||
delete.addParameter(parentId);
|
||||
} else {
|
||||
targetDescriptor.getIdBinder().bindId(delete, parentId);
|
||||
}
|
||||
return delete;
|
||||
}
|
||||
|
||||
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdist, Transaction t) {
|
||||
if (parentId != null) {
|
||||
return findIdsByParentId(parentId, t);
|
||||
} else {
|
||||
return findIdsByParentIdList(parentIdist, t);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> findIdsByParentId(Object parentId, Transaction t) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(false);
|
||||
|
||||
List<Object> bindValues = new ArrayList<>();
|
||||
bindWhereParentId(bindValues, parentId);
|
||||
|
||||
EbeanServer server = getBeanDescriptor().getEbeanServer();
|
||||
Query<?> q = server.find(getPropertyType())
|
||||
.where()
|
||||
.raw(rawWhere, bindValues.toArray())
|
||||
.query();
|
||||
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(true);
|
||||
String inClause = targetIdBinder.getIdInValueExpr(parentIdList.size());
|
||||
|
||||
String expr = rawWhere + inClause;
|
||||
|
||||
List<Object> bindValues = new ArrayList<>();
|
||||
for (Object aParentIdList : parentIdList) {
|
||||
bindWhereParentId(bindValues, aParentIdList);
|
||||
}
|
||||
|
||||
EbeanServer server = getBeanDescriptor().getEbeanServer();
|
||||
Query<?> q = (Query<?>) server.find(getPropertyType())
|
||||
.where().raw(expr, bindValues.toArray());
|
||||
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
void addFkey() {
|
||||
if (importedId != null) {
|
||||
importedId.addFkeys(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerColumn(BeanDescriptor<?> desc, String prefix) {
|
||||
if (embedded) {
|
||||
for (BeanProperty prop : embeddedProps) {
|
||||
prop.registerColumn(desc, SplitName.add(prefix, name));
|
||||
}
|
||||
} else {
|
||||
if (targetIdProperty != null) {
|
||||
BeanDescriptor<T> target = getTargetDescriptor();
|
||||
String basePath = SplitName.add(prefix, name);
|
||||
if (dbColumn != null) {
|
||||
BeanProperty idProperty = target.getIdProperty();
|
||||
desc.registerColumn(dbColumn, SplitName.add(basePath, idProperty.getName()));
|
||||
}
|
||||
|
||||
desc.registerTable(target.getBaseTable(), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return meta data for the deployment of the embedded bean specific to this
|
||||
* property.
|
||||
*/
|
||||
public BeanProperty[] getProperties() {
|
||||
return embeddedProps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
|
||||
|
||||
prefix = SplitName.add(prefix, name);
|
||||
|
||||
if (!embedded) {
|
||||
InheritInfo inheritInfo = targetDescriptor.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// expect the discriminator column to be included in order
|
||||
// to determine the inheritance type so we add it to the
|
||||
// selectChain (so that it takes a position in the resultSet)
|
||||
String discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
String discProperty = prefix + "." + discriminatorColumn;
|
||||
selectChain.add(discProperty);
|
||||
}
|
||||
targetIdBinder.buildRawSqlSelectChain(prefix, selectChain);
|
||||
|
||||
} else {
|
||||
for (BeanProperty embeddedProp : embeddedProps) {
|
||||
embeddedProp.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this a OneToOne property. Otherwise assumed ManyToOne.
|
||||
*/
|
||||
public boolean isOneToOne() {
|
||||
return oneToOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is the exported side of a OneToOne.
|
||||
*/
|
||||
public boolean isOneToOneExported() {
|
||||
return oneToOneExported;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true this bean maps to the primary key.
|
||||
*/
|
||||
public boolean isImportedPrimaryKey() {
|
||||
return importedPrimaryKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void diffForInsert(String prefix, Map<String, ValuePair> map, EntityBean newBean) {
|
||||
Object newEmb = (newBean == null) ? null : getValue(newBean);
|
||||
if (newEmb != null) {
|
||||
prefix = (prefix == null) ? name : prefix + "." + name;
|
||||
if (embedded) {
|
||||
getTargetDescriptor().diffForInsert(prefix, map, (EntityBean) newEmb);
|
||||
} else {
|
||||
// we are only interested in the Id value
|
||||
BeanDescriptor<T> targetDescriptor = getTargetDescriptor();
|
||||
BeanProperty idProperty = targetDescriptor.getIdProperty();
|
||||
idProperty.diffForInsert(prefix, map, (EntityBean) newEmb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, EntityBean oldBean) {
|
||||
|
||||
Object newEmb = (newBean == null) ? null : getValue(newBean);
|
||||
Object oldEmb = (oldBean == null) ? null : getValue(oldBean);
|
||||
if (newEmb == null && oldEmb == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (embedded) {
|
||||
prefix = (prefix == null) ? name : prefix + "." + name;
|
||||
BeanDescriptor<T> targetDescriptor = getTargetDescriptor();
|
||||
targetDescriptor.diff(prefix, map, (EntityBean) newEmb, (EntityBean) oldEmb);
|
||||
|
||||
} else {
|
||||
// we are only interested in the Id value
|
||||
newBean = (EntityBean)newEmb;
|
||||
oldBean = (EntityBean)oldEmb;
|
||||
|
||||
BeanDescriptor<T> targetDescriptor = getTargetDescriptor();
|
||||
BeanProperty idProperty = targetDescriptor.getIdProperty();
|
||||
|
||||
Object newId = (newBean == null) ? null : idProperty.getValue(newBean);
|
||||
Object oldId = (oldBean == null) ? null : idProperty.getValue(oldBean);
|
||||
if (newId != null || oldId != null) {
|
||||
prefix = (prefix == null) ? name : prefix + "." + name;
|
||||
idProperty.diffVal(prefix, map, newId, oldId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getPropertyType(). Return the type of the bean this property
|
||||
* represents.
|
||||
*/
|
||||
public Class<?> getTargetType() {
|
||||
return getPropertyType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
Object ap = getValue(bean);
|
||||
if (ap == null) {
|
||||
return null;
|
||||
}
|
||||
if (embedded) {
|
||||
return targetDescriptor.cacheEmbeddedBeanExtract((EntityBean) ap);
|
||||
} else {
|
||||
return targetDescriptor.getIdProperty().getCacheDataValue((EntityBean) ap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
|
||||
if (cacheData == null) {
|
||||
setValue(bean, null);
|
||||
} else {
|
||||
if (embedded) {
|
||||
setValue(bean, targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData, context));
|
||||
} else {
|
||||
if (cacheData instanceof String) {
|
||||
cacheData = targetDescriptor.getIdProperty().scalarType.parse((String)cacheData);
|
||||
}
|
||||
// cacheData is the id value, maybe already in persistence context
|
||||
Object assocBean = targetDescriptor.contextGet(context, cacheData);
|
||||
if (assocBean == null) {
|
||||
assocBean = targetDescriptor.createReference(Boolean.FALSE, false, cacheData, context);
|
||||
}
|
||||
setValue(bean, assocBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id values from the given bean.
|
||||
*/
|
||||
@Override
|
||||
public Object[] getAssocIdValues(EntityBean bean) {
|
||||
return targetDescriptor.getIdBinder().getIdValues(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id expression to add to where clause etc.
|
||||
*/
|
||||
public String getAssocIdExpression(String prefix, String operator) {
|
||||
return targetDescriptor.getIdBinder().getAssocOneIdExpr(prefix, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logical id value expression taking into account embedded id's.
|
||||
*/
|
||||
@Override
|
||||
public String getAssocIdInValueExpr(int size) {
|
||||
return targetDescriptor.getIdBinder().getIdInValueExpr(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logical id in expression taking into account embedded id's.
|
||||
*/
|
||||
@Override
|
||||
public String getAssocIdInExpr(String prefix) {
|
||||
return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssocId() {
|
||||
return !embedded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssocProperty() {
|
||||
return !embedded;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a bean of the target type to be used as an embeddedId
|
||||
* value.
|
||||
*/
|
||||
public Object createEmbeddedId() {
|
||||
return getTargetDescriptor().createEntityBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object pathGetNested(Object bean) {
|
||||
Object value = getValueIntercept((EntityBean)bean);
|
||||
if (value == null) {
|
||||
value = targetDescriptor.createEntityBean();
|
||||
setValueIntercept((EntityBean)bean, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public ImportedId getImportedId() {
|
||||
return importedId;
|
||||
}
|
||||
|
||||
private String deriveWhereParentIdSql(boolean inClause) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < exportedProperties.length; i++) {
|
||||
String fkColumn = exportedProperties[i].getForeignDbColumn();
|
||||
if (i > 0) {
|
||||
String s = inClause ? "," : " and ";
|
||||
sb.append(s);
|
||||
}
|
||||
sb.append(fkColumn);
|
||||
if (!inClause) {
|
||||
sb.append("=? ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the array of ExportedProperty used to build reference objects.
|
||||
*/
|
||||
private ExportedProperty[] createExported() {
|
||||
|
||||
BeanProperty idProp = descriptor.getIdProperty();
|
||||
|
||||
ArrayList<ExportedProperty> list = new ArrayList<>();
|
||||
|
||||
if (idProp != null && idProp.isEmbedded()) {
|
||||
|
||||
BeanPropertyAssocOne<?> one = (BeanPropertyAssocOne<?>) idProp;
|
||||
BeanDescriptor<?> targetDesc = one.getTargetDescriptor();
|
||||
BeanProperty[] emIds = targetDesc.propertiesBaseScalar();
|
||||
try {
|
||||
for (BeanProperty emId : emIds) {
|
||||
ExportedProperty expProp = findMatch(true, emId);
|
||||
list.add(expProp);
|
||||
}
|
||||
} catch (PersistenceException e) {
|
||||
// not found as individual scalar properties
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (idProp != null) {
|
||||
ExportedProperty expProp = findMatch(false, idProp);
|
||||
list.add(expProp);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new ExportedProperty[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the matching foreignDbColumn for a given local property.
|
||||
*/
|
||||
private ExportedProperty findMatch(boolean embeddedProp, BeanProperty prop) {
|
||||
|
||||
String matchColumn = prop.getDbColumn();
|
||||
|
||||
String searchTable = tableJoin.getTable();
|
||||
TableJoinColumn[] columns = tableJoin.columns();
|
||||
|
||||
for (TableJoinColumn column : columns) {
|
||||
String matchTo = column.getLocalDbColumn();
|
||||
|
||||
if (matchColumn.equalsIgnoreCase(matchTo)) {
|
||||
String foreignCol = column.getForeignDbColumn();
|
||||
return new ExportedProperty(embeddedProp, foreignCol, prop);
|
||||
}
|
||||
}
|
||||
|
||||
String msg = "Error with the Join on [" + getFullBeanName()
|
||||
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
|
||||
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
if (!isTransient) {
|
||||
localHelp.appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
if (!isTransient) {
|
||||
localHelp.appendFrom(ctx, joinType);
|
||||
if (sqlFormulaJoin != null) {
|
||||
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
return localHelp.readSet(ctx, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data from the resultSet effectively ignoring it and returning null.
|
||||
*/
|
||||
@Override
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
// just read the resultSet incrementing the column index
|
||||
// pass in null for the bean so any data read is ignored
|
||||
return localHelp.read(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(EntityBean bean, Object value) {
|
||||
super.setValue(bean, value);
|
||||
if (embedded && value instanceof EntityBean) {
|
||||
setEmbeddedOwner(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the owner on the embedded bean property.
|
||||
*/
|
||||
void setEmbeddedOwner(EntityBean owner) {
|
||||
|
||||
Object emb = getValue(owner);
|
||||
if (emb != null) {
|
||||
setEmbeddedOwner(owner, emb);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEmbeddedOwner(EntityBean bean, Object value) {
|
||||
((EntityBean)value)._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueIntercept(EntityBean bean, Object value) {
|
||||
super.setValueIntercept(bean, value);
|
||||
if (embedded && value instanceof EntityBean) {
|
||||
setEmbeddedOwner(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
localHelp.loadIgnore(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(SqlBeanLoad sqlBeanLoad) {
|
||||
Object dbVal = sqlBeanLoad.load(this);
|
||||
if (embedded && sqlBeanLoad.isLazyLoad()) {
|
||||
if (dbVal instanceof EntityBean) {
|
||||
((EntityBean) dbVal)._ebean_getIntercept().setLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported) {
|
||||
if (embedded) {
|
||||
return new AssocOneHelpEmbedded(this);
|
||||
} else if (oneToOneExported) {
|
||||
return new AssocOneHelpRefExported(this);
|
||||
} else {
|
||||
if (targetInheritInfo != null) {
|
||||
return new AssocOneHelpRefInherit(this);
|
||||
} else {
|
||||
return new AssocOneHelpRefSimple(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
|
||||
|
||||
if (!jsonSerialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object value = getValueIntercept(bean);
|
||||
if (value == null) {
|
||||
writeJson.writeNullField(name);
|
||||
|
||||
} else {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (writeJson.isParentBean(value)) {
|
||||
// bi-directional and already rendered parent
|
||||
|
||||
} else {
|
||||
// Hmmm, not writing complex non-entity bean
|
||||
if (value instanceof EntityBean) {
|
||||
writeJson.beginAssocOne(name, bean);
|
||||
BeanDescriptor<?> refDesc = descriptor.getBeanDescriptor(value.getClass());
|
||||
refDesc.jsonWrite(writeJson, (EntityBean) value, name);
|
||||
writeJson.endAssocOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException {
|
||||
if (jsonDeserialize && targetDescriptor != null) {
|
||||
T assocBean = targetDescriptor.jsonRead(readJson, name);
|
||||
setValue(bean, assocBean);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReference(Object detailBean) {
|
||||
EntityBean eb = (EntityBean) detailBean;
|
||||
return targetDescriptor.isReference(eb._ebean_getIntercept());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent bean to the child bean if it has not already been set.
|
||||
*/
|
||||
public void setParentBeanToChild(EntityBean parent, EntityBean child) {
|
||||
|
||||
if (mappedBy != null) {
|
||||
BeanProperty beanProperty = targetDescriptor.getBeanProperty(mappedBy);
|
||||
if (beanProperty != null && beanProperty.getValue(child) == null) {
|
||||
// set the 'parent' bean to the 'child' bean
|
||||
beanProperty.setValue(child, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.lib.util.StringHelper;
|
||||
|
||||
/**
|
||||
* Used hold meta data when a bean property is overridden.
|
||||
* <p>
|
||||
* Typically this is for Embedded Beans.
|
||||
* </p>
|
||||
*/
|
||||
public class BeanPropertyOverride {
|
||||
|
||||
private final String dbColumn;
|
||||
|
||||
public BeanPropertyOverride(String dbColumn) {
|
||||
this.dbColumn = InternString.intern(dbColumn);
|
||||
}
|
||||
|
||||
public String getDbColumn() {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
public String replace(String src, String srcDbColumn) {
|
||||
return StringHelper.replaceString(src, srcDbColumn, dbColumn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
|
||||
|
||||
public class BeanPropertySimpleCollection<T> extends BeanPropertyAssocMany<T> {
|
||||
|
||||
public BeanPropertySimpleCollection(BeanDescriptor<?> descriptor, DeployBeanPropertySimpleCollection<T> deploy) {
|
||||
super(descriptor, deploy);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation for creating BeanControllers.
|
||||
*/
|
||||
public class BeanQueryAdapterManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanQueryAdapterManager.class);
|
||||
|
||||
private final List<BeanQueryAdapter> list;
|
||||
|
||||
public BeanQueryAdapterManager(BootupClasses bootupClasses) {
|
||||
|
||||
list = bootupClasses.getBeanQueryAdapters();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanPersistController for a given entity type.
|
||||
*/
|
||||
public void addQueryAdapter(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
for (BeanQueryAdapter c : list) {
|
||||
if (c.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanQueryAdapter on[" + deployDesc.getFullName() + "] " + c.getClass().getName());
|
||||
deployDesc.addQueryAdapter(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.common.BeanSet;
|
||||
import io.ebeaninternal.server.text.json.WriteJson;
|
||||
|
||||
/**
|
||||
* Helper specifically for dealing with Sets.
|
||||
*/
|
||||
public final class BeanSetHelp<T> implements BeanCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
private final String propertyName;
|
||||
private BeanCollectionLoader loader;
|
||||
|
||||
/**
|
||||
* When attached to a specific many property.
|
||||
*/
|
||||
public BeanSetHelp(BeanPropertyAssocMany<T> many) {
|
||||
this.many = many;
|
||||
this.targetDescriptor = many.getTargetDescriptor();
|
||||
this.propertyName = many.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a query that returns a set.
|
||||
*/
|
||||
public BeanSetHelp() {
|
||||
this.many = null;
|
||||
this.targetDescriptor = null;
|
||||
this.propertyName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoader(BeanCollectionLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
if (bc instanceof BeanSet<?>) {
|
||||
BeanSet<?> beanSet = (BeanSet<?>) bc;
|
||||
if (beanSet.getActualSet() == null) {
|
||||
beanSet.setActualSet(new LinkedHashSet<>());
|
||||
}
|
||||
return beanSet;
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
|
||||
if (withCheck) {
|
||||
collection.internalAddWithCheck(bean);
|
||||
} else {
|
||||
collection.internalAdd(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmptyNoParent() {
|
||||
return new BeanSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmpty(EntityBean ownerBean) {
|
||||
BeanSet<T> beanSet = new BeanSet<>(loader, ownerBean, propertyName);
|
||||
if (many != null) {
|
||||
beanSet.setModifyListening(many.getModifyListenMode());
|
||||
}
|
||||
return beanSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createReference(EntityBean parentBean) {
|
||||
|
||||
BeanSet<T> beanSet = new BeanSet<>(loader, parentBean, propertyName);
|
||||
beanSet.setModifyListening(many.getModifyListenMode());
|
||||
return beanSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
|
||||
|
||||
BeanSet<?> newBeanSet = (BeanSet<?>) server.findSet(query, t);
|
||||
refresh(newBeanSet, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(BeanCollection<?> bc, EntityBean parentBean) {
|
||||
|
||||
BeanSet<?> newBeanSet = (BeanSet<?>) bc;
|
||||
|
||||
Set<?> current = (Set<?>) many.getValue(parentBean);
|
||||
|
||||
newBeanSet.setModifyListening(many.getModifyListenMode());
|
||||
if (current == null) {
|
||||
// the currentList is null? Not really expecting this...
|
||||
many.setValue(parentBean, newBeanSet);
|
||||
|
||||
} else if (current instanceof BeanSet<?>) {
|
||||
// normally this case, replace just the underlying list
|
||||
BeanSet<?> currentBeanSet = (BeanSet<?>) current;
|
||||
currentBeanSet.setActualSet(newBeanSet.getActualSet());
|
||||
currentBeanSet.setModifyListening(many.getModifyListenMode());
|
||||
|
||||
} else {
|
||||
// replace the entire set
|
||||
many.setValue(parentBean, newBeanSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException {
|
||||
|
||||
Set<?> set;
|
||||
if (collection instanceof BeanCollection<?>) {
|
||||
BeanSet<?> bc = (BeanSet<?>) collection;
|
||||
if (!bc.isPopulated()) {
|
||||
if (explicitInclude) {
|
||||
// invoke lazy loading as collection
|
||||
// is explicitly included in the output
|
||||
bc.size();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
set = bc.getActualSet();
|
||||
} else {
|
||||
set = (Set<?>) collection;
|
||||
}
|
||||
|
||||
if (!set.isEmpty() || ctx.isIncludeEmpty()) {
|
||||
ctx.beginAssocMany(name);
|
||||
for (Object bean : set) {
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
|
||||
}
|
||||
ctx.endAssocMany();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Used for associated beans in place of a BeanDescriptor. This is done to avoid
|
||||
* recursion issues due to the potentially bi-directional and circular
|
||||
* relationships between beans.
|
||||
* <p>
|
||||
* It holds the main deployment information and not all the detail that is held
|
||||
* in a BeanDescriptor.
|
||||
* </p>
|
||||
*/
|
||||
public class BeanTable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanTable.class);
|
||||
|
||||
private final BeanDescriptorMap owner;
|
||||
|
||||
private final Class<?> beanType;
|
||||
|
||||
/**
|
||||
* The base table.
|
||||
*/
|
||||
private final String baseTable;
|
||||
|
||||
private final BeanProperty[] idProperties;
|
||||
|
||||
/**
|
||||
* Create the BeanTable.
|
||||
*/
|
||||
public BeanTable(DeployBeanTable mutable, BeanDescriptorMap owner) {
|
||||
this.owner = owner;
|
||||
this.beanType = mutable.getBeanType();
|
||||
this.baseTable = InternString.intern(mutable.getBaseTable());
|
||||
this.idProperties = mutable.createIdProperties(owner);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table for this BeanTable.
|
||||
* This is used to determine the join information
|
||||
* for associations.
|
||||
*/
|
||||
public String getBaseTable() {
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unqualified base table.
|
||||
*
|
||||
* @return the unqualified base table
|
||||
*/
|
||||
public String getUnqualifiedBaseTable(){
|
||||
final String[] chunks = baseTable.split("\\.");
|
||||
return chunks.length == 2 ? chunks[1] :chunks[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id properties.
|
||||
*/
|
||||
public BeanProperty[] getIdProperties() {
|
||||
return idProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class for this beanTable.
|
||||
*/
|
||||
public Class<?> getBeanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect) {
|
||||
|
||||
boolean complexKey = false;
|
||||
BeanProperty[] props = idProperties;
|
||||
|
||||
if (idProperties.length == 1){
|
||||
if (idProperties[0] instanceof BeanPropertyAssocOne<?>) {
|
||||
BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>)idProperties[0];
|
||||
props = assocOne.getProperties();
|
||||
complexKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
|
||||
String lc = prop.getDbColumn();
|
||||
String fk = lc;
|
||||
if (foreignKeyPrefix != null) {
|
||||
fk = owner.getNamingConvention().getForeignKey(foreignKeyPrefix, fk);
|
||||
}
|
||||
|
||||
if (complexKey) {
|
||||
// just to copy the column name rather than prefix with the foreignKeyPrefix.
|
||||
// I think that with complex keys this is the more common approach.
|
||||
logger.debug("On table[{}] foreign key column [{}]", baseTable, lc);
|
||||
fk = lc;
|
||||
}
|
||||
if (sqlFormulaSelect != null) {
|
||||
fk = sqlFormulaSelect;
|
||||
}
|
||||
|
||||
DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk);
|
||||
joinCol.setForeignSqlFormula(sqlFormulaSelect);
|
||||
if (reverse) {
|
||||
joinCol = joinCol.reverse();
|
||||
}
|
||||
join.addJoinColumn(joinCol);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Chains multiple BeanPersistController's together.
|
||||
*
|
||||
* Used when multiple BeanPersistController register for the same bean type.
|
||||
*/
|
||||
public class ChainedBeanPersistController implements BeanPersistController {
|
||||
|
||||
private static final Sorter SORTER = new Sorter();
|
||||
|
||||
private final List<BeanPersistController> list;
|
||||
|
||||
private final BeanPersistController[] chain;
|
||||
|
||||
/**
|
||||
* Construct adding 2 BeanPersistController's.
|
||||
*/
|
||||
public ChainedBeanPersistController(BeanPersistController c1, BeanPersistController c2) {
|
||||
this(addList(c1, c2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to create a list from 2 BeanPersistController's.
|
||||
*/
|
||||
private static List<BeanPersistController> addList(BeanPersistController c1, BeanPersistController c2) {
|
||||
ArrayList<BeanPersistController> addList = new ArrayList<>(2);
|
||||
addList.add(c1);
|
||||
addList.add(c2);
|
||||
return addList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct given the list of BeanPersistController's.
|
||||
*/
|
||||
public ChainedBeanPersistController(List<BeanPersistController> list) {
|
||||
this.list = list;
|
||||
BeanPersistController[] c = list.toArray(new BeanPersistController[list.size()]);
|
||||
Arrays.sort(c, SORTER);
|
||||
this.chain = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the chain.
|
||||
*/
|
||||
protected int size() {
|
||||
return chain.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new BeanPersistController and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPersistController register(BeanPersistController c) {
|
||||
if (list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanPersistController> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.add(c);
|
||||
|
||||
return new ChainedBeanPersistController(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a BeanPersistController and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPersistController deregister(BeanPersistController c) {
|
||||
if (!list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanPersistController> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.remove(c);
|
||||
|
||||
return new ChainedBeanPersistController(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns 0 (not used for this object).
|
||||
*/
|
||||
@Override
|
||||
public int getExecutionOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns false (not used for this object).
|
||||
*/
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDelete(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
aChain.postDelete(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInsert(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
aChain.postInsert(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
aChain.postUpdate(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSoftDelete(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
aChain.postSoftDelete(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preDelete(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
if (!aChain.preDelete(request)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preSoftDelete(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
if (!aChain.preSoftDelete(request)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
if (!aChain.preInsert(request)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
for (BeanPersistController aChain : chain) {
|
||||
if (!aChain.preUpdate(request)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to order the BeanPersistController's in a chain.
|
||||
*/
|
||||
private static class Sorter implements Comparator<BeanPersistController> {
|
||||
|
||||
public int compare(BeanPersistController o1, BeanPersistController o2) {
|
||||
|
||||
int i1 = o1.getExecutionOrder() ;
|
||||
int i2 = o2.getExecutionOrder() ;
|
||||
return (i1<i2 ? -1 : (i1==i2 ? 0 : 1));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
|
||||
/**
|
||||
* Handles multiple BeanPersistListener's for a given entity type.
|
||||
*/
|
||||
public class ChainedBeanPersistListener implements BeanPersistListener {
|
||||
|
||||
private final List<BeanPersistListener> list;
|
||||
|
||||
private final BeanPersistListener[] chain;
|
||||
|
||||
/**
|
||||
* Construct adding 2 BeanPersistListener's.
|
||||
*/
|
||||
ChainedBeanPersistListener(BeanPersistListener c1, BeanPersistListener c2) {
|
||||
this(addList(c1, c2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the chain.
|
||||
*/
|
||||
protected int size() {
|
||||
return chain.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// never called
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to create a list from 2 BeanPersistListener.
|
||||
*/
|
||||
private static List<BeanPersistListener> addList(BeanPersistListener c1, BeanPersistListener c2) {
|
||||
ArrayList<BeanPersistListener> addList = new ArrayList<>(2);
|
||||
addList.add(c1);
|
||||
addList.add(c2);
|
||||
return addList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct given the list of BeanPersistListener's.
|
||||
*/
|
||||
public ChainedBeanPersistListener(List<BeanPersistListener> list) {
|
||||
this.list = list;
|
||||
this.chain = list.toArray(new BeanPersistListener[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new BeanPersistListener and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPersistListener register(BeanPersistListener c) {
|
||||
if (list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
List<BeanPersistListener> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.add(c);
|
||||
|
||||
return new ChainedBeanPersistListener(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a BeanPersistListener and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPersistListener deregister(BeanPersistListener c) {
|
||||
if (!list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanPersistListener> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.remove(c);
|
||||
|
||||
return new ChainedBeanPersistListener(newList);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleted(Object bean) {
|
||||
for (BeanPersistListener aChain : chain) {
|
||||
aChain.deleted(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void softDeleted(Object bean) {
|
||||
for (BeanPersistListener aChain : chain) {
|
||||
aChain.softDeleted(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void inserted(Object bean) {
|
||||
for (BeanPersistListener aChain : chain) {
|
||||
aChain.inserted(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void updated(Object bean, Set<String> updatedProperties) {
|
||||
for (BeanPersistListener aChain : chain) {
|
||||
aChain.updated(bean, updatedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPostConstructListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles multiple BeanPostLoad's for a given entity type.
|
||||
*/
|
||||
public class ChainedBeanPostConstructListener implements BeanPostConstructListener {
|
||||
|
||||
private final List<BeanPostConstructListener> list;
|
||||
|
||||
private final BeanPostConstructListener[] chain;
|
||||
|
||||
/**
|
||||
* Construct given the list of BeanPostCreate's.
|
||||
*/
|
||||
public ChainedBeanPostConstructListener(List<BeanPostConstructListener> list) {
|
||||
this.list = list;
|
||||
this.chain = list.toArray(new BeanPostConstructListener[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new BeanPostCreate and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPostConstructListener register(BeanPostConstructListener c) {
|
||||
if (list.contains(c)) {
|
||||
return this;
|
||||
} else {
|
||||
List<BeanPostConstructListener> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.add(c);
|
||||
|
||||
return new ChainedBeanPostConstructListener(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a BeanPostCreate and return the resulting chain.
|
||||
*/
|
||||
public BeanPostConstructListener deregister(BeanPostConstructListener c) {
|
||||
if (!list.contains(c)) {
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanPostConstructListener> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.remove(c);
|
||||
|
||||
return new ChainedBeanPostConstructListener(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the chain.
|
||||
*/
|
||||
protected int size() {
|
||||
return chain.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// never called
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire postLoad on all registered BeanPostCreate implementations.
|
||||
*/
|
||||
@Override
|
||||
public void postConstruct(Object bean) {
|
||||
for (BeanPostConstructListener aChain : chain) {
|
||||
aChain.postConstruct(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autowire(Object bean) {
|
||||
for (BeanPostConstructListener aChain : chain) {
|
||||
aChain.autowire(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreate(Object bean) {
|
||||
for (BeanPostConstructListener aChain : chain) {
|
||||
aChain.postCreate(bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPostLoad;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles multiple BeanPostLoad's for a given entity type.
|
||||
*/
|
||||
public class ChainedBeanPostLoad implements BeanPostLoad {
|
||||
|
||||
private final List<BeanPostLoad> list;
|
||||
|
||||
private final BeanPostLoad[] chain;
|
||||
|
||||
/**
|
||||
* Construct given the list of BeanPostLoad's.
|
||||
*/
|
||||
public ChainedBeanPostLoad(List<BeanPostLoad> list) {
|
||||
this.list = list;
|
||||
this.chain = list.toArray(new BeanPostLoad[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new BeanPostLoad and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPostLoad register(BeanPostLoad c) {
|
||||
if (list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
List<BeanPostLoad> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.add(c);
|
||||
|
||||
return new ChainedBeanPostLoad(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a BeanPostLoad and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanPostLoad deregister(BeanPostLoad c) {
|
||||
if (!list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanPostLoad> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.remove(c);
|
||||
|
||||
return new ChainedBeanPostLoad(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the chain.
|
||||
*/
|
||||
protected int size() {
|
||||
return chain.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
// never called
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire postLoad on all registered BeanPostLoad implementations.
|
||||
*/
|
||||
@Override
|
||||
public void postLoad(Object bean) {
|
||||
for (BeanPostLoad aChain : chain) {
|
||||
aChain.postLoad(bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
|
||||
/**
|
||||
* Handles multiple BeanQueryAdapter for a given entity type.
|
||||
*/
|
||||
public class ChainedBeanQueryAdapter implements BeanQueryAdapter {
|
||||
|
||||
private static final Sorter SORTER = new Sorter();
|
||||
|
||||
private final List<BeanQueryAdapter> list;
|
||||
|
||||
private final BeanQueryAdapter[] chain;
|
||||
|
||||
/**
|
||||
* Construct given the list of BeanQueryAdapter's.
|
||||
*/
|
||||
public ChainedBeanQueryAdapter(List<BeanQueryAdapter> list) {
|
||||
this.list = list;
|
||||
BeanQueryAdapter[] c = list.toArray(new BeanQueryAdapter[list.size()]);
|
||||
Arrays.sort(c, SORTER);
|
||||
this.chain = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new BeanQueryAdapter and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanQueryAdapter register(BeanQueryAdapter c) {
|
||||
if (list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
List<BeanQueryAdapter> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.add(c);
|
||||
|
||||
return new ChainedBeanQueryAdapter(newList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a BeanQueryAdapter and return the resulting chain.
|
||||
*/
|
||||
public ChainedBeanQueryAdapter deregister(BeanQueryAdapter c) {
|
||||
if (!list.contains(c)){
|
||||
return this;
|
||||
} else {
|
||||
ArrayList<BeanQueryAdapter> newList = new ArrayList<>();
|
||||
newList.addAll(list);
|
||||
newList.remove(c);
|
||||
|
||||
return new ChainedBeanQueryAdapter(newList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return 0 as not used by this Chained adapter.
|
||||
*/
|
||||
public int getExecutionOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false as only individual adapters are registered.
|
||||
*/
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void preQuery(BeanQueryRequest<?> request) {
|
||||
|
||||
for (BeanQueryAdapter aChain : chain) {
|
||||
aChain.preQuery(request);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper to order the BeanQueryAdapter's in a chain.
|
||||
*/
|
||||
private static class Sorter implements Comparator<BeanQueryAdapter> {
|
||||
|
||||
public int compare(BeanQueryAdapter o1, BeanQueryAdapter o2) {
|
||||
|
||||
int i1 = o1.getExecutionOrder() ;
|
||||
int i2 = o2.getExecutionOrder() ;
|
||||
return (i1<i2 ? -1 : (i1==i2 ? 0 : 1));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
|
||||
/**
|
||||
* Context provided when a BeanProperty reads from a ResultSet.
|
||||
*/
|
||||
public interface DbReadContext {
|
||||
|
||||
/**
|
||||
* Return the state of the object graph.
|
||||
*/
|
||||
Boolean isReadOnly();
|
||||
|
||||
/**
|
||||
* Propagate the state to the bean.
|
||||
*/
|
||||
void propagateState(Object e);
|
||||
|
||||
/**
|
||||
* Return the DataReader.
|
||||
*/
|
||||
DataReader getDataReader();
|
||||
|
||||
/**
|
||||
* Return true if the query is using supplied SQL rather than generated SQL.
|
||||
*/
|
||||
boolean isRawSql();
|
||||
|
||||
/**
|
||||
* Set the JoinNode - used by proxy/reference beans for profiling.
|
||||
*/
|
||||
void setCurrentPrefix(String currentPrefix, Map<String, String> pathMap);
|
||||
|
||||
/**
|
||||
* Return true if we are profiling this query.
|
||||
*/
|
||||
boolean isAutoTuneProfiling();
|
||||
|
||||
/**
|
||||
* Add AutoTune profiling for a loaded entity bean.
|
||||
*/
|
||||
void profileBean(EntityBeanIntercept ebi, String prefix);
|
||||
|
||||
/**
|
||||
* Return the persistence context.
|
||||
*/
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
/**
|
||||
* Register a reference for lazy loading.
|
||||
*/
|
||||
void register(String path, EntityBeanIntercept ebi);
|
||||
|
||||
/**
|
||||
* Register a collection for lazy loading.
|
||||
*/
|
||||
void register(String path, BeanCollection<?> bc);
|
||||
|
||||
/**
|
||||
* Return the property that is associated with the many. There can only be
|
||||
* one. This can be null.
|
||||
*/
|
||||
BeanPropertyAssocMany<?> getManyProperty();
|
||||
|
||||
/**
|
||||
* Set back the bean that has just been loaded with its id.
|
||||
*/
|
||||
void setLazyLoadedChildBean(EntityBean loadedBean, Object parentId);
|
||||
|
||||
/**
|
||||
* Return the query mode.
|
||||
*/
|
||||
SpiQuery.Mode getQueryMode();
|
||||
|
||||
/**
|
||||
* Return true if the underlying query is a 'asDraft' query.
|
||||
*/
|
||||
boolean isDraftQuery();
|
||||
|
||||
/**
|
||||
* Return true if this request disables lazy loading.
|
||||
*/
|
||||
boolean isDisableLazyLoading();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Used to provide context during sql construction.
|
||||
*/
|
||||
public interface DbSqlContext {
|
||||
|
||||
/**
|
||||
* Add a join to the sql query.
|
||||
*/
|
||||
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance);
|
||||
|
||||
void pushSecondaryTableAlias(String alias);
|
||||
|
||||
/**
|
||||
* Push the current table alias onto the stack.
|
||||
*/
|
||||
void pushTableAlias(String tableAlias);
|
||||
|
||||
/**
|
||||
* Pop the current table alias from the stack.
|
||||
*/
|
||||
void popTableAlias();
|
||||
|
||||
/**
|
||||
* Add an encrypted property which will require additional binding.
|
||||
*/
|
||||
void addEncryptedProp(BeanProperty prop);
|
||||
|
||||
/**
|
||||
* Return a list of encrypted properties which require additional binding.
|
||||
*/
|
||||
BeanProperty[] getEncryptedProps();
|
||||
|
||||
/**
|
||||
* Append a char directly to the SQL buffer.
|
||||
*/
|
||||
DbSqlContext append(char s);
|
||||
|
||||
/**
|
||||
* Append a string directly to the SQL buffer.
|
||||
*/
|
||||
DbSqlContext append(String s);
|
||||
|
||||
/**
|
||||
* Peek the current table alias.
|
||||
*/
|
||||
String peekTableAlias();
|
||||
|
||||
/**
|
||||
* Add a raw column to the sql.
|
||||
*/
|
||||
void appendRawColumn(String rawcolumnWithTableAlias);
|
||||
|
||||
/**
|
||||
* Append a column with an explicit table alias.
|
||||
*/
|
||||
void appendColumn(String tableAlias, String column);
|
||||
|
||||
/**
|
||||
* Append a column with the current table alias.
|
||||
*/
|
||||
void appendColumn(String column);
|
||||
|
||||
/**
|
||||
* Append a Sql Formula select. This converts the "${ta}" keyword to the
|
||||
* current table alias.
|
||||
*/
|
||||
void appendFormulaSelect(String sqlFormulaSelect);
|
||||
|
||||
/**
|
||||
* Append a Sql Formula join. This converts the "${ta}" keyword to the current
|
||||
* table alias.
|
||||
*/
|
||||
void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType);
|
||||
|
||||
/**
|
||||
* Return the current content length.
|
||||
*/
|
||||
int length();
|
||||
|
||||
/**
|
||||
* Return the current context of the sql context.
|
||||
*/
|
||||
String getContent();
|
||||
|
||||
/**
|
||||
* Return the current join node.
|
||||
*/
|
||||
String peekJoin();
|
||||
|
||||
/**
|
||||
* Push a join node onto the stack.
|
||||
*/
|
||||
void pushJoin(String prefix);
|
||||
|
||||
/**
|
||||
* Pop a join node off the stack.
|
||||
*/
|
||||
void popJoin();
|
||||
|
||||
/**
|
||||
* Return a table alias without many where clause joins. Typically this is for
|
||||
* the select clause (fetch joins).
|
||||
*/
|
||||
String getTableAlias(String prefix);
|
||||
|
||||
/**
|
||||
* Return a table alias that takes into account many where joins.
|
||||
*/
|
||||
String getTableAliasManyWhere(String prefix);
|
||||
|
||||
String getRelativePrefix(String propName);
|
||||
|
||||
/**
|
||||
* Append the lower and upper bound columns into the select clause
|
||||
* for findVersions() queries.
|
||||
*/
|
||||
void appendHistorySysPeriod();
|
||||
|
||||
/**
|
||||
* Return true if the query is a 'asDraft' query.
|
||||
*/
|
||||
boolean isDraftQuery();
|
||||
|
||||
/**
|
||||
* Start group by clause.
|
||||
*/
|
||||
void startGroupBy();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.annotation.DocCode;
|
||||
import io.ebean.annotation.DocProperty;
|
||||
import io.ebean.annotation.DocSortable;
|
||||
import io.ebeanservice.docstore.api.mapping.DocPropertyOptions;
|
||||
|
||||
/**
|
||||
* The options for document property collected when reading deployment mapping.
|
||||
*/
|
||||
public class DeployDocPropertyOptions {
|
||||
|
||||
private static DocPropertyOptions EMPTY = new DocPropertyOptions();
|
||||
|
||||
private DocPropertyOptions mapping;
|
||||
|
||||
private void createOptions() {
|
||||
if (mapping == null) {
|
||||
mapping = new DocPropertyOptions();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Read the DocProperty deployment options.
|
||||
*/
|
||||
public void setDocProperty(DocProperty doc) {
|
||||
|
||||
createOptions();
|
||||
mapping.apply(doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the DocSortable deployment options.
|
||||
*/
|
||||
public void setDocSortable(DocSortable doc) {
|
||||
|
||||
createOptions();
|
||||
mapping.setSortable(true);
|
||||
setStore(doc.store());
|
||||
setBoost(doc.boost());
|
||||
setNullValue(doc.nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the DocCode deployment options.
|
||||
*/
|
||||
public void setDocCode(DocCode doc) {
|
||||
|
||||
createOptions();
|
||||
mapping.setCode(true);
|
||||
setStore(doc.store());
|
||||
setBoost(doc.boost());
|
||||
setNullValue(doc.nullValue());
|
||||
}
|
||||
|
||||
private void setNullValue(String value) {
|
||||
if (!value.equals("")) {
|
||||
mapping.setNullValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBoost(float boost) {
|
||||
if (boost != 1) {
|
||||
mapping.setBoost(boost);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStore(boolean store) {
|
||||
if (store) {
|
||||
mapping.setStore(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DocPropertyOptions with the collected options.
|
||||
*/
|
||||
public DocPropertyOptions create() {
|
||||
return (mapping == null) ? EMPTY : mapping;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Converts logical property names to database columns.
|
||||
*/
|
||||
public abstract class DeployParser {
|
||||
|
||||
/**
|
||||
* used to identify sql literal.
|
||||
*/
|
||||
protected static final char SINGLE_QUOTE = '\'';
|
||||
|
||||
/**
|
||||
* used to identify query named parameters.
|
||||
*/
|
||||
protected static final char COLON = ':';
|
||||
|
||||
/**
|
||||
* Used to determine when a column name terminates.
|
||||
*/
|
||||
protected static final char UNDERSCORE = '_';
|
||||
|
||||
/**
|
||||
* Used to determine when a column name terminates.
|
||||
*/
|
||||
protected static final char PERIOD = '.';
|
||||
|
||||
protected boolean encrypted;
|
||||
|
||||
protected String source;
|
||||
|
||||
protected StringBuilder sb;
|
||||
|
||||
protected int sourceLength;
|
||||
|
||||
protected int pos;
|
||||
|
||||
protected String word;
|
||||
|
||||
protected char wordTerminator;
|
||||
|
||||
protected abstract String convertWord();
|
||||
|
||||
public abstract String getDeployWord(String expression);
|
||||
|
||||
|
||||
/**
|
||||
* Return the join includes.
|
||||
*/
|
||||
public abstract Set<String> getIncludes();
|
||||
|
||||
public void setEncrypted(boolean encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public String parse(String source) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
pos = -1;
|
||||
this.source = source;
|
||||
this.sourceLength = source.length();
|
||||
this.sb = new StringBuilder(source.length() + 20);
|
||||
|
||||
while (nextWord()) {
|
||||
String deployWord = convertWord();
|
||||
sb.append(deployWord);
|
||||
if (pos < sourceLength) {
|
||||
sb.append(wordTerminator);
|
||||
if (wordTerminator == SINGLE_QUOTE) {
|
||||
readLiteral();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean nextWord() {
|
||||
|
||||
if (!findWordStart()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder wordBuffer = new StringBuilder();
|
||||
wordBuffer.append(source.charAt(pos));
|
||||
while (++pos < sourceLength) {
|
||||
char ch = source.charAt(pos);
|
||||
if (isWordPart(ch)) {
|
||||
wordBuffer.append(ch);
|
||||
} else {
|
||||
wordTerminator = ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
word = wordBuffer.toString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean findWordStart() {
|
||||
while (++pos < sourceLength) {
|
||||
char ch = source.charAt(pos);
|
||||
if (ch == SINGLE_QUOTE) {
|
||||
// read a literal value and just
|
||||
// append to string builder
|
||||
sb.append(ch);
|
||||
readLiteral();
|
||||
} else if (ch == COLON) {
|
||||
// read a named parameter
|
||||
// just append to string builder
|
||||
sb.append(ch);
|
||||
readNamedParameter();
|
||||
} else if (isWordStart(ch)) {
|
||||
// its the start of a word that could
|
||||
// be translated
|
||||
return true;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the rest of a literal value. These do not get translated so are just
|
||||
* read and appended to the string builder.
|
||||
*/
|
||||
private void readLiteral() {
|
||||
while (++pos < sourceLength) {
|
||||
char ch = source.charAt(pos);
|
||||
sb.append(ch);
|
||||
if (ch == SINGLE_QUOTE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a named parameter. These are not translated. They will be replaced
|
||||
* by positioned parameters later.
|
||||
*/
|
||||
private void readNamedParameter() {
|
||||
while (++pos < sourceLength) {
|
||||
char ch = source.charAt(pos);
|
||||
sb.append(ch);
|
||||
if (Character.isWhitespace(ch)) {
|
||||
break;
|
||||
} else if (ch == ',') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if the char is a letter, digit or underscore.
|
||||
*/
|
||||
private boolean isWordPart(char ch) {
|
||||
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD;
|
||||
}
|
||||
|
||||
private boolean isWordStart(char ch) {
|
||||
return Character.isLetter(ch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
|
||||
/**
|
||||
* Converts logical property names to database columns with table alias.
|
||||
* <p>
|
||||
* In doing so it builds an 'includes' set which becomes the joins required to
|
||||
* support the properties parsed.
|
||||
* </p>
|
||||
*/
|
||||
public final class DeployPropertyParser extends DeployParser {
|
||||
|
||||
|
||||
private final BeanDescriptor<?> beanDescriptor;
|
||||
|
||||
private final Set<String> includes = new HashSet<>();
|
||||
|
||||
public DeployPropertyParser(BeanDescriptor<?> beanDescriptor) {
|
||||
this.beanDescriptor = beanDescriptor;
|
||||
}
|
||||
|
||||
public Set<String> getIncludes() {
|
||||
return includes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeployWord(String expression) {
|
||||
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(expression);
|
||||
if (elProp == null){
|
||||
return null;
|
||||
} else {
|
||||
addIncludes(elProp.getElPrefix());
|
||||
return elProp.getElPlaceholder(encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertWord() {
|
||||
String r = getDeployWord(word);
|
||||
return r == null ? word : r;
|
||||
}
|
||||
|
||||
private void addIncludes(String prefix) {
|
||||
if (prefix != null){
|
||||
includes.add(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Converts logical property names to database columns using a Map.
|
||||
*/
|
||||
public final class DeployPropertyParserMap extends DeployParser {
|
||||
|
||||
private final Map<String, String> map;
|
||||
|
||||
public DeployPropertyParserMap(Map<String, String> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null for raw sql queries.
|
||||
*/
|
||||
public Set<String> getIncludes() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public String convertWord() {
|
||||
String r = getDeployWord(word);
|
||||
return r == null ? word : r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeployWord(String expression) {
|
||||
|
||||
String deployExpr = map.get(expression);
|
||||
if (deployExpr == null) {
|
||||
return null;
|
||||
} else {
|
||||
return deployExpr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
|
||||
/**
|
||||
* For updates converts logical property names to database columns and bean type to base table.
|
||||
*/
|
||||
public final class DeployUpdateParser extends DeployParser {
|
||||
|
||||
private final BeanDescriptor<?> beanDescriptor;
|
||||
|
||||
public DeployUpdateParser(BeanDescriptor<?> beanDescriptor) {
|
||||
this.beanDescriptor = beanDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null as not used for updates.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getIncludes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String convertWord() {
|
||||
|
||||
String dbWord = getDeployWord(word);
|
||||
|
||||
if (dbWord != null) {
|
||||
return dbWord;
|
||||
}
|
||||
// maybe tableAlias.propertyName
|
||||
return convertSubword(0, word, null);
|
||||
}
|
||||
|
||||
private String convertSubword(int start, String currentWord, StringBuilder localBuffer) {
|
||||
|
||||
int dotPos = currentWord.indexOf('.', start);
|
||||
if (start == 0 && dotPos == -1){
|
||||
return currentWord;
|
||||
}
|
||||
if (start == 0){
|
||||
localBuffer = new StringBuilder();
|
||||
}
|
||||
if (dotPos == -1){
|
||||
// no match...
|
||||
localBuffer.append(currentWord.substring(start));
|
||||
return localBuffer.toString();
|
||||
}
|
||||
|
||||
// append up to the dot
|
||||
localBuffer.append(currentWord.substring(start, dotPos+1));
|
||||
|
||||
if (dotPos == currentWord.length()-1){
|
||||
// ends with a "." ???
|
||||
return localBuffer.toString();
|
||||
}
|
||||
|
||||
// get the remainder after the dot
|
||||
start = dotPos+1;
|
||||
String remainder = currentWord.substring(start, currentWord.length());
|
||||
|
||||
//String dbWord = deployMap.get(remainder.toLowerCase());
|
||||
String dbWord = getDeployWord(remainder);
|
||||
if (dbWord != null){
|
||||
// we have found a match for the remainder
|
||||
localBuffer.append(dbWord);
|
||||
return localBuffer.toString();
|
||||
} else {
|
||||
//
|
||||
return convertSubword(start, currentWord, localBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDeployWord(String expression) {
|
||||
|
||||
if (expression.equalsIgnoreCase(beanDescriptor.getName())){
|
||||
return beanDescriptor.getBaseTable();
|
||||
}
|
||||
|
||||
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(expression);
|
||||
if (elProp != null){
|
||||
return elProp.getDbColumn();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
|
||||
class DetermineAggPath {
|
||||
|
||||
/**
|
||||
* Return the many path for the given aggregation formula.
|
||||
*/
|
||||
static String manyPath(String aggregation, DeployBeanDescriptor<?> desc) {
|
||||
DetermineAggPath.Path path = paths(aggregation);
|
||||
return path.getManyPath(0, desc);
|
||||
}
|
||||
|
||||
static Path paths(String aggregation) {
|
||||
String aggPath = path(aggregation);
|
||||
return new Path(aggPath.split("\\."), aggregation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return the full path for the aggregation.
|
||||
*/
|
||||
static String path(String aggregation) {
|
||||
|
||||
// aggregations always have a form of sum(), avg(), max(), count() etc
|
||||
// so find the first open bracket
|
||||
int start = aggregation.indexOf('(');
|
||||
if (start == -1) {
|
||||
throw new IllegalArgumentException("Aggregation formula ["+aggregation+"] is expected to have a '(' ?");
|
||||
}
|
||||
for (int i = start + 1; i< aggregation.length(); i++) {
|
||||
char ch = aggregation.charAt(i);
|
||||
if (!isNamePart(ch)) {
|
||||
return aggregation.substring(start + 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Could not find path in aggregation formula ["+aggregation+"]");
|
||||
}
|
||||
|
||||
private static boolean isNamePart(char ch) {
|
||||
return ch == '.' || Character.isJavaIdentifierPart(ch);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class holding aggregation path segments.
|
||||
*/
|
||||
static class Path {
|
||||
|
||||
final String aggregation;
|
||||
final String[] paths;
|
||||
|
||||
Path(String[] paths, String aggregation) {
|
||||
this.paths = paths;
|
||||
this.aggregation = aggregation;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return paths.length;
|
||||
}
|
||||
|
||||
String path(int pos) {
|
||||
if (pos == 0) {
|
||||
return paths[0];
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < pos; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(paths[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
String getManyPath(int pos, DeployBeanDescriptor<?> desc) {
|
||||
|
||||
String path = paths[pos];
|
||||
DeployBeanProperty details = desc.getBeanProperty(path);
|
||||
if (details instanceof DeployBeanPropertyAssocMany<?>) {
|
||||
return path(pos);
|
||||
|
||||
} else if (details instanceof DeployBeanPropertyAssocOne<?>) {
|
||||
DeployBeanPropertyAssocOne<?> one = (DeployBeanPropertyAssocOne<?>)details;
|
||||
DeployBeanDescriptor<?> targetDesc = one.getTargetDeploy();
|
||||
return getManyPath(pos + 1, targetDesc);
|
||||
}
|
||||
throw new IllegalArgumentException("Can not find path to many in aggregation formula ["+aggregation+"]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Determine the Many Type for a property.
|
||||
*/
|
||||
public class DetermineManyType {
|
||||
|
||||
public DetermineManyType() {
|
||||
}
|
||||
|
||||
public ManyType getManyType(Class<?> type) {
|
||||
if (type.equals(List.class)) {
|
||||
return ManyType.LIST;
|
||||
}
|
||||
if (type.equals(Set.class)) {
|
||||
return ManyType.SET;
|
||||
}
|
||||
if (type.equals(Map.class)) {
|
||||
return ManyType.MAP;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
|
||||
/**
|
||||
* The Exported foreign key and property.
|
||||
* <p>
|
||||
* Used to for Assoc Manys to create references etc.
|
||||
* </p>
|
||||
*/
|
||||
public class ExportedProperty {
|
||||
|
||||
private final String foreignDbColumn;
|
||||
|
||||
private final BeanProperty property;
|
||||
|
||||
private final boolean embedded;
|
||||
|
||||
public ExportedProperty(boolean embedded, String foreignDbColumn, BeanProperty property) {
|
||||
this.embedded = embedded;
|
||||
this.foreignDbColumn = InternString.intern(foreignDbColumn);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is part of an embedded concatinated key.
|
||||
*/
|
||||
public boolean isEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value from the bean.
|
||||
*/
|
||||
public Object getValue(EntityBean bean) {
|
||||
return property.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign database column matching this property.
|
||||
* <p>
|
||||
* We use this foreign database column in the query predicates
|
||||
* in preference to a parentProperty.idProperty = value.
|
||||
* Just using the foreign database column avoids triggering
|
||||
* a join to the 'parent' table.
|
||||
* </p>
|
||||
*/
|
||||
public String getForeignDbColumn() {
|
||||
return foreignDbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a logical where for the foreign db column to logical property name,
|
||||
*/
|
||||
public void appendWhere(StringBuilder sb, String path) {
|
||||
sb.append(foreignDbColumn).append(" = ");
|
||||
if (path != null) {
|
||||
sb.append(path).append(".");
|
||||
}
|
||||
sb.append(property.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
/**
|
||||
* Holds multiple column unique constraints defined for an entity.
|
||||
*/
|
||||
public class IndexDefinition {
|
||||
|
||||
private final String[] columns;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean unique;
|
||||
|
||||
/**
|
||||
* A single column index.
|
||||
*/
|
||||
public IndexDefinition(String column, String name, boolean unique) {
|
||||
this.columns = new String[]{column};
|
||||
this.unique = unique;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public IndexDefinition(String[] columns, String name, boolean unique) {
|
||||
this.columns = columns;
|
||||
this.unique = unique;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique constraint given the column names.
|
||||
*/
|
||||
public IndexDefinition(String[] columns) {
|
||||
this.columns = columns;
|
||||
this.unique = true;
|
||||
this.name = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a unique constraint.
|
||||
*/
|
||||
public boolean isUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name (can be null).
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns that make up this unique constraint.
|
||||
*/
|
||||
public String[] getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployInheritInfo;
|
||||
import io.ebeaninternal.server.query.SqlTreeProperties;
|
||||
|
||||
/**
|
||||
* Represents a node in the Inheritance tree. Holds information regarding Super Subclass support.
|
||||
*/
|
||||
public class InheritInfo {
|
||||
|
||||
private final String discriminatorStringValue;
|
||||
private final Object discriminatorValue;
|
||||
|
||||
private final String discriminatorColumn;
|
||||
|
||||
private final int discriminatorType;
|
||||
|
||||
private final int discriminatorLength;
|
||||
|
||||
private final String where;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final ArrayList<InheritInfo> children = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Map of discriminator values to InheritInfo.
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> discMap;
|
||||
|
||||
/**
|
||||
* Map of class types to InheritInfo (taking into account subclass proxy classes).
|
||||
*/
|
||||
private final HashMap<String, InheritInfo> typeMap;
|
||||
|
||||
private final InheritInfo parent;
|
||||
|
||||
private final InheritInfo root;
|
||||
|
||||
private BeanDescriptor<?> descriptor;
|
||||
|
||||
public InheritInfo(InheritInfo r, InheritInfo parent, DeployInheritInfo deploy) {
|
||||
|
||||
this.parent = parent;
|
||||
this.type = deploy.getType();
|
||||
this.discriminatorColumn = InternString.intern(deploy.getDiscriminatorColumn(parent));
|
||||
this.discriminatorValue = deploy.getDiscriminatorObjectValue();
|
||||
this.discriminatorStringValue = deploy.getDiscriminatorStringValue();
|
||||
|
||||
this.discriminatorType = deploy.getDiscriminatorType(parent);
|
||||
this.discriminatorLength = deploy.getDiscriminatorLength(parent);
|
||||
this.where = InternString.intern(deploy.getWhere());
|
||||
|
||||
if (r == null) {
|
||||
// this is a root node
|
||||
root = this;
|
||||
discMap = new HashMap<>();
|
||||
typeMap = new HashMap<>();
|
||||
registerWithRoot(this);
|
||||
|
||||
} else {
|
||||
this.root = r;
|
||||
// register with the root node...
|
||||
discMap = null;
|
||||
typeMap = null;
|
||||
root.registerWithRoot(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the children in the inheritance tree.
|
||||
*/
|
||||
public void visitChildren(InheritInfoVisitor visitor) {
|
||||
|
||||
for (InheritInfo child : children) {
|
||||
visitor.visit(child);
|
||||
child.visitChildren(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append check constraint values for the entire inheritance hierarchy.
|
||||
*/
|
||||
public void appendCheckConstraintValues(final String propertyName, final Set<String> checkConstraintValues) {
|
||||
|
||||
visitChildren(inheritInfo -> {
|
||||
BeanProperty prop = inheritInfo.desc().getBeanProperty(propertyName);
|
||||
if (prop != null) {
|
||||
Set<String> values = prop.getDbCheckConstraintValues();
|
||||
if (values != null) {
|
||||
checkConstraintValues.addAll(values);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship with a save cascade on
|
||||
* it.
|
||||
*/
|
||||
public boolean isSaveRecurseSkippable() {
|
||||
return root.isNodeSaveRecurseSkippable();
|
||||
}
|
||||
|
||||
private boolean isNodeSaveRecurseSkippable() {
|
||||
if (!descriptor.isSaveRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
for (InheritInfo child : children) {
|
||||
if (!child.isNodeSaveRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if anything in the inheritance hierarchy has a relationship with a delete cascade
|
||||
* on it.
|
||||
*/
|
||||
public boolean isDeleteRecurseSkippable() {
|
||||
return root.isNodeDeleteRecurseSkippable();
|
||||
}
|
||||
|
||||
private boolean isNodeDeleteRecurseSkippable() {
|
||||
if (!descriptor.isDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
for (InheritInfo child : children) {
|
||||
if (!child.isNodeDeleteRecurseSkippable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the descriptor for this node.
|
||||
*/
|
||||
public void setDescriptor(BeanDescriptor<?> descriptor) {
|
||||
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated BeanDescriptor for this node.
|
||||
*/
|
||||
public BeanDescriptor<?> desc() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the local properties for this node in the hierarchy.
|
||||
*/
|
||||
public BeanProperty[] localProperties() {
|
||||
return descriptor.propertiesLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean property additionally looking in the sub types.
|
||||
*/
|
||||
public BeanProperty findSubTypeProperty(String propertyName) {
|
||||
|
||||
BeanProperty prop;
|
||||
|
||||
for (InheritInfo childInfo : children) {
|
||||
// recursively search this child bean descriptor
|
||||
prop = childInfo.desc().findBeanProperty(propertyName);
|
||||
if (prop != null) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the local properties for each sub class below this one.
|
||||
*/
|
||||
public void addChildrenProperties(SqlTreeProperties selectProps) {
|
||||
|
||||
for (InheritInfo childInfo : children) {
|
||||
selectProps.add(childInfo.descriptor.propertiesLocal());
|
||||
|
||||
childInfo.addChildrenProperties(selectProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this DB row read.
|
||||
*/
|
||||
public InheritInfo readType(DbReadContext ctx) throws SQLException {
|
||||
|
||||
String discValue = ctx.getDataReader().getString();
|
||||
return readType(discValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this discriminator value.
|
||||
*/
|
||||
public InheritInfo readType(String discValue) {
|
||||
|
||||
if (discValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InheritInfo typeInfo = root.getType(discValue);
|
||||
if (typeInfo == null) {
|
||||
throw new PersistenceException("Inheritance type for discriminator value [" + discValue + "] was not found?");
|
||||
}
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated InheritInfo for this bean type.
|
||||
*/
|
||||
public InheritInfo readType(Class<?> beanType) {
|
||||
|
||||
InheritInfo typeInfo = root.getTypeByClass(beanType);
|
||||
if (typeInfo == null) {
|
||||
throw new PersistenceException("Inheritance type for bean type [" + beanType.getName() + "] was not found?");
|
||||
}
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EntityBean for this type.
|
||||
*/
|
||||
public EntityBean createEntityBean() {
|
||||
return descriptor.createEntityBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdBinder for this type.
|
||||
*/
|
||||
public IdBinder getIdBinder() {
|
||||
return descriptor.getIdBinder();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the type.
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root node of the tree.
|
||||
* <p>
|
||||
* The root has a map of discriminator values to types.
|
||||
* </p>
|
||||
*/
|
||||
public InheritInfo getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent node.
|
||||
*/
|
||||
public InheritInfo getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is the root node.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return parent == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a discriminator get the inheritance information for this tree.
|
||||
*/
|
||||
public InheritInfo getType(String discValue) {
|
||||
return discMap.get(discValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the InheritInfo for the given bean type.
|
||||
*/
|
||||
private InheritInfo getTypeByClass(Class<?> beanType) {
|
||||
return typeMap.get(beanType.getName());
|
||||
}
|
||||
|
||||
private void registerWithRoot(InheritInfo info) {
|
||||
if (info.getDiscriminatorStringValue() != null) {
|
||||
String stringDiscValue = info.getDiscriminatorStringValue();
|
||||
discMap.put(stringDiscValue, info);
|
||||
}
|
||||
typeMap.put(info.getType().getName(), info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child node.
|
||||
*/
|
||||
public void addChild(InheritInfo childInfo) {
|
||||
children.add(childInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the derived where for the discriminator.
|
||||
*/
|
||||
public String getWhere() {
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column name of the discriminator.
|
||||
*/
|
||||
public String getDiscriminatorColumn() {
|
||||
return discriminatorColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sql type of the discriminator value.
|
||||
*/
|
||||
public int getDiscriminatorType() {
|
||||
return discriminatorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the discriminator column.
|
||||
*/
|
||||
public int getDiscriminatorLength() {
|
||||
return discriminatorLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the discriminator value for this node.
|
||||
*/
|
||||
public String getDiscriminatorStringValue() {
|
||||
return discriminatorStringValue;
|
||||
}
|
||||
|
||||
public Object getDiscriminatorValue() {
|
||||
return discriminatorValue;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "InheritInfo[" + type.getName() + "] disc[" + discriminatorStringValue + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
/**
|
||||
* Used to visit all the InheritInfo in a single inheritance hierarchy.
|
||||
*/
|
||||
public interface InheritInfoVisitor {
|
||||
|
||||
/**
|
||||
* visit the InheritInfo for this node.
|
||||
*/
|
||||
void visit(InheritInfo inheritInfo);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebeaninternal.api.BindParams;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.expression.IdInExpression;
|
||||
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
|
||||
|
||||
public class IntersectionRow {
|
||||
|
||||
private final String tableName;
|
||||
|
||||
private final BeanDescriptor<?> targetDescriptor;
|
||||
|
||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
|
||||
|
||||
private ArrayList<Object> excludeIds;
|
||||
private BeanDescriptor<?> excludeDescriptor;
|
||||
|
||||
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = targetDescriptor;
|
||||
}
|
||||
|
||||
public IntersectionRow(String tableName) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Id's to exclude. This is for deleting non-attached detail Id's.
|
||||
*/
|
||||
public void setExcludeIds(ArrayList<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
|
||||
this.excludeIds = excludeIds;
|
||||
this.excludeDescriptor = excludeDescriptor;
|
||||
}
|
||||
|
||||
public void put(String key, Object value) {
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
public SqlUpdate createInsert(EbeanServer server) {
|
||||
|
||||
BindParams bindParams = new BindParams();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("insert into ").append(tableName).append(" (");
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(entry.getKey());
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
}
|
||||
|
||||
sb.append(") values (");
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("?");
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
|
||||
|
||||
BindParams bindParams = new BindParams();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (softDelete) {
|
||||
sb.append("update ").append(tableName).append(" set ");
|
||||
sb.append(targetDescriptor.getSoftDeleteDbSet());
|
||||
} else {
|
||||
sb.append("delete from ").append(tableName);
|
||||
}
|
||||
sb.append(" where ");
|
||||
|
||||
int count = setBindParams(bindParams, sb);
|
||||
|
||||
if (excludeIds != null) {
|
||||
IdInExpression idIn = new IdInExpression(excludeIds);
|
||||
|
||||
DefaultExpressionRequest er = new DefaultExpressionRequest(excludeDescriptor);
|
||||
idIn.addSqlNoAlias(er);
|
||||
idIn.addBindValues(er);
|
||||
|
||||
sb.append(" and not ( ");
|
||||
sb.append(er.getSql());
|
||||
sb.append(" ) ");
|
||||
|
||||
List<Object> bindValues = er.getBindValues();
|
||||
for (Object bindValue : bindValues) {
|
||||
bindParams.setParameter(++count, bindValue);
|
||||
}
|
||||
}
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
public SqlUpdate createDeleteChildren(EbeanServer server) {
|
||||
|
||||
BindParams bindParams = new BindParams();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("delete from ").append(tableName).append(" where ");
|
||||
|
||||
setBindParams(bindParams, sb);
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
private int setBindParams(BindParams bindParams, StringBuilder sb) {
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
|
||||
sb.append(entry.getKey());
|
||||
sb.append(" = ?");
|
||||
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the type of a OneToMany or ManyToMany property.
|
||||
*/
|
||||
public enum ManyType {
|
||||
|
||||
LIST(false, List.class),
|
||||
SET(false, Set.class),
|
||||
MAP(true, null);
|
||||
|
||||
|
||||
private final boolean map;
|
||||
|
||||
private final Class<? extends Collection> type;
|
||||
|
||||
ManyType(boolean map, Class<? extends Collection> type) {
|
||||
this.map = map;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns List.class or Set.class and null for Map.
|
||||
* Not intended to be called for maps.
|
||||
*/
|
||||
public Class<? extends Collection> getCollectionType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Helper object to find generic parameter types for a given class.
|
||||
*/
|
||||
public class ParamTypeUtil {
|
||||
|
||||
/**
|
||||
* Find and return the parameter type given a generic interface or class.
|
||||
* <p>
|
||||
* This assumes there is only one generic parameter.
|
||||
* </p>
|
||||
* <p>
|
||||
* Returns null if no match was found.
|
||||
* </p>
|
||||
* @param cls the class to search for the parameter type
|
||||
* @param matchType the type which has the generic parameter
|
||||
*/
|
||||
public static Class<?> findParamType(Class<?> cls, Class<?> matchType) {
|
||||
|
||||
// search for: implementing a generic interface
|
||||
Type paramType = matchByInterfaces(cls, matchType);
|
||||
if (paramType == null){
|
||||
// search for: extending a generic class
|
||||
Type genericSuperclass = cls.getGenericSuperclass();
|
||||
if (genericSuperclass != null){
|
||||
paramType = matchParamType(genericSuperclass, matchType);
|
||||
}
|
||||
}
|
||||
|
||||
if (paramType instanceof Class<?>){
|
||||
// only interested in classes
|
||||
return (Class<?>)paramType;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the type is a generic one with parameters and of the correct type we are
|
||||
* searching for. Return the parameter type if this matches otherwise return null.
|
||||
*/
|
||||
private static Type matchParamType(Type type, Class<?> matchType) {
|
||||
if (type instanceof ParameterizedType){
|
||||
ParameterizedType pt = (ParameterizedType)type;
|
||||
Type rawType = pt.getRawType();
|
||||
boolean isAssignable = matchType.isAssignableFrom((Class<?>) rawType);
|
||||
if (isAssignable) {
|
||||
// assume there is only one parameter type
|
||||
Type[] typeArguments = pt.getActualTypeArguments();
|
||||
if (typeArguments.length != 1){
|
||||
String m = "Expecting only 1 generic paramater but got "+typeArguments.length+" for "+type;
|
||||
throw new RuntimeException(m);
|
||||
}
|
||||
return typeArguments[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the interfaces this class implements.
|
||||
*/
|
||||
private static Type matchByInterfaces(Class<?> cls, Class<?> matchType) {
|
||||
|
||||
Type[] gis = cls.getGenericInterfaces();
|
||||
for (Type gi : gis) {
|
||||
Type match = matchParamType(gi, matchType);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Default implementation for creating BeanControllers.
|
||||
*/
|
||||
public class PersistControllerManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PersistControllerManager.class);
|
||||
|
||||
private final List<BeanPersistController> list;
|
||||
|
||||
public PersistControllerManager(BootupClasses bootupClasses) {
|
||||
|
||||
list = bootupClasses.getBeanPersistControllers();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanPersistController for a given entity type.
|
||||
*/
|
||||
public void addPersistControllers(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
for (BeanPersistController c : list) {
|
||||
if (c.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanPersistController on[" + deployDesc.getFullName() + "] " + c.getClass().getName());
|
||||
deployDesc.addPersistController(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Manages the assignment/registration of BeanPersistListener with their
|
||||
* respective DeployBeanDescriptor's.
|
||||
*/
|
||||
public class PersistListenerManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PersistListenerManager.class);
|
||||
|
||||
private final List<BeanPersistListener> list;
|
||||
|
||||
public PersistListenerManager(BootupClasses bootupClasses) {
|
||||
list = bootupClasses.getBeanPersistListeners();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanPersistController for a given entity type.
|
||||
*/
|
||||
public <T> void addPersistListeners(DeployBeanDescriptor<T> deployDesc) {
|
||||
|
||||
for (BeanPersistListener listener : list) {
|
||||
if (listener.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanPersistListener on[{}] {}", deployDesc.getFullName(), listener.getClass().getName());
|
||||
deployDesc.addPersistListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPostConstructListener;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation for creating BeanControllers.
|
||||
*/
|
||||
public class PostConstructManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PostConstructManager.class);
|
||||
|
||||
private final List<BeanPostConstructListener> list;
|
||||
|
||||
public PostConstructManager(BootupClasses bootupClasses) {
|
||||
this.list = bootupClasses.getBeanPostConstructoListeners();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register BeanPostLoad listeners for a given entity type.
|
||||
*/
|
||||
public void addPostConstructListeners(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
for (BeanPostConstructListener c : list) {
|
||||
if (c.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanPostLoad on[" + deployDesc.getFullName() + "] " + c.getClass().getName());
|
||||
deployDesc.addPostConstructListener(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.event.BeanPostLoad;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation for creating BeanControllers.
|
||||
*/
|
||||
public class PostLoadManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PostLoadManager.class);
|
||||
|
||||
private final List<BeanPostLoad> list;
|
||||
|
||||
public PostLoadManager(BootupClasses bootupClasses) {
|
||||
this.list = bootupClasses.getBeanPostLoaders();
|
||||
}
|
||||
|
||||
public int getRegisterCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register BeanPostLoad listeners for a given entity type.
|
||||
*/
|
||||
public void addPostLoad(DeployBeanDescriptor<?> deployDesc) {
|
||||
|
||||
for (BeanPostLoad c : list) {
|
||||
if (c.isRegisterFor(deployDesc.getBeanType())) {
|
||||
logger.debug("BeanPostLoad on[" + deployDesc.getFullName() + "] " + c.getClass().getName());
|
||||
deployDesc.addPostLoad(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Represents a join to another table.
|
||||
*/
|
||||
public final class TableJoin {
|
||||
|
||||
/**
|
||||
* The joined table.
|
||||
*/
|
||||
private final String table;
|
||||
|
||||
/**
|
||||
* The type of join as per deployment (cardinality and optionality).
|
||||
*/
|
||||
private final SqlJoinType type;
|
||||
|
||||
private final InheritInfo inheritInfo;
|
||||
|
||||
/**
|
||||
* Columns as an array.
|
||||
*/
|
||||
private final TableJoinColumn[] columns;
|
||||
|
||||
/**
|
||||
* A hash that can be used with the query plan.
|
||||
*/
|
||||
private final int queryHash;
|
||||
|
||||
/**
|
||||
* Create a TableJoin.
|
||||
*/
|
||||
public TableJoin(DeployTableJoin deploy) {
|
||||
|
||||
this.table = InternString.intern(deploy.getTable());
|
||||
this.type = deploy.getType();
|
||||
this.inheritInfo = deploy.getInheritInfo();
|
||||
|
||||
DeployTableJoinColumn[] deployCols = deploy.columns();
|
||||
this.columns = new TableJoinColumn[deployCols.length];
|
||||
for (int i = 0; i < deployCols.length; i++) {
|
||||
this.columns[i] = new TableJoinColumn(deployCols[i]);
|
||||
}
|
||||
|
||||
this.queryHash = calcQueryHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a hash value for adding to a query plan.
|
||||
*/
|
||||
private int calcQueryHash() {
|
||||
int hc = type.hashCode();
|
||||
hc = hc * 92821 + (table == null ? 0 : table.hashCode());
|
||||
for (TableJoinColumn column : columns) {
|
||||
hc = hc * 92821 + column.queryHash();
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TableJoin that = (TableJoin) o;
|
||||
|
||||
if (!table.equals(that.table)) return false;
|
||||
if (type != that.type) return false;
|
||||
if (columns.length != that.columns.length) return false;
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (!columns[i].equals(that.columns[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a hash value for adding to a query plan.
|
||||
*/
|
||||
public int queryHash() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
sb.append(type).append(" ").append(table).append(" ");
|
||||
for (TableJoinColumn column : columns) {
|
||||
sb.append(column).append(" ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the join columns.
|
||||
*/
|
||||
public TableJoinColumn[] columns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the joined table name.
|
||||
*/
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of join. LEFT JOIN etc.
|
||||
*/
|
||||
public SqlJoinType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
|
||||
|
||||
String[] names = SplitName.split(prefix);
|
||||
String a1 = ctx.getTableAlias(names[0]);
|
||||
String a2 = ctx.getTableAlias(prefix);
|
||||
|
||||
return addJoin(joinType, a1, a2, ctx);
|
||||
}
|
||||
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
|
||||
|
||||
String inheritance = inheritInfo != null ? inheritInfo.getWhere() : null;
|
||||
|
||||
String joinLiteral = joinType.getLiteral(type);
|
||||
ctx.addJoin(joinLiteral, table, columns(), a1, a2, inheritance);
|
||||
|
||||
return joinType.autoToOuter(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
|
||||
|
||||
/**
|
||||
* A join pair of local and foreign properties.
|
||||
*/
|
||||
public class TableJoinColumn {
|
||||
|
||||
/**
|
||||
* The local database column name.
|
||||
*/
|
||||
private final String localDbColumn;
|
||||
|
||||
private final String localSqlFormula;
|
||||
|
||||
/**
|
||||
* The foreign database column name.
|
||||
*/
|
||||
private final String foreignDbColumn;
|
||||
|
||||
private final String foreignSqlFormula;
|
||||
|
||||
private final boolean insertable;
|
||||
|
||||
private final boolean updateable;
|
||||
|
||||
|
||||
/**
|
||||
* Hash for including in a query plan
|
||||
*/
|
||||
private final int queryHash;
|
||||
|
||||
/**
|
||||
* Create the pair.
|
||||
*/
|
||||
public TableJoinColumn(DeployTableJoinColumn deploy) {
|
||||
this.localDbColumn = InternString.intern(deploy.getLocalDbColumn());
|
||||
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
|
||||
this.localSqlFormula = InternString.intern(deploy.getLocalSqlFormula());
|
||||
this.foreignSqlFormula = InternString.intern(deploy.getForeignSqlFormula());
|
||||
|
||||
this.insertable = deploy.isInsertable();
|
||||
this.updateable = deploy.isUpdateable();
|
||||
this.queryHash = hash();
|
||||
}
|
||||
|
||||
int hash() {
|
||||
int result = localDbColumn != null ? localDbColumn.hashCode() : 0;
|
||||
result = 92821 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0);
|
||||
result = 92821 * result + (localSqlFormula != null ? localSqlFormula.hashCode() : 0);
|
||||
result = 92821 * result + (foreignSqlFormula != null ? foreignSqlFormula.hashCode() : 0);
|
||||
result = 92821 * result + (insertable ? 1 : 0);
|
||||
result = 92821 * result + (updateable ? 1 : 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TableJoinColumn that = (TableJoinColumn) o;
|
||||
if (insertable != that.insertable) return false;
|
||||
if (updateable != that.updateable) return false;
|
||||
if (!localDbColumn.equals(that.localDbColumn)) return false;
|
||||
return foreignDbColumn.equals(that.foreignDbColumn);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return (localSqlFormula == null ? localDbColumn : localSqlFormula) + " = "
|
||||
+ (foreignSqlFormula == null ? foreignDbColumn : foreignSqlFormula);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash for including in a query plan.
|
||||
*/
|
||||
public int queryHash() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign database column name.
|
||||
*/
|
||||
public String getForeignDbColumn() {
|
||||
return foreignDbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the local database column name.
|
||||
*/
|
||||
public String getLocalDbColumn() {
|
||||
return localDbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this column should be insertable.
|
||||
*/
|
||||
public boolean isInsertable() {
|
||||
return insertable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this column should be updateable.
|
||||
*/
|
||||
public boolean isUpdateable() {
|
||||
return updateable;
|
||||
}
|
||||
|
||||
public String getLocalSqlFormula() {
|
||||
return localSqlFormula;
|
||||
}
|
||||
|
||||
public String getForeignSqlFormula() {
|
||||
return foreignSqlFormula;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Creates "Counter" GeneratedProperty for various types of number.
|
||||
* <p>
|
||||
* Aka, Integer, Long, Short etc.
|
||||
* </p>
|
||||
*/
|
||||
public class CounterFactory {
|
||||
|
||||
final GeneratedCounterInteger integerCounter = new GeneratedCounterInteger();
|
||||
|
||||
final GeneratedCounterLong longCounter = new GeneratedCounterLong();
|
||||
|
||||
public void setCounter(DeployBeanProperty property) {
|
||||
|
||||
property.setGeneratedProperty(createCounter(property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the GeneratedProperty based on the property type.
|
||||
*/
|
||||
private GeneratedProperty createCounter(DeployBeanProperty property) {
|
||||
|
||||
Class<?> propType = property.getPropertyType();
|
||||
if (propType.equals(Integer.class) || propType.equals(int.class)) {
|
||||
return integerCounter;
|
||||
}
|
||||
if (propType.equals(Long.class) || propType.equals(long.class)) {
|
||||
return longCounter;
|
||||
}
|
||||
|
||||
int type = getType(propType);
|
||||
return new GeneratedCounter(type);
|
||||
}
|
||||
|
||||
private int getType(Class<?> propType) {
|
||||
if (propType.equals(Short.class) || propType.equals(short.class)) {
|
||||
return Types.TINYINT;
|
||||
}
|
||||
if (propType.equals(BigDecimal.class)) {
|
||||
return Types.DECIMAL;
|
||||
}
|
||||
if (propType.equals(Double.class) || propType.equals(double.class)) {
|
||||
return Types.DOUBLE;
|
||||
}
|
||||
if (propType.equals(Float.class) || propType.equals(float.class)) {
|
||||
return Types.REAL;
|
||||
}
|
||||
if (propType.equals(BigInteger.class)) {
|
||||
return Types.BIGINT;
|
||||
}
|
||||
String msg = "Can not support Counter for type " + propType.getName();
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* A general number counter for various number types.
|
||||
*/
|
||||
public class GeneratedCounter implements GeneratedProperty {
|
||||
|
||||
final int numberType;
|
||||
|
||||
public GeneratedCounter(int numberType) {
|
||||
this.numberType = numberType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns a 1.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return BasicTypeConverter.convert(1, numberType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current value by one.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
Number currVal = (Number) prop.getValue(bean);
|
||||
Integer nextVal = currVal.intValue() + 1;
|
||||
return BasicTypeConverter.convert(nextVal, numberType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every update.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert setting initial counter value to 1.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to create a counter version column for Integer.
|
||||
*/
|
||||
public class GeneratedCounterInteger implements GeneratedProperty {
|
||||
|
||||
public GeneratedCounterInteger() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns a 1.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current value by one.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
Integer i = (Integer) prop.getValue(bean);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every update.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert setting initial counter value to 1.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to create a counter version column for Long.
|
||||
*/
|
||||
public class GeneratedCounterLong implements GeneratedProperty {
|
||||
|
||||
public GeneratedCounterLong() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns a 1.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return (long) 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current value by one.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
Long i = (Long) prop.getValue(bean);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every update.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert setting initial counter value to 1.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Used to generate a (java.util.Date) timestamp when a bean is inserted.
|
||||
*/
|
||||
public class GeneratedInsertDate implements GeneratedProperty {
|
||||
|
||||
/**
|
||||
* Return the current time as a Timestamp.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Date(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just returns the beans original insert timestamp value.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Support java.time types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedInsertJavaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty, GeneratedWhenCreated {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instant support.
|
||||
*/
|
||||
public static class InstantDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toInstant(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toLocalDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OffsetDateTime support.
|
||||
*/
|
||||
public static class OffsetDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toOffsetDateTime(now);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ZonedDateTime support.
|
||||
*/
|
||||
public static class ZonedDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toZonedDateTime(now);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Support joda time types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedInsertJodaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty, GeneratedWhenCreated {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new LocalDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DateTime support.
|
||||
*/
|
||||
public static class DateTimeDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new DateTime(now);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to generate a (Long) timestamp when a bean is inserted.
|
||||
*/
|
||||
public class GeneratedInsertLong implements GeneratedProperty {
|
||||
|
||||
/**
|
||||
* Return the current time as a Timestamp.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just returns the beans original insert timestamp value.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/**
|
||||
* Used to generate a timestamp when a bean is inserted.
|
||||
*/
|
||||
public class GeneratedInsertTimestamp implements GeneratedProperty, GeneratedWhenCreated {
|
||||
|
||||
/**
|
||||
* Return the current time as a Timestamp.
|
||||
*/
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Timestamp(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just returns the beans original insert timestamp value.
|
||||
*/
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to generate values for a property rather than have then set by the user.
|
||||
* For example generate the update timestamp when a bean is updated.
|
||||
*/
|
||||
public interface GeneratedProperty {
|
||||
|
||||
/**
|
||||
* Get the generated insert value for a specific property of a bean.
|
||||
*/
|
||||
Object getInsertValue(BeanProperty prop, EntityBean bean, long now);
|
||||
|
||||
/**
|
||||
* Get the generated update value for a specific property of a bean.
|
||||
*/
|
||||
Object getUpdateValue(BeanProperty prop, EntityBean bean, long now);
|
||||
|
||||
/**
|
||||
* Return true if this should always be includes in an update statement.
|
||||
* <p>
|
||||
* Used to include GeneratedUpdateTimestamp in dynamic table updates.
|
||||
* </p>
|
||||
*/
|
||||
boolean includeInUpdate();
|
||||
|
||||
/**
|
||||
* Return true if the property should be included in an update even if
|
||||
* it is not loaded (ie. Last Updated Timestamp).
|
||||
*/
|
||||
boolean includeInAllUpdates();
|
||||
|
||||
/**
|
||||
* Return true if this should be included in insert statements.
|
||||
*/
|
||||
boolean includeInInsert();
|
||||
|
||||
/**
|
||||
* Return true if the GeneratedProperty implies the DDL to create the DB
|
||||
* column should have a not null constraint.
|
||||
*/
|
||||
boolean isDDLNotNullable();
|
||||
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.config.ClassLoadConfig;
|
||||
import io.ebean.config.CurrentUserProvider;
|
||||
import io.ebean.config.IdGenerator;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default implementation of GeneratedPropertyFactory.
|
||||
*/
|
||||
public class GeneratedPropertyFactory {
|
||||
|
||||
private final CounterFactory counterFactory = new CounterFactory();
|
||||
|
||||
private final InsertTimestampFactory insertFactory;
|
||||
|
||||
private final UpdateTimestampFactory updateFactory;
|
||||
|
||||
private final HashSet<String> numberTypes = new HashSet<>();
|
||||
|
||||
private final GeneratedWhoModified generatedWhoModified;
|
||||
|
||||
private final GeneratedWhoCreated generatedWhoCreated;
|
||||
|
||||
private final ClassLoadConfig classLoadConfig;
|
||||
|
||||
private final Map<String,PlatformIdGenerator> idGeneratorMap = new HashMap<>();
|
||||
|
||||
public GeneratedPropertyFactory(ServerConfig serverConfig, List<IdGenerator> idGenerators) {
|
||||
|
||||
this.classLoadConfig = serverConfig.getClassLoadConfig();
|
||||
this.insertFactory = new InsertTimestampFactory(classLoadConfig);
|
||||
this.updateFactory = new UpdateTimestampFactory(classLoadConfig);
|
||||
|
||||
CurrentUserProvider currentUserProvider = serverConfig.getCurrentUserProvider();
|
||||
if (currentUserProvider != null) {
|
||||
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
|
||||
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
|
||||
} else {
|
||||
generatedWhoCreated = null;
|
||||
generatedWhoModified = null;
|
||||
}
|
||||
|
||||
numberTypes.add(Integer.class.getName());
|
||||
numberTypes.add(int.class.getName());
|
||||
numberTypes.add(Long.class.getName());
|
||||
numberTypes.add(long.class.getName());
|
||||
numberTypes.add(Short.class.getName());
|
||||
numberTypes.add(short.class.getName());
|
||||
numberTypes.add(Double.class.getName());
|
||||
numberTypes.add(double.class.getName());
|
||||
numberTypes.add(BigDecimal.class.getName());
|
||||
|
||||
if (idGenerators != null) {
|
||||
for (IdGenerator idGenerator : idGenerators) {
|
||||
idGeneratorMap.put(idGenerator.getName(), new CustomIdGenerator(idGenerator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ClassLoadConfig getClassLoadConfig() {
|
||||
return classLoadConfig;
|
||||
}
|
||||
|
||||
private boolean isNumberType(String typeClassName) {
|
||||
return numberTypes.contains(typeClassName);
|
||||
}
|
||||
|
||||
public void setVersion(DeployBeanProperty property) {
|
||||
if (isNumberType(property.getPropertyType().getName())) {
|
||||
setCounter(property);
|
||||
} else {
|
||||
setUpdateTimestamp(property);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCounter(DeployBeanProperty property) {
|
||||
|
||||
counterFactory.setCounter(property);
|
||||
}
|
||||
|
||||
public void setInsertTimestamp(DeployBeanProperty property) {
|
||||
|
||||
insertFactory.setInsertTimestamp(property);
|
||||
}
|
||||
|
||||
public void setUpdateTimestamp(DeployBeanProperty property) {
|
||||
|
||||
updateFactory.setUpdateTimestamp(property);
|
||||
}
|
||||
|
||||
public void setWhoCreated(DeployBeanProperty property) {
|
||||
if (generatedWhoCreated == null) {
|
||||
throw new IllegalStateException("No CurrentUserProvider has been set so @WhoCreated is not supported");
|
||||
}
|
||||
property.setGeneratedProperty(generatedWhoCreated);
|
||||
}
|
||||
|
||||
public void setWhoModified(DeployBeanProperty property) {
|
||||
if (generatedWhoModified == null) {
|
||||
throw new IllegalStateException("No CurrentUserProvider has been set so @WhoModified is not supported");
|
||||
}
|
||||
property.setGeneratedProperty(generatedWhoModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the named custom IdGenerator (wrapped as a PlatformIdGenerator).
|
||||
*/
|
||||
public PlatformIdGenerator getIdGenerator(String generatorName) {
|
||||
return idGeneratorMap.get(generatorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the custom IdGenerator to implement PlatformIdGenerator.
|
||||
*/
|
||||
private static class CustomIdGenerator implements PlatformIdGenerator {
|
||||
|
||||
private final IdGenerator generator;
|
||||
|
||||
CustomIdGenerator(IdGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return generator.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDbSequence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nextId(Transaction transaction) {
|
||||
return generator.nextValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preAllocateIds(int allocateSize) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Generate a (java.util.Date) Timestamp whenever the bean is inserted or
|
||||
* updated.
|
||||
*/
|
||||
public class GeneratedUpdateDate implements GeneratedProperty {
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Date(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Date(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* For dynamic table updates make sure this is included.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Support java.time DateTime types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedUpdateJavaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty, GeneratedWhenModified {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instant support.
|
||||
*/
|
||||
public static class InstantDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toInstant(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toInstant(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toLocalDateTime(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toLocalDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OffsetDateTime support.
|
||||
*/
|
||||
public static class OffsetDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toOffsetDateTime(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toOffsetDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ZonedDateTime support.
|
||||
*/
|
||||
public static class ZonedDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toZonedDateTime(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return JavaTimeUtils.toZonedDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Support java.time DateTime types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedUpdateJodaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty, GeneratedWhenModified {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new LocalDateTime(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new LocalDateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OffsetDateTime support.
|
||||
*/
|
||||
public static class DateTimeDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new DateTime(now);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new DateTime(now);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Generate a (Long) Timestamp whenever the bean is inserted or updated.
|
||||
*/
|
||||
public class GeneratedUpdateLong implements GeneratedProperty {
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return now;
|
||||
}
|
||||
|
||||
/**
|
||||
* For dynamic table updates make sure this is included.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/**
|
||||
* Generate a Timestamp whenever the bean is inserted or updated.
|
||||
*/
|
||||
public class GeneratedUpdateTimestamp implements GeneratedProperty, GeneratedWhenModified {
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Timestamp(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return now as a Timestamp.
|
||||
*/
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return new Timestamp(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* For dynamic table updates make sure this is included.
|
||||
*/
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include this in every insert.
|
||||
*/
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
/**
|
||||
* Marker interface for all implementations mapping to @WhenCreated or @CreatedTimestamp.
|
||||
*/
|
||||
public interface GeneratedWhenCreated {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
/**
|
||||
* Marker interface for all implementations mapping to @WhenModified or @UpdatedTimestamp.
|
||||
*/
|
||||
public interface GeneratedWhenModified {
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.CurrentUserProvider;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to populate @WhoCreated bean properties.
|
||||
*/
|
||||
public class GeneratedWhoCreated implements GeneratedProperty {
|
||||
|
||||
final CurrentUserProvider currentUserProvider;
|
||||
|
||||
public GeneratedWhoCreated(CurrentUserProvider currentUserProvider) {
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return currentUserProvider.currentUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.CurrentUserProvider;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Used to populate @WhoModified bean properties.
|
||||
*/
|
||||
public class GeneratedWhoModified implements GeneratedProperty {
|
||||
|
||||
final CurrentUserProvider currentUserProvider;
|
||||
|
||||
public GeneratedWhoModified(CurrentUserProvider currentUserProvider) {
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return currentUserProvider.currentUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
|
||||
return currentUserProvider.currentUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.config.ClassLoadConfig;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper for creating Insert timestamp GeneratedProperty objects.
|
||||
*/
|
||||
public class InsertTimestampFactory {
|
||||
|
||||
final GeneratedInsertLong longTime = new GeneratedInsertLong();
|
||||
|
||||
final Map<Class<?>, GeneratedProperty> map = new HashMap<>();
|
||||
|
||||
public InsertTimestampFactory(ClassLoadConfig classLoadConfig) {
|
||||
map.put(Timestamp.class, new GeneratedInsertTimestamp());
|
||||
map.put(java.util.Date.class, new GeneratedInsertDate());
|
||||
map.put(Long.class, longTime);
|
||||
map.put(long.class, longTime);
|
||||
|
||||
if (classLoadConfig.isJavaTimePresent()) {
|
||||
map.put(Instant.class, new GeneratedInsertJavaTime.InstantDT());
|
||||
map.put(LocalDateTime.class, new GeneratedInsertJavaTime.LocalDT());
|
||||
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
|
||||
}
|
||||
if (classLoadConfig.isJodaTimePresent()) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedInsertJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedInsertJodaTime.DateTimeDT());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setInsertTimestamp(DeployBeanProperty property) {
|
||||
|
||||
property.setGeneratedProperty(createInsertTimestamp(property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the insert GeneratedProperty depending on the property type.
|
||||
*/
|
||||
public GeneratedProperty createInsertTimestamp(DeployBeanProperty property) {
|
||||
|
||||
Class<?> propType = property.getPropertyType();
|
||||
GeneratedProperty generatedProperty = map.get(propType);
|
||||
if (generatedProperty != null) {
|
||||
return generatedProperty;
|
||||
}
|
||||
|
||||
throw new PersistenceException("Generated Insert Timestamp not supported on " + propType.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* Helper methods for Java time conversion.
|
||||
*/
|
||||
public class JavaTimeUtils {
|
||||
|
||||
/**
|
||||
* Return the system millis time as a LocalDateTime.
|
||||
*/
|
||||
public static Object toInstant(long systemMillis) {
|
||||
return Instant.ofEpochMilli(systemMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system millis time as a LocalDateTime.
|
||||
*/
|
||||
public static Object toLocalDateTime(long systemMillis) {
|
||||
return new Timestamp(systemMillis).toLocalDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system millis time as a OffsetDateTime.
|
||||
*/
|
||||
public static Object toOffsetDateTime(long systemMillis) {
|
||||
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(systemMillis), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system millis time as a ZonedDateTime.
|
||||
*/
|
||||
public static Object toZonedDateTime(long systemMillis) {
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(systemMillis), ZoneId.systemDefault());
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package io.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import io.ebean.config.ClassLoadConfig;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper for creating Update timestamp GeneratedProperty objects.
|
||||
*/
|
||||
public class UpdateTimestampFactory {
|
||||
|
||||
final GeneratedUpdateLong longTime = new GeneratedUpdateLong();
|
||||
|
||||
final Map<Class<?>, GeneratedProperty> map = new HashMap<>();
|
||||
|
||||
public UpdateTimestampFactory(ClassLoadConfig classLoadConfig) {
|
||||
map.put(Timestamp.class, new GeneratedUpdateTimestamp());
|
||||
map.put(java.util.Date.class, new GeneratedUpdateDate());
|
||||
map.put(Long.class, longTime);
|
||||
map.put(long.class, longTime);
|
||||
|
||||
if (classLoadConfig.isJavaTimePresent()) {
|
||||
map.put(Instant.class, new GeneratedUpdateJavaTime.InstantDT());
|
||||
map.put(LocalDateTime.class, new GeneratedUpdateJavaTime.LocalDT());
|
||||
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
|
||||
}
|
||||
if (classLoadConfig.isJodaTimePresent()) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedUpdateJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedUpdateJodaTime.DateTimeDT());
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpdateTimestamp(DeployBeanProperty property) {
|
||||
|
||||
property.setGeneratedProperty(createUpdateTimestamp(property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the update GeneratedProperty depending on the property type.
|
||||
*/
|
||||
protected GeneratedProperty createUpdateTimestamp(DeployBeanProperty property) {
|
||||
|
||||
Class<?> propType = property.getPropertyType();
|
||||
GeneratedProperty generatedProperty = map.get(propType);
|
||||
if (generatedProperty != null) {
|
||||
return generatedProperty;
|
||||
}
|
||||
|
||||
throw new PersistenceException("Generated update Timestamp not supported on " + propType.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
|
||||
<TITLE>Counter, Insert Timestamp, Update Timestamp support</TITLE>
|
||||
</HEAD>
|
||||
<Body BGCOLOR="#ffffff">
|
||||
Counter, Insert Timestamp, Update Timestamp support
|
||||
</Body>
|
||||
</HTML>
|
||||
@@ -0,0 +1,200 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Binds id values to prepared statements.
|
||||
*/
|
||||
public interface IdBinder {
|
||||
|
||||
/**
|
||||
* Initialise the binder.
|
||||
*/
|
||||
void initialise();
|
||||
|
||||
/**
|
||||
* Return true if this is a compound key and must use expanded and or form.
|
||||
*/
|
||||
boolean isIdInExpandedForm();
|
||||
|
||||
/**
|
||||
* Write the Id value to binary DataOuput.
|
||||
*/
|
||||
void writeData(DataOutput dataOutput, Object idValue) throws IOException;
|
||||
|
||||
/**
|
||||
* Read the Id value from the binary DataInput.
|
||||
*/
|
||||
Object readData(DataInput dataInput) throws IOException;
|
||||
|
||||
/**
|
||||
* Return the name(s) of the Id property(s). Comma delimited if there is more
|
||||
* than one.
|
||||
* <p>
|
||||
* This can be used to include in a query.
|
||||
* </p>
|
||||
*/
|
||||
String getIdProperty();
|
||||
|
||||
/**
|
||||
* Return the Id BeanProperty.
|
||||
*/
|
||||
BeanProperty getBeanProperty();
|
||||
|
||||
/**
|
||||
* Find a BeanProperty that is mapped to the database column.
|
||||
*/
|
||||
BeanProperty findBeanProperty(String dbColumnName);
|
||||
|
||||
/**
|
||||
* Return false if the id is a simple scalar and false if it is embedded or
|
||||
* concatenated.
|
||||
*/
|
||||
boolean isComplexId();
|
||||
|
||||
/**
|
||||
* Return the default order by that may need to be used if the query includes
|
||||
* a many property.
|
||||
*/
|
||||
String getDefaultOrderBy();
|
||||
|
||||
String getOrderBy(String pathPrefix, boolean ascending);
|
||||
|
||||
/**
|
||||
* Return the values as an array of scalar bindable values.
|
||||
* <p>
|
||||
* For concatenated keys that use an Embedded bean or multiple id properties
|
||||
* this determines the field values are returns them as an Object array.
|
||||
* </p>
|
||||
* <p>
|
||||
* Added primarily for Query.addWhere().add(Expr.idEq()) support.
|
||||
* </p>
|
||||
*/
|
||||
Object[] getBindValues(Object idValue);
|
||||
|
||||
|
||||
/**
|
||||
* For EmbeddedId convert the idValue into a simple map.
|
||||
* Otherwise the idValue is just returned as is.
|
||||
* <p>
|
||||
* This is used to provide a simple JSON serializable version of the id value.
|
||||
* </p>
|
||||
*/
|
||||
Object getIdForJson(EntityBean idValue);
|
||||
|
||||
/**
|
||||
* For EmbeddedId the value is assumed to be a Map and this is
|
||||
* takes the values from the map and builds an embedded id bean.
|
||||
* <p>
|
||||
* For other simple id's this just returns the value (no conversion required).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is used to provide a simple JSON serializable version of the id value.
|
||||
* </p>
|
||||
*/
|
||||
Object convertIdFromJson(Object value);
|
||||
|
||||
/**
|
||||
* Return the id values for a given bean.
|
||||
*/
|
||||
Object[] getIdValues(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Build a string of the logical expressions.
|
||||
* <p>
|
||||
* Typically used to build a id = ? string.
|
||||
* </p>
|
||||
*/
|
||||
String getAssocOneIdExpr(String prefix, String operator);
|
||||
|
||||
/**
|
||||
* Return the logical id in expression taking into account embedded id's.
|
||||
*/
|
||||
String getAssocIdInExpr(String prefix);
|
||||
|
||||
/**
|
||||
* Binds an id value to a prepared statement.
|
||||
*/
|
||||
void bindId(DataBind dataBind, Object value) throws SQLException;
|
||||
|
||||
/**
|
||||
* Bind the id value to a SqlUpdate statement.
|
||||
*/
|
||||
void bindId(DefaultSqlUpdate sqlUpdate, Object value);
|
||||
|
||||
void addIdInBindValue(SpiExpressionRequest request, Object value);
|
||||
|
||||
/**
|
||||
* Return the sql for binding the id using an IN clause.
|
||||
*/
|
||||
String getBindIdInSql(String baseTableAlias);
|
||||
|
||||
/**
|
||||
* Return the binding expression (like "?" or "(?,?)")for the Id.
|
||||
*/
|
||||
String getIdInValueExpr(int size);
|
||||
|
||||
/**
|
||||
* Same as getIdInValueExpr but for delete by id.
|
||||
*/
|
||||
String getIdInValueExprDelete(int size);
|
||||
|
||||
void buildRawSqlSelectChain(String prefix, List<String> selectChain);
|
||||
|
||||
/**
|
||||
* Read the id value from the result set and set it to the bean also returning
|
||||
* it.
|
||||
*/
|
||||
Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Ignore the appropriate number of scalar properties for this id.
|
||||
*/
|
||||
void loadIgnore(DbReadContext ctx);
|
||||
|
||||
/**
|
||||
* Read the id value from the result set and return it.
|
||||
*/
|
||||
Object read(DbReadContext ctx) throws SQLException;
|
||||
|
||||
/**
|
||||
* Append to the select clause.
|
||||
*/
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
/**
|
||||
* Return the sql for binding the id to. This includes table alias and columns
|
||||
* that make up the id.
|
||||
*/
|
||||
String getBindIdSql(String baseTableAlias);
|
||||
|
||||
/**
|
||||
* Cast or convert the Id value if necessary and optionally set it.
|
||||
* <p>
|
||||
* The Id value is not assumed to be the correct type so it is converted to
|
||||
* the correct type. Typically this is because we could get a Integer, Long or
|
||||
* BigDecimal depending on the JDBC driver and situation.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the bean is not null, then the value is set to the bean.
|
||||
* </p>
|
||||
*/
|
||||
Object convertSetId(Object idValue, EntityBean bean);
|
||||
|
||||
/**
|
||||
* Cast or convert the Id value if necessary.
|
||||
*/
|
||||
Object convertId(Object idValue);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bind an Id that is an Embedded bean.
|
||||
*/
|
||||
public final class IdBinderEmbedded implements IdBinder {
|
||||
|
||||
private final BeanPropertyAssocOne<?> embIdProperty;
|
||||
|
||||
private final boolean idInExpandedForm;
|
||||
|
||||
private BeanProperty[] props;
|
||||
|
||||
private BeanDescriptor<?> idDesc;
|
||||
|
||||
private String idInValueSql;
|
||||
|
||||
public IdBinderEmbedded(boolean idInExpandedForm, BeanPropertyAssocOne<?> embIdProperty) {
|
||||
this.idInExpandedForm = idInExpandedForm;
|
||||
this.embIdProperty = embIdProperty;
|
||||
}
|
||||
|
||||
public void initialise() {
|
||||
this.idDesc = embIdProperty.getTargetDescriptor();
|
||||
this.props = embIdProperty.getProperties();
|
||||
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
|
||||
}
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
private String idInExpanded() {
|
||||
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(idDesc.getBaseTableAlias());
|
||||
sb.append(".");
|
||||
sb.append(props[i].getDbColumn());
|
||||
sb.append("=?");
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String idInCompressed() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append("?");
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanProperty getBeanProperty() {
|
||||
return embIdProperty;
|
||||
}
|
||||
|
||||
public String getOrderBy(String pathPrefix, boolean ascending) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
if (pathPrefix != null) {
|
||||
sb.append(pathPrefix).append(".");
|
||||
}
|
||||
|
||||
sb.append(embIdProperty.getName()).append(".");
|
||||
sb.append(props[i].getName());
|
||||
if (!ascending) {
|
||||
sb.append(" desc");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public BeanDescriptor<?> getIdBeanDescriptor() {
|
||||
return idDesc;
|
||||
}
|
||||
|
||||
public String getIdProperty() {
|
||||
return embIdProperty.getName();
|
||||
}
|
||||
|
||||
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
|
||||
|
||||
prefix = SplitName.add(prefix, embIdProperty.getName());
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
prop.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanProperty findBeanProperty(String dbColumnName) {
|
||||
for (BeanProperty prop : props) {
|
||||
if (dbColumnName.equalsIgnoreCase(prop.getDbColumn())) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isComplexId() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDefaultOrderBy() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
sb.append(embIdProperty.getName());
|
||||
sb.append(".");
|
||||
sb.append(props[i].getName());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public BeanProperty[] getProperties() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
|
||||
for (BeanProperty prop : props) {
|
||||
request.addBindValue(prop.getValue((EntityBean) value));
|
||||
}
|
||||
}
|
||||
|
||||
public String getIdInValueExprDelete(int size) {
|
||||
if (!idInExpandedForm) {
|
||||
return getIdInValueExpr(size);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
|
||||
for (int j = 0; j < size; j++) {
|
||||
if (j > 0) {
|
||||
sb.append(" or ");
|
||||
}
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(props[i].getDbColumn());
|
||||
sb.append("=?");
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
sb.append(") ");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getIdInValueExpr(int size) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!idInExpandedForm) {
|
||||
sb.append(" in");
|
||||
}
|
||||
sb.append(" (");
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i > 0) {
|
||||
if (idInExpandedForm) {
|
||||
sb.append(" or ");
|
||||
} else {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
sb.append(idInValueSql);
|
||||
}
|
||||
sb.append(") ");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Object[] getIdValues(EntityBean bean) {
|
||||
Object val = embIdProperty.getValue(bean);
|
||||
Object[] bindvalues = new Object[props.length];
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
bindvalues[i] = props[i].getValue((EntityBean) val);
|
||||
}
|
||||
return bindvalues;
|
||||
}
|
||||
|
||||
public Object[] getBindValues(Object value) {
|
||||
|
||||
Object[] bindvalues = new Object[props.length];
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
bindvalues[i] = props[i].getValue((EntityBean) value);
|
||||
}
|
||||
return bindvalues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from embedded bean to Map.
|
||||
*/
|
||||
@Override
|
||||
public Object getIdForJson(EntityBean bean) {
|
||||
|
||||
EntityBean ebValue = (EntityBean)embIdProperty.getValue(bean);
|
||||
Map<String,Object> map = new LinkedHashMap<>();
|
||||
for (BeanProperty prop : props) {
|
||||
map.put(prop.getName(), prop.getValue(ebValue));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert back from a Map to embedded bean.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object convertIdFromJson(Object value) {
|
||||
|
||||
Map<String,Object> map = (Map<String, Object>)value;
|
||||
|
||||
EntityBean idValue = idDesc.createEntityBean();
|
||||
for (BeanProperty prop : props) {
|
||||
Object val = map.get(prop.getName());
|
||||
prop.setValue(idValue, val);
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
|
||||
|
||||
public void bindId(DefaultSqlUpdate sqlUpdate, Object value) {
|
||||
for (BeanProperty prop : props) {
|
||||
Object embFieldValue = prop.getValue((EntityBean) value);
|
||||
sqlUpdate.addParameter(embFieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void bindId(DataBind dataBind, Object value) throws SQLException {
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
Object embFieldValue = prop.getValue((EntityBean) value);
|
||||
prop.bind(dataBind, embFieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public Object readData(DataInput dataInput) throws IOException {
|
||||
|
||||
EntityBean embId = idDesc.createEntityBean();
|
||||
boolean notNull = true;
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
Object value = prop.readData(dataInput);
|
||||
prop.setValue(embId, value);
|
||||
if (value == null) {
|
||||
notNull = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (notNull) {
|
||||
return embId;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeData(DataOutput dataOutput, Object idValue) throws IOException {
|
||||
for (BeanProperty prop : props) {
|
||||
Object embFieldValue = prop.getValue((EntityBean) idValue);
|
||||
prop.writeData(dataOutput, embFieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
for (BeanProperty prop : props) {
|
||||
prop.loadIgnore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
|
||||
EntityBean embId = idDesc.createEntityBean();
|
||||
boolean notNull = true;
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
Object value = prop.readSet(ctx, embId);
|
||||
if (value == null) {
|
||||
notNull = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (notNull) {
|
||||
return embId;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
|
||||
Object embId = read(ctx);
|
||||
if (embId != null) {
|
||||
embIdProperty.setValue(bean, embId);
|
||||
return embId;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
for (BeanProperty prop : props) {
|
||||
prop.appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAssocIdInExpr(String prefix) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
if (prefix != null) {
|
||||
sb.append(prefix);
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(props[i].getName());
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getAssocOneIdExpr(String prefix, String operator) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
if (prefix != null) {
|
||||
sb.append(prefix);
|
||||
sb.append(".");
|
||||
}
|
||||
|
||||
sb.append(embIdProperty.getName());
|
||||
sb.append(".");
|
||||
sb.append(props[i].getName());
|
||||
sb.append(operator);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getBindIdSql(String baseTableAlias) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
if (baseTableAlias != null) {
|
||||
sb.append(baseTableAlias);
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(props[i].getDbColumn());
|
||||
sb.append(" = ? ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getBindIdInSql(String baseTableAlias) {
|
||||
|
||||
if (idInExpandedForm) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
if (baseTableAlias != null) {
|
||||
sb.append(baseTableAlias);
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(props[i].getDbColumn());
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertId(Object idValue) {
|
||||
// can not cast/convert if it is embedded
|
||||
return idValue;
|
||||
}
|
||||
|
||||
public Object convertSetId(Object idValue, EntityBean bean) {
|
||||
|
||||
// can not cast/convert if it is embedded
|
||||
if (bean != null) {
|
||||
// support PropertyChangeSupport
|
||||
embIdProperty.setValueIntercept(bean, idValue);
|
||||
}
|
||||
|
||||
return idValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* For beans with no id properties AKA report type beans.
|
||||
*/
|
||||
public final class IdBinderEmpty implements IdBinder {
|
||||
|
||||
private static final String bindIdSql = "";
|
||||
|
||||
public IdBinderEmpty() {
|
||||
|
||||
}
|
||||
|
||||
public void initialise() {
|
||||
|
||||
}
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getOrderBy(String pathPrefix, boolean ascending) {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanProperty getBeanProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getIdProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public BeanProperty findBeanProperty(String dbColumnName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isComplexId() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDefaultOrderBy() {
|
||||
// this should never happen?
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBindIdSql(String baseTableAlias) {
|
||||
return bindIdSql;
|
||||
}
|
||||
|
||||
public String getAssocOneIdExpr(String prefix, String operator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getAssocIdInExpr(String prefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
|
||||
|
||||
}
|
||||
|
||||
public String getIdInValueExprDelete(int size) {
|
||||
return getIdInValueExpr(size);
|
||||
}
|
||||
|
||||
public String getIdInValueExpr(int size) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getBindIdInSql(String baseTableAlias) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object[] getIdValues(EntityBean bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object[] getBindValues(Object idValue) {
|
||||
return new Object[]{idValue};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getIdForJson(EntityBean bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertIdFromJson(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void bindId(DefaultSqlUpdate sqlUpdate, Object value) {
|
||||
|
||||
}
|
||||
|
||||
public void bindId(DataBind dataBind, Object value) throws SQLException {
|
||||
|
||||
}
|
||||
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
}
|
||||
|
||||
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
}
|
||||
|
||||
public Object convertSetId(Object idValue, EntityBean bean) {
|
||||
return idValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertId(Object idValue) {
|
||||
return idValue;
|
||||
}
|
||||
|
||||
public Object readData(DataInput dataOutput) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void writeData(DataOutput dataOutput, Object idValue) throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
|
||||
/**
|
||||
* Creates the appropriate IdConvertSet depending on the type of Id property(s).
|
||||
*/
|
||||
public class IdBinderFactory {
|
||||
|
||||
private static final IdBinderEmpty EMPTY = new IdBinderEmpty();
|
||||
|
||||
private final boolean idInExpandedForm;
|
||||
|
||||
public IdBinderFactory(boolean idInExpandedForm) {
|
||||
this.idInExpandedForm = idInExpandedForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the IdConvertSet for the given type of Id properties.
|
||||
*/
|
||||
public IdBinder createIdBinder(BeanProperty id) {
|
||||
|
||||
if (id == null) {
|
||||
// for report type beans that don't need an id
|
||||
return EMPTY;
|
||||
|
||||
}
|
||||
if (id.isEmbedded()) {
|
||||
return new IdBinderEmbedded(idInExpandedForm, (BeanPropertyAssocOne<?>) id);
|
||||
} else {
|
||||
return new IdBinderSimple(id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bind an Id where the Id is made of a single property (not embedded).
|
||||
*/
|
||||
public final class IdBinderSimple implements IdBinder {
|
||||
|
||||
private final BeanProperty idProperty;
|
||||
|
||||
private final String bindIdSql;
|
||||
|
||||
private final Class<?> expectedType;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final ScalarType scalarType;
|
||||
|
||||
public IdBinderSimple(BeanProperty idProperty) {
|
||||
this.idProperty = idProperty;
|
||||
this.scalarType = idProperty.getScalarType();
|
||||
this.expectedType = idProperty.getPropertyType();
|
||||
bindIdSql = InternString.intern(idProperty.getDbColumn() + " = ? ");
|
||||
}
|
||||
|
||||
public void initialise() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getOrderBy(String pathPrefix, boolean ascending) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (pathPrefix != null) {
|
||||
sb.append(pathPrefix).append(".");
|
||||
}
|
||||
sb.append(idProperty.getName());
|
||||
if (!ascending) {
|
||||
sb.append(" desc");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
|
||||
|
||||
idProperty.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanProperty getBeanProperty() {
|
||||
return idProperty;
|
||||
}
|
||||
|
||||
public String getIdProperty() {
|
||||
return idProperty.getName();
|
||||
}
|
||||
|
||||
public BeanProperty findBeanProperty(String dbColumnName) {
|
||||
if (dbColumnName.equalsIgnoreCase(idProperty.getDbColumn())) {
|
||||
return idProperty;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isComplexId() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getDefaultOrderBy() {
|
||||
return idProperty.getName();
|
||||
}
|
||||
|
||||
public String getBindIdInSql(String baseTableAlias) {
|
||||
if (baseTableAlias == null) {
|
||||
return idProperty.getDbColumn();
|
||||
} else {
|
||||
return baseTableAlias + "." + idProperty.getDbColumn();
|
||||
}
|
||||
}
|
||||
|
||||
public String getBindIdSql(String baseTableAlias) {
|
||||
if (baseTableAlias == null) {
|
||||
return bindIdSql;
|
||||
} else {
|
||||
return baseTableAlias + "." + bindIdSql;
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] getIdValues(EntityBean bean) {
|
||||
return new Object[]{idProperty.getValue(bean)};
|
||||
}
|
||||
|
||||
public Object[] getBindValues(Object idValue) {
|
||||
return new Object[]{idValue};
|
||||
}
|
||||
|
||||
public String getIdInValueExprDelete(int size) {
|
||||
return getIdInValueExpr(size);
|
||||
}
|
||||
|
||||
public String getIdInValueExpr(int size) {
|
||||
StringBuilder sb = new StringBuilder(2 * size + 10);
|
||||
sb.append(" in");
|
||||
sb.append(" (?");
|
||||
for (int i = 1; i < size; i++) {
|
||||
sb.append(",?");
|
||||
}
|
||||
sb.append(") ");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
|
||||
value = convertSetId(value, null);
|
||||
request.addBindValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getIdForJson(EntityBean bean) {
|
||||
return idProperty.getValue(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertIdFromJson(Object value) {
|
||||
// handle simple type conversion if required
|
||||
return convertId(value);
|
||||
}
|
||||
|
||||
public void bindId(DefaultSqlUpdate sqlUpdate, Object value) {
|
||||
sqlUpdate.addParameter(value);
|
||||
}
|
||||
|
||||
public void bindId(DataBind dataBind, Object value) throws SQLException {
|
||||
if (!value.getClass().equals(expectedType)) {
|
||||
value = scalarType.toBeanType(value);
|
||||
}
|
||||
idProperty.bind(dataBind, value);
|
||||
}
|
||||
|
||||
public void writeData(DataOutput os, Object value) throws IOException {
|
||||
idProperty.writeData(os, value);
|
||||
}
|
||||
|
||||
public Object readData(DataInput is) throws IOException {
|
||||
return idProperty.readData(is);
|
||||
}
|
||||
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
idProperty.loadIgnore(ctx);
|
||||
}
|
||||
|
||||
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
Object id = idProperty.read(ctx);
|
||||
if (id != null) {
|
||||
idProperty.setValue(bean, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
return idProperty.read(ctx);
|
||||
}
|
||||
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
idProperty.appendSelect(ctx, subQuery);
|
||||
}
|
||||
|
||||
public String getAssocOneIdExpr(String prefix, String operator) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (prefix != null) {
|
||||
sb.append(prefix);
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(idProperty.getName());
|
||||
sb.append(operator);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getAssocIdInExpr(String prefix) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (prefix != null) {
|
||||
sb.append(prefix);
|
||||
sb.append(".");
|
||||
}
|
||||
sb.append(idProperty.getName());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Object convertId(Object idValue) {
|
||||
|
||||
if (!idValue.getClass().equals(expectedType)) {
|
||||
return scalarType.toBeanType(idValue);
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
|
||||
public Object convertSetId(Object idValue, EntityBean bean) {
|
||||
|
||||
if (!idValue.getClass().equals(expectedType)) {
|
||||
idValue = scalarType.toBeanType(idValue);
|
||||
}
|
||||
if (bean != null) {
|
||||
// support PropertyChangeSupport
|
||||
idProperty.setValueIntercept(bean, idValue);
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.IntersectionRow;
|
||||
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
|
||||
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
|
||||
|
||||
/**
|
||||
* Represents a imported property.
|
||||
*/
|
||||
public interface ImportedId {
|
||||
|
||||
void addFkeys(String name);
|
||||
|
||||
/**
|
||||
* Return true if this id is a simple single scalar value. False if it is a
|
||||
* compound id (embedded or multiple).
|
||||
*/
|
||||
boolean isScalar();
|
||||
|
||||
/**
|
||||
* For scalar id return the related single db column.
|
||||
* <p>
|
||||
* This is essentially the imported foreign key column (where there is only
|
||||
* one).
|
||||
* </p>
|
||||
*/
|
||||
String getDbColumn();
|
||||
|
||||
/**
|
||||
* Append the the SQL query statement.
|
||||
*/
|
||||
void sqlAppend(DbSqlContext ctx);
|
||||
|
||||
/**
|
||||
* Append to the DML statement.
|
||||
*/
|
||||
void dmlAppend(GenerateDmlRequest request);
|
||||
|
||||
/**
|
||||
* Bind the value from the bean.
|
||||
*/
|
||||
Object bind(BindableRequest request, EntityBean bean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Bind the imported Id value to the SqlUpdate.
|
||||
*/
|
||||
int bind(int position, SqlUpdate update, EntityBean bean);
|
||||
|
||||
/**
|
||||
* For inserting into ManyToMany intersection.
|
||||
*/
|
||||
void buildImport(IntersectionRow row, EntityBean other);
|
||||
|
||||
/**
|
||||
* Used to derive a missing concatenated key from multiple imported keys.
|
||||
*/
|
||||
BeanProperty findMatchImport(String matchDbColumn);
|
||||
|
||||
/**
|
||||
* Return the set importedId clause.
|
||||
*/
|
||||
String importedIdClause();
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanFkeyProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.IntersectionRow;
|
||||
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
|
||||
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
|
||||
|
||||
/**
|
||||
* Imported Embedded id.
|
||||
*/
|
||||
public class ImportedIdEmbedded implements ImportedId {
|
||||
|
||||
private final BeanPropertyAssoc<?> owner;
|
||||
|
||||
private final BeanPropertyAssocOne<?> foreignAssocOne;
|
||||
|
||||
private final ImportedIdSimple[] imported;
|
||||
|
||||
public ImportedIdEmbedded(BeanPropertyAssoc<?> owner, BeanPropertyAssocOne<?> foreignAssocOne, ImportedIdSimple[] imported) {
|
||||
this.owner = owner;
|
||||
this.foreignAssocOne = foreignAssocOne;
|
||||
this.imported = imported;
|
||||
}
|
||||
|
||||
public void addFkeys(String name) {
|
||||
|
||||
BeanProperty[] embeddedProps = foreignAssocOne.getProperties();
|
||||
|
||||
for (int i = 0; i < imported.length; i++) {
|
||||
String n = name + "." + foreignAssocOne.getName() + "." + embeddedProps[i].getName();
|
||||
BeanFkeyProperty fkey = new BeanFkeyProperty(n, imported[i].localDbColumn, foreignAssocOne.getDeployOrder());
|
||||
owner.getBeanDescriptor().add(fkey);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isScalar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getDbColumn() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void sqlAppend(DbSqlContext ctx) {
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
ctx.appendColumn(anImported.localDbColumn);
|
||||
}
|
||||
}
|
||||
|
||||
public void dmlAppend(GenerateDmlRequest request) {
|
||||
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
request.appendColumn(anImported.localDbColumn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String importedIdClause() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < imported.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(imported[i].localDbColumn).append(" = ?");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bind(int position, SqlUpdate update, EntityBean bean) {
|
||||
|
||||
int pos = position;
|
||||
|
||||
EntityBean embedded = (EntityBean) foreignAssocOne.getValue(bean);
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
if (anImported.owner.isUpdateable()) {
|
||||
Object scalarValue = anImported.foreignProperty.getValue(embedded);
|
||||
update.setParameter(pos++, scalarValue);
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
public Object bind(BindableRequest request, EntityBean bean) throws SQLException {
|
||||
|
||||
Object embeddedId = null;
|
||||
|
||||
if (bean != null) {
|
||||
embeddedId = foreignAssocOne.getValue(bean);
|
||||
}
|
||||
|
||||
if (embeddedId == null) {
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
if (anImported.owner.isUpdateable()) {
|
||||
request.bind(null, anImported.foreignProperty);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
EntityBean embedded = (EntityBean) embeddedId;
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
if (anImported.owner.isUpdateable()) {
|
||||
Object scalarValue = anImported.foreignProperty.getValue(embedded);
|
||||
request.bind(scalarValue, anImported.foreignProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// hmmm, not worrying about this just yet
|
||||
return null;
|
||||
}
|
||||
|
||||
public void buildImport(IntersectionRow row, EntityBean other) {
|
||||
|
||||
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
|
||||
if (embeddedId == null) {
|
||||
String msg = "Foreign Key value null?";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
|
||||
row.put(anImported.localDbColumn, scalarValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported for embedded id.
|
||||
*/
|
||||
public BeanProperty findMatchImport(String matchDbColumn) {
|
||||
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
BeanProperty p = anImported.findMatchImport(matchDbColumn);
|
||||
if (p != null) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package io.ebeaninternal.server.deploy.id;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.BeanFkeyProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.IntersectionRow;
|
||||
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
|
||||
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
|
||||
|
||||
/**
|
||||
* Single scalar imported id.
|
||||
*/
|
||||
public final class ImportedIdSimple implements ImportedId, Comparable<ImportedIdSimple> {
|
||||
|
||||
/**
|
||||
* Helper class to sort ImportedIdSimple.
|
||||
*/
|
||||
private final static class EntryComparator implements Comparator<ImportedIdSimple> {
|
||||
public int compare(ImportedIdSimple o1, ImportedIdSimple o2) {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
}
|
||||
|
||||
private static final EntryComparator COMPARATOR = new EntryComparator();
|
||||
|
||||
protected final BeanPropertyAssoc<?> owner;
|
||||
|
||||
protected final String localDbColumn;
|
||||
|
||||
protected final String localSqlFormula;
|
||||
|
||||
protected final String logicalName;
|
||||
|
||||
protected final BeanProperty foreignProperty;
|
||||
|
||||
protected final int position;
|
||||
|
||||
public ImportedIdSimple(BeanPropertyAssoc<?> owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) {
|
||||
this.owner = owner;
|
||||
this.localDbColumn = InternString.intern(localDbColumn);
|
||||
this.localSqlFormula = InternString.intern(localSqlFormula);
|
||||
this.foreignProperty = foreignProperty;
|
||||
this.position = position;
|
||||
this.logicalName = InternString.intern(owner.getName() + "." + foreignProperty.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list as an array sorted into the same order as the Bean Properties.
|
||||
*/
|
||||
public static ImportedIdSimple[] sort(List<ImportedIdSimple> list) {
|
||||
|
||||
ImportedIdSimple[] importedIds = list.toArray(new ImportedIdSimple[list.size()]);
|
||||
|
||||
// sort into the same order as the BeanProperties
|
||||
Arrays.sort(importedIds, COMPARATOR);
|
||||
return importedIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// remove FindBugs warning
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
public int compareTo(ImportedIdSimple other) {
|
||||
return (position < other.position ? -1 : (position == other.position ? 0 : 1));
|
||||
}
|
||||
|
||||
public void addFkeys(String name) {
|
||||
BeanFkeyProperty fkey = new BeanFkeyProperty(name + "." + foreignProperty.getName(), localDbColumn, owner.getDeployOrder());
|
||||
owner.getBeanDescriptor().add(fkey);
|
||||
}
|
||||
|
||||
public boolean isScalar() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDbColumn() {
|
||||
return localDbColumn;
|
||||
}
|
||||
|
||||
private Object getIdValue(EntityBean bean) {
|
||||
return foreignProperty.getValue(bean);
|
||||
}
|
||||
|
||||
public void buildImport(IntersectionRow row, EntityBean other) {
|
||||
|
||||
Object value = getIdValue(other);
|
||||
if (value == null) {
|
||||
String msg = "Foreign Key value null?";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
row.put(localDbColumn, value);
|
||||
}
|
||||
|
||||
public void sqlAppend(DbSqlContext ctx) {
|
||||
if (localSqlFormula != null) {
|
||||
ctx.appendFormulaSelect(localSqlFormula);
|
||||
} else {
|
||||
ctx.appendColumn(localDbColumn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void dmlAppend(GenerateDmlRequest request) {
|
||||
request.appendColumn(localDbColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String importedIdClause() {
|
||||
return localDbColumn + " = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bind(int position, SqlUpdate update, EntityBean bean) {
|
||||
Object value = getIdValue(bean);
|
||||
update.setParameter(position, value);
|
||||
return ++position;
|
||||
}
|
||||
|
||||
public Object bind(BindableRequest request, EntityBean bean) throws SQLException {
|
||||
|
||||
Object value = null;
|
||||
if (bean != null) {
|
||||
value = getIdValue(bean);
|
||||
}
|
||||
request.bind(value, foreignProperty);
|
||||
return value;
|
||||
}
|
||||
|
||||
public BeanProperty findMatchImport(String matchDbColumn) {
|
||||
|
||||
if (matchDbColumn.equals(localDbColumn)) {
|
||||
return foreignProperty;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
|
||||
<TITLE>Helpers for Id property conversion</TITLE>
|
||||
</HEAD>
|
||||
<Body BGCOLOR="#ffffff">
|
||||
Helpers for Id property conversion
|
||||
|
||||
</Body>
|
||||
</HTML>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Collects Deployment information on Embedded beans.
|
||||
* <p>
|
||||
* Typically collects the overridden column names mapped
|
||||
* to the Embedded bean.
|
||||
* </p>
|
||||
*/
|
||||
public class DeployBeanEmbedded {
|
||||
|
||||
/**
|
||||
* A map of property names to dbColumns.
|
||||
*/
|
||||
private final Map<String, String> propMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set a Map of property names to dbColumns.
|
||||
*/
|
||||
public void putAll(Map<String, String> propertyColumnMap) {
|
||||
propMap.putAll(propertyColumnMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of property names to dbColumns.
|
||||
*/
|
||||
public Map<String, String> getPropertyColumnMap() {
|
||||
return propMap;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,992 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import io.ebean.annotation.CreatedTimestamp;
|
||||
import io.ebean.annotation.DocCode;
|
||||
import io.ebean.annotation.DocProperty;
|
||||
import io.ebean.annotation.DocSortable;
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
import io.ebean.annotation.UpdatedTimestamp;
|
||||
import io.ebean.annotation.WhenCreated;
|
||||
import io.ebean.annotation.WhenModified;
|
||||
import io.ebean.annotation.WhoCreated;
|
||||
import io.ebean.annotation.WhoModified;
|
||||
import io.ebean.config.ScalarTypeConverter;
|
||||
import io.ebean.config.dbplatform.DbDefaultValue;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DeployDocPropertyOptions;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.properties.BeanPropertyGetter;
|
||||
import io.ebeaninternal.server.properties.BeanPropertySetter;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.ScalarTypeWrapper;
|
||||
import io.ebeanservice.docstore.api.mapping.DocPropertyOptions;
|
||||
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Version;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Description of a property of a bean. Includes its deployment information such
|
||||
* as database column mapping information.
|
||||
*/
|
||||
public class DeployBeanProperty {
|
||||
|
||||
private static final int ID_ORDER = 1000000;
|
||||
private static final int UNIDIRECTIONAL_ORDER = 100000;
|
||||
private static final int AUDITCOLUMN_ORDER = -1000000;
|
||||
private static final int VERSIONCOLUMN_ORDER = -1000000;
|
||||
|
||||
/**
|
||||
* Flag to mark this at part of the unique id.
|
||||
*/
|
||||
private boolean id;
|
||||
|
||||
/**
|
||||
* Flag to mark the property as embedded. This could be on
|
||||
* BeanPropertyAssocOne rather than here. Put it here for checking Id type
|
||||
* (embedded or not).
|
||||
*/
|
||||
private boolean embedded;
|
||||
|
||||
/**
|
||||
* Flag indicating if this the version property.
|
||||
*/
|
||||
private boolean versionColumn;
|
||||
|
||||
private boolean fetchEager = true;
|
||||
|
||||
/**
|
||||
* Set if this property is nullable.
|
||||
*/
|
||||
private boolean nullable = true;
|
||||
|
||||
private boolean unique;
|
||||
|
||||
private boolean discriminator;
|
||||
|
||||
/**
|
||||
* The length or precision of the DB column.
|
||||
*/
|
||||
private int dbLength;
|
||||
|
||||
private int dbScale;
|
||||
|
||||
private String dbColumnDefn;
|
||||
|
||||
private boolean isTransient;
|
||||
|
||||
private boolean localEncrypted;
|
||||
|
||||
private boolean jsonSerialize = true;
|
||||
private boolean jsonDeserialize = true;
|
||||
|
||||
private boolean dbEncrypted;
|
||||
private DbEncryptFunction dbEncryptFunction;
|
||||
|
||||
private int dbEncryptedType;
|
||||
|
||||
private String dbBind = "?";
|
||||
|
||||
/**
|
||||
* Is this property include in database resultSet.
|
||||
*/
|
||||
private boolean dbRead;
|
||||
|
||||
/**
|
||||
* Include this in DB insert.
|
||||
*/
|
||||
private boolean dbInsertable;
|
||||
|
||||
/**
|
||||
* Include this in a DB update.
|
||||
*/
|
||||
private boolean dbUpdateable;
|
||||
|
||||
private DeployTableJoin secondaryTableJoin;
|
||||
|
||||
private String secondaryTableJoinPrefix;
|
||||
|
||||
/**
|
||||
* Set to true if this property is based on a secondary table.
|
||||
*/
|
||||
private String secondaryTable;
|
||||
|
||||
/**
|
||||
* The type that owns this property.
|
||||
*/
|
||||
private Class<?> owningType;
|
||||
|
||||
/**
|
||||
* True if the property is a Clob, Blob LongVarchar or LongVarbinary.
|
||||
*/
|
||||
private boolean lob;
|
||||
|
||||
private boolean naturalKey;
|
||||
|
||||
/**
|
||||
* The logical bean property name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The reflected field.
|
||||
*/
|
||||
private Field field;
|
||||
|
||||
/**
|
||||
* The bean type.
|
||||
*/
|
||||
private final Class<?> propertyType;
|
||||
|
||||
private final Type genericType;
|
||||
|
||||
/**
|
||||
* Set for Non-JDBC types to provide logical to db type conversion.
|
||||
*/
|
||||
private ScalarType<?> scalarType;
|
||||
|
||||
/**
|
||||
* The database column. This can include quoted identifiers.
|
||||
*/
|
||||
private String dbColumn;
|
||||
|
||||
private String aggregationPrefix;
|
||||
private String aggregation;
|
||||
|
||||
private String sqlFormulaSelect;
|
||||
private String sqlFormulaJoin;
|
||||
|
||||
/**
|
||||
* The jdbc data type this maps to.
|
||||
*/
|
||||
private int dbType;
|
||||
|
||||
private DeployDocPropertyOptions docMapping = new DeployDocPropertyOptions();
|
||||
|
||||
/**
|
||||
* The method used to read the property.
|
||||
*/
|
||||
private Method readMethod;
|
||||
|
||||
private int propertyIndex;
|
||||
|
||||
private BeanPropertyGetter getter;
|
||||
|
||||
private BeanPropertySetter setter;
|
||||
|
||||
/**
|
||||
* Generator for insert or update timestamp etc.
|
||||
*/
|
||||
private GeneratedProperty generatedProperty;
|
||||
|
||||
protected final DeployBeanDescriptor<?> desc;
|
||||
|
||||
private boolean undirectionalShadow;
|
||||
|
||||
private int sortOrder;
|
||||
|
||||
private boolean excludedFromHistory;
|
||||
|
||||
private boolean tenantId;
|
||||
|
||||
private boolean draft;
|
||||
private boolean draftOnly;
|
||||
private boolean draftDirty;
|
||||
private boolean draftReset;
|
||||
|
||||
private boolean softDelete;
|
||||
private boolean unmappedJson;
|
||||
|
||||
private String dbComment;
|
||||
|
||||
private String dbColumnDefault;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
this.genericType = null;
|
||||
this.scalarType = wrapScalarType(propertyType, scalarType, typeConverter);
|
||||
this.dbType = (scalarType == null) ? 0 : scalarType.getJdbcType();
|
||||
}
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, Type genericType) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
this.genericType = genericType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the ScalarType using a ScalarTypeConverter.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private ScalarType<?> wrapScalarType(Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
if (typeConverter == null) {
|
||||
return scalarType;
|
||||
}
|
||||
return new ScalarTypeWrapper(propertyType, scalarType, typeConverter);
|
||||
}
|
||||
|
||||
public int getSortOverride() {
|
||||
if (field == null) {
|
||||
return 0;
|
||||
}
|
||||
if (AnnotationBase.findAnnotation(field, Id.class) != null) {
|
||||
return ID_ORDER;
|
||||
} else if (AnnotationBase.findAnnotation(field, EmbeddedId.class) != null) {
|
||||
return ID_ORDER;
|
||||
} else if (undirectionalShadow) {
|
||||
return UNIDIRECTIONAL_ORDER;
|
||||
} else if (isAuditProperty()) {
|
||||
return AUDITCOLUMN_ORDER;
|
||||
} else if (AnnotationBase.findAnnotation(field, Version.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
} else if (AnnotationBase.findAnnotation(field, SoftDelete.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean isAuditProperty() {
|
||||
return (AnnotationBase.findAnnotation(field, WhenCreated.class) != null
|
||||
|| AnnotationBase.findAnnotation(field, WhenModified.class) != null
|
||||
|| AnnotationBase.findAnnotation(field, WhoModified.class) != null
|
||||
|| AnnotationBase.findAnnotation(field, WhoCreated.class) != null
|
||||
|| AnnotationBase.findAnnotation(field, UpdatedTimestamp.class) != null
|
||||
|| AnnotationBase.findAnnotation(field, CreatedTimestamp.class) != null);
|
||||
}
|
||||
|
||||
public String getFullBeanName() {
|
||||
return desc.getFullName() + "." + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column length for character columns.
|
||||
* <p>
|
||||
* Note if there is no length explicitly defined then the scalarType is
|
||||
* checked to see if that has one (primarily to support putting a length on
|
||||
* Enum types).
|
||||
* </p>
|
||||
*/
|
||||
public int getDbLength() {
|
||||
if (dbLength == 0 && scalarType != null) {
|
||||
return scalarType.getLength();
|
||||
}
|
||||
|
||||
return dbLength;
|
||||
}
|
||||
|
||||
public boolean isJsonSerialize() {
|
||||
return jsonSerialize;
|
||||
}
|
||||
|
||||
public void setJsonSerialize(boolean jsonSerialize) {
|
||||
this.jsonSerialize = jsonSerialize;
|
||||
}
|
||||
|
||||
public boolean isJsonDeserialize() {
|
||||
return jsonDeserialize;
|
||||
}
|
||||
|
||||
public void setJsonDeserialize(boolean jsonDeserialize) {
|
||||
this.jsonDeserialize = jsonDeserialize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sortOrder for the properties.
|
||||
*/
|
||||
public int getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sortOrder for the properties.
|
||||
*/
|
||||
public void setSortOrder(int sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a placeholder property for a unidirectional relationship.
|
||||
*/
|
||||
public boolean isUndirectionalShadow() {
|
||||
return undirectionalShadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this property as a placeholder for a unidirectional relationship.
|
||||
*/
|
||||
public void setUndirectionalShadow() {
|
||||
this.undirectionalShadow = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this property as mapping to the discriminator column.
|
||||
*/
|
||||
public void setDiscriminator() {
|
||||
this.discriminator = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property maps to the inheritance discriminator column.s
|
||||
*/
|
||||
public boolean isDiscriminator() {
|
||||
return discriminator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is encrypted in java rather than in the DB.
|
||||
*/
|
||||
public boolean isLocalEncrypted() {
|
||||
return localEncrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when the property is encrypted in java rather than in the DB.
|
||||
*/
|
||||
public void setLocalEncrypted() {
|
||||
this.localEncrypted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB column length for character columns.
|
||||
*/
|
||||
public void setDbLength(int dbLength) {
|
||||
this.dbLength = dbLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Db scale for numeric columns.
|
||||
*/
|
||||
public int getDbScale() {
|
||||
return dbScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db scale for numeric columns.
|
||||
*/
|
||||
public void setDbScale(int dbScale) {
|
||||
this.dbScale = dbScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column definition if defined.
|
||||
*/
|
||||
public String getDbColumnDefn() {
|
||||
return dbColumnDefn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific DB column definition.
|
||||
*/
|
||||
public void setDbColumnDefn(String dbColumnDefn) {
|
||||
if (dbColumnDefn == null || dbColumnDefn.trim().isEmpty()) {
|
||||
this.dbColumnDefn = null;
|
||||
} else {
|
||||
this.dbColumnDefn = InternString.intern(dbColumnDefn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scalarType. This returns null for native JDBC types, otherwise
|
||||
* it is used to convert between logical types and jdbc types.
|
||||
*/
|
||||
public ScalarType<?> getScalarType() {
|
||||
return scalarType;
|
||||
}
|
||||
|
||||
public void setScalarType(ScalarType<?> scalarType) {
|
||||
this.scalarType = scalarType;
|
||||
}
|
||||
|
||||
public int getPropertyIndex() {
|
||||
return propertyIndex;
|
||||
}
|
||||
|
||||
public void setPropertyIndex(int propertyIndex) {
|
||||
this.propertyIndex = propertyIndex;
|
||||
}
|
||||
|
||||
public BeanPropertyGetter getGetter() {
|
||||
return getter;
|
||||
}
|
||||
|
||||
public BeanPropertySetter getSetter() {
|
||||
return setter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the getter method.
|
||||
*/
|
||||
public Method getReadMethod() {
|
||||
return readMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the owning type form a Inheritance heirarchy.
|
||||
*/
|
||||
public void setOwningType(Class<?> owningType) {
|
||||
this.owningType = owningType;
|
||||
}
|
||||
|
||||
public Class<?> getOwningType() {
|
||||
return owningType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is local to this type - aka not from a super type.
|
||||
*/
|
||||
public boolean isLocal() {
|
||||
return owningType == null || owningType.equals(desc.getBeanType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the getter used to read the property value from a bean.
|
||||
*/
|
||||
public void setGetter(BeanPropertyGetter getter) {
|
||||
this.getter = getter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the setter used to set the property value to a bean.
|
||||
*/
|
||||
public void setSetter(BeanPropertySetter setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the property.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the property.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = InternString.intern(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean Field associated with this property.
|
||||
*/
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean Field associated with this property.
|
||||
*/
|
||||
public void setField(Field field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public boolean isNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
|
||||
public void setNaturalKey() {
|
||||
this.naturalKey = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the GeneratedValue. Used to generate update timestamp etc.
|
||||
*/
|
||||
public GeneratedProperty getGeneratedProperty() {
|
||||
return generatedProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GeneratedValue. Used to generate update timestamp etc.
|
||||
*/
|
||||
public void setGeneratedProperty(GeneratedProperty generatedValue) {
|
||||
this.generatedProperty = generatedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is mandatory.
|
||||
*/
|
||||
public boolean isNullable() {
|
||||
return nullable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the not nullable of this property.
|
||||
*/
|
||||
public void setNullable(boolean isNullable) {
|
||||
this.nullable = isNullable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB column is unique.
|
||||
*/
|
||||
public boolean isUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the DB column is unique.
|
||||
*/
|
||||
public void setUnique(boolean unique) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a version column used for concurrency checking.
|
||||
*/
|
||||
public boolean isVersionColumn() {
|
||||
return versionColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if this is a version column used for concurrency checking.
|
||||
*/
|
||||
public void setVersionColumn() {
|
||||
this.versionColumn = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this should be eager fetched by default.
|
||||
*/
|
||||
public boolean isFetchEager() {
|
||||
return fetchEager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default fetch type for this property.
|
||||
*/
|
||||
public void setFetchType(FetchType fetchType) {
|
||||
this.fetchEager = FetchType.EAGER.equals(fetchType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula this property is based on.
|
||||
*/
|
||||
public String getSqlFormulaSelect() {
|
||||
return sqlFormulaSelect;
|
||||
}
|
||||
|
||||
public String getSqlFormulaJoin() {
|
||||
return sqlFormulaJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* The property is based on a formula.
|
||||
*/
|
||||
public void setSqlFormula(String formulaSelect, String formulaJoin) {
|
||||
this.sqlFormulaSelect = formulaSelect;
|
||||
this.sqlFormulaJoin = formulaJoin.equals("") ? null : formulaJoin;
|
||||
this.dbRead = true;
|
||||
this.dbInsertable = false;
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isAggregation() {
|
||||
return aggregation != null;
|
||||
}
|
||||
|
||||
public String getAggregation() {
|
||||
return aggregation;
|
||||
}
|
||||
|
||||
public void setAggregation(String aggregation) {
|
||||
this.aggregation = aggregation;
|
||||
this.dbRead = true;
|
||||
this.dbInsertable = false;
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the aggregation.
|
||||
*/
|
||||
public void setAggregationPrefix(String aggregationPrefix) {
|
||||
this.aggregationPrefix = aggregationPrefix;
|
||||
this.aggregation = aggregation.replace(aggregationPrefix, "u1");
|
||||
}
|
||||
|
||||
public String getElPrefix() {
|
||||
if (aggregation != null) {
|
||||
return aggregationPrefix;
|
||||
} else {
|
||||
return secondaryTableJoinPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
public String getElPlaceHolder() {
|
||||
if (aggregation != null) {
|
||||
return aggregation;
|
||||
} else if (sqlFormulaSelect != null) {
|
||||
return sqlFormulaSelect;
|
||||
} else {
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
return "${" + secondaryTableJoinPrefix + "}" + getDbColumn();
|
||||
}
|
||||
// prepend table alias placeholder
|
||||
return ElPropertyValue.ROOT_ELPREFIX + getDbColumn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The database column name this is mapped to.
|
||||
*/
|
||||
public String getDbColumn() {
|
||||
if (sqlFormulaSelect != null) {
|
||||
return sqlFormulaSelect;
|
||||
}
|
||||
if (aggregation != null) {
|
||||
return aggregation;
|
||||
}
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database column name this is mapped to.
|
||||
*/
|
||||
public void setDbColumn(String dbColumn) {
|
||||
this.dbColumn = InternString.intern(dbColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database jdbc data type this is mapped to.
|
||||
*/
|
||||
public int getDbType() {
|
||||
return dbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database jdbc data type this is mapped to.
|
||||
*/
|
||||
public void setDbType(int dbType) {
|
||||
this.dbType = dbType;
|
||||
this.lob = BeanProperty.isLobType(dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is mapped to a Clob Blob LongVarchar or
|
||||
* LongVarbinary.
|
||||
*/
|
||||
public boolean isLob() {
|
||||
return lob;
|
||||
}
|
||||
|
||||
public boolean isDbNumberType() {
|
||||
return isNumericType(dbType);
|
||||
}
|
||||
|
||||
private boolean isNumericType(int type) {
|
||||
switch (type) {
|
||||
case Types.BIGINT:
|
||||
return true;
|
||||
case Types.DECIMAL:
|
||||
return true;
|
||||
case Types.DOUBLE:
|
||||
return true;
|
||||
case Types.FLOAT:
|
||||
return true;
|
||||
case Types.INTEGER:
|
||||
return true;
|
||||
case Types.NUMERIC:
|
||||
return true;
|
||||
case Types.REAL:
|
||||
return true;
|
||||
case Types.SMALLINT:
|
||||
return true;
|
||||
case Types.TINYINT:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is based on a secondary table.
|
||||
*/
|
||||
public boolean isSecondaryTable() {
|
||||
return secondaryTable != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the secondary table this property is associated with.
|
||||
*/
|
||||
public String getSecondaryTable() {
|
||||
return secondaryTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this property is included in persisting.
|
||||
*/
|
||||
public void setSecondaryTable(String secondaryTable) {
|
||||
this.secondaryTable = secondaryTable;
|
||||
this.dbInsertable = false;
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public String getSecondaryTableJoinPrefix() {
|
||||
return secondaryTableJoinPrefix;
|
||||
}
|
||||
|
||||
public DeployTableJoin getSecondaryTableJoin() {
|
||||
return secondaryTableJoin;
|
||||
}
|
||||
|
||||
public void setSecondaryTableJoin(DeployTableJoin secondaryTableJoin, String prefix) {
|
||||
this.secondaryTableJoin = secondaryTableJoin;
|
||||
this.secondaryTableJoinPrefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB Bind parameter. Typically is "?" but can be different for
|
||||
* encrypted bind.
|
||||
*/
|
||||
public String getDbBind() {
|
||||
return dbBind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is encrypted in the DB.
|
||||
*/
|
||||
public boolean isDbEncrypted() {
|
||||
return dbEncrypted;
|
||||
}
|
||||
|
||||
public DbEncryptFunction getDbEncryptFunction() {
|
||||
return dbEncryptFunction;
|
||||
}
|
||||
|
||||
public void setDbEncryptFunction(DbEncryptFunction dbEncryptFunction, DbEncrypt dbEncrypt, int dbLen) {
|
||||
this.dbEncryptFunction = dbEncryptFunction;
|
||||
this.dbEncrypted = true;
|
||||
this.dbBind = dbEncryptFunction.getEncryptBindSql();
|
||||
|
||||
this.dbEncryptedType = isLob() ? Types.BLOB : dbEncrypt.getEncryptDbType();
|
||||
if (dbLen > 0) {
|
||||
setDbLength(dbLen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type for the encrypted property. This can differ from the
|
||||
* logical type (String encrypted and stored in a VARBINARY)
|
||||
*/
|
||||
public int getDbEncryptedType() {
|
||||
return dbEncryptedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is included in database queries.
|
||||
*/
|
||||
public boolean isDbRead() {
|
||||
return dbRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this property is included in database queries.
|
||||
*/
|
||||
public void setDbRead(boolean isDBRead) {
|
||||
this.dbRead = isDBRead;
|
||||
}
|
||||
|
||||
public boolean isDbInsertable() {
|
||||
return dbInsertable;
|
||||
}
|
||||
|
||||
public void setDbInsertable(boolean insertable) {
|
||||
this.dbInsertable = insertable;
|
||||
}
|
||||
|
||||
public boolean isDbUpdateable() {
|
||||
return dbUpdateable;
|
||||
}
|
||||
|
||||
public void setDbUpdateable(boolean updateable) {
|
||||
this.dbUpdateable = updateable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is transient.
|
||||
*/
|
||||
public boolean isTransient() {
|
||||
return isTransient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the property explicitly as a transient property.
|
||||
*/
|
||||
public void setTransient() {
|
||||
this.isTransient = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean read method.
|
||||
* <p>
|
||||
* NB: That a BeanReflectGetter is used to actually perform the getting of
|
||||
* property values from a bean. This is due to performance considerations.
|
||||
* </p>
|
||||
*/
|
||||
public void setReadMethod(Method readMethod) {
|
||||
this.readMethod = readMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property type.
|
||||
*/
|
||||
public Class<?> getPropertyType() {
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the generic type for this property.
|
||||
*/
|
||||
public Type getGenericType() {
|
||||
return genericType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is included in the unique id.
|
||||
*/
|
||||
public boolean isId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this is included in the unique id.
|
||||
*/
|
||||
public void setId() {
|
||||
this.id = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an Embedded property. In this case it shares the
|
||||
* table and pk of its owner object.
|
||||
*/
|
||||
public boolean isEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this is an embedded property.
|
||||
*/
|
||||
public void setEmbedded() {
|
||||
this.embedded = true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return desc.getFullName() + "." + name;
|
||||
}
|
||||
|
||||
public boolean isExcludedFromHistory() {
|
||||
return excludedFromHistory;
|
||||
}
|
||||
|
||||
public void setExcludedFromHistory() {
|
||||
this.excludedFromHistory = true;
|
||||
}
|
||||
|
||||
public void setDraft() {
|
||||
this.draft = true;
|
||||
this.isTransient = true;
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public void setDraftOnly() {
|
||||
this.draftOnly = true;
|
||||
}
|
||||
|
||||
public boolean isDraftOnly() {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
public void setDraftDirty() {
|
||||
this.draftOnly = true;
|
||||
this.draftDirty = true;
|
||||
this.nullable = false;
|
||||
}
|
||||
|
||||
public boolean isDraftDirty() {
|
||||
return draftDirty;
|
||||
}
|
||||
|
||||
public void setDraftReset() {
|
||||
this.draftReset = true;
|
||||
}
|
||||
|
||||
public boolean isDraftReset() {
|
||||
return draftReset;
|
||||
}
|
||||
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
this.nullable = false;
|
||||
this.dbColumnDefault = DbDefaultValue.FALSE;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public void setUnmappedJson() {
|
||||
this.unmappedJson = true;
|
||||
this.isTransient = true;
|
||||
}
|
||||
|
||||
public boolean isUnmappedJson() {
|
||||
return unmappedJson;
|
||||
}
|
||||
|
||||
public void setDbComment(String dbComment) {
|
||||
this.dbComment = dbComment;
|
||||
}
|
||||
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
public void setDocProperty(DocProperty docProperty) {
|
||||
docMapping.setDocProperty(docProperty);
|
||||
}
|
||||
|
||||
public void setDocSortable(DocSortable docSortable) {
|
||||
docMapping.setDocSortable(docSortable);
|
||||
}
|
||||
|
||||
public void setDocCode(DocCode docCode) {
|
||||
docMapping.setDocCode(docCode);
|
||||
}
|
||||
|
||||
public DocPropertyOptions getDocPropertyOptions() {
|
||||
return docMapping.create();
|
||||
}
|
||||
|
||||
public String getDbColumnDefault() {
|
||||
return dbColumnDefault;
|
||||
}
|
||||
|
||||
public void setTenantId() {
|
||||
this.tenantId = true;
|
||||
this.nullable = false;
|
||||
this.dbInsertable = true;
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
public boolean isTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
|
||||
import io.ebeaninternal.server.deploy.BeanTable;
|
||||
|
||||
/**
|
||||
* Abstract base for properties mapped to an associated bean, list, set or map.
|
||||
*/
|
||||
public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
|
||||
|
||||
/**
|
||||
* The type of the joined bean.
|
||||
*/
|
||||
protected Class<T> targetType;
|
||||
|
||||
/**
|
||||
* Persist settings.
|
||||
*/
|
||||
private final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
|
||||
|
||||
/**
|
||||
* The join table information.
|
||||
*/
|
||||
private BeanTable beanTable;
|
||||
|
||||
/**
|
||||
* Join between the beans.
|
||||
*/
|
||||
protected final DeployTableJoin tableJoin = new DeployTableJoin();
|
||||
|
||||
/**
|
||||
* Literal added to where clause of lazy loading query.
|
||||
*/
|
||||
private String extraWhere;
|
||||
|
||||
/**
|
||||
* From the deployment mappedBy attribute.
|
||||
*/
|
||||
private String mappedBy;
|
||||
|
||||
private String docStoreDoc;
|
||||
|
||||
/**
|
||||
* Construct the property.
|
||||
*/
|
||||
DeployBeanPropertyAssoc(DeployBeanDescriptor<?> desc, Class<T> targetType) {
|
||||
super(desc, targetType, null, null);
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target DeployBeanDescriptor for this associated bean property.
|
||||
*/
|
||||
public DeployBeanDescriptor<?> getTargetDeploy() {
|
||||
return desc.getDeploy(targetType).getDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the target.
|
||||
* <p>
|
||||
* This is the class of the associated bean, or beans contained in a list,
|
||||
* set or map.
|
||||
* </p>
|
||||
*/
|
||||
public Class<T> getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a literal expression that is added to the query that lazy loads
|
||||
* the collection.
|
||||
*/
|
||||
public String getExtraWhere() {
|
||||
return extraWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a literal expression to add to the query that lazy loads the
|
||||
* collection.
|
||||
*/
|
||||
public void setExtraWhere(String extraWhere) {
|
||||
this.extraWhere = extraWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the join to use for the bean.
|
||||
*/
|
||||
public DeployTableJoin getTableJoin() {
|
||||
return tableJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanTable for this association.
|
||||
* <p>
|
||||
* This has the table name which is used to determine the relationship for
|
||||
* this association.
|
||||
* </p>
|
||||
*/
|
||||
public BeanTable getBeanTable() {
|
||||
return beanTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean table.
|
||||
*/
|
||||
public void setBeanTable(BeanTable beanTable) {
|
||||
this.beanTable = beanTable;
|
||||
getTableJoin().setTable(beanTable.getBaseTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persist info.
|
||||
*/
|
||||
public BeanCascadeInfo getCascadeInfo() {
|
||||
return cascadeInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the mappedBy deployment attribute.
|
||||
* <p>
|
||||
* This is the name of the property in the 'detail' bean that maps back to
|
||||
* this 'master' bean.
|
||||
* </p>
|
||||
*/
|
||||
public String getMappedBy() {
|
||||
return mappedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mappedBy deployment attribute.
|
||||
*/
|
||||
public void setMappedBy(String mappedBy) {
|
||||
if (!"".equals(mappedBy)) {
|
||||
this.mappedBy = mappedBy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set DocStoreEmbedded deployment information.
|
||||
*/
|
||||
public void setDocStoreEmbedded(String embeddedDoc) {
|
||||
this.docStoreDoc = embeddedDoc;
|
||||
}
|
||||
|
||||
public String getDocStoreDoc() {
|
||||
return docStoreDoc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import io.ebean.bean.BeanCollection.ModifyListenMode;
|
||||
import io.ebeaninternal.server.deploy.ManyType;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
|
||||
/**
|
||||
* Property mapped to a List Set or Map.
|
||||
*/
|
||||
public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
|
||||
|
||||
/**
|
||||
* The type of the many, set, list or map.
|
||||
*/
|
||||
final ManyType manyType;
|
||||
|
||||
ModifyListenMode modifyListenMode = ModifyListenMode.NONE;
|
||||
|
||||
/**
|
||||
* Flag to indicate manyToMany relationship.
|
||||
*/
|
||||
boolean manyToMany;
|
||||
|
||||
/**
|
||||
* Flag to indicate this is a unidirectional relationship.
|
||||
*/
|
||||
boolean unidirectional;
|
||||
|
||||
/**
|
||||
* Join for manyToMany intersection table.
|
||||
*/
|
||||
DeployTableJoin intersectionJoin;
|
||||
|
||||
/**
|
||||
* For ManyToMany this is the Inverse join used to build reference queries.
|
||||
*/
|
||||
DeployTableJoin inverseJoin;
|
||||
|
||||
String fetchOrderBy;
|
||||
|
||||
String mapKey;
|
||||
|
||||
String intersectionDraftTable;
|
||||
|
||||
/**
|
||||
* Create this property.
|
||||
*/
|
||||
public DeployBeanPropertyAssocMany(DeployBeanDescriptor<?> desc, Class<T> targetType, ManyType manyType) {
|
||||
super(desc, targetType);
|
||||
this.manyType = manyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* When generics is not used for manyType you can specify via annotations.
|
||||
* <p>
|
||||
* Really only expect this for Scala due to a Scala compiler bug at the moment.
|
||||
* Otherwise I'd probably not bother support this.
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setTargetType(Class<?> cls){
|
||||
this.targetType = (Class<T>)cls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the many type.
|
||||
*/
|
||||
public ManyType getManyType() {
|
||||
return manyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is many to many.
|
||||
*/
|
||||
public boolean isManyToMany() {
|
||||
return manyToMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this is a many to many.
|
||||
*/
|
||||
public void setManyToMany() {
|
||||
this.manyToMany = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode for listening to changes to the List Set or Map.
|
||||
*/
|
||||
public ModifyListenMode getModifyListenMode() {
|
||||
return modifyListenMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode for listening to changes to the List Set or Map.
|
||||
*/
|
||||
public void setModifyListenMode(ModifyListenMode modifyListenMode) {
|
||||
this.modifyListenMode = modifyListenMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a unidirectional relationship.
|
||||
*/
|
||||
public boolean isUnidirectional() {
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this is a unidirectional relationship.
|
||||
*/
|
||||
public void setUnidirectional() {
|
||||
this.unidirectional = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the immutable version of the intersection join.
|
||||
*/
|
||||
public TableJoin createIntersectionTableJoin() {
|
||||
if (intersectionJoin != null){
|
||||
return new TableJoin(intersectionJoin);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the immutable version of the inverse join.
|
||||
*/
|
||||
public TableJoin createInverseTableJoin() {
|
||||
if (inverseJoin != null){
|
||||
return new TableJoin(inverseJoin);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ManyToMany only, join from local table to intersection table.
|
||||
*/
|
||||
public DeployTableJoin getIntersectionJoin() {
|
||||
return intersectionJoin;
|
||||
}
|
||||
|
||||
public DeployTableJoin getInverseJoin() {
|
||||
return inverseJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* ManyToMany only, join from local table to intersection table.
|
||||
*/
|
||||
public void setIntersectionJoin(DeployTableJoin intersectionJoin) {
|
||||
this.intersectionJoin = intersectionJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* ManyToMany only, join from foreign table to intersection table.
|
||||
*/
|
||||
public void setInverseJoin(DeployTableJoin inverseJoin) {
|
||||
this.inverseJoin = inverseJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order by clause used to order the fetching of the data for
|
||||
* this list, set or map.
|
||||
*/
|
||||
public String getFetchOrderBy() {
|
||||
return fetchOrderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default mapKey when returning a Map.
|
||||
*/
|
||||
public String getMapKey() {
|
||||
return mapKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default mapKey to use when returning a Map.
|
||||
*/
|
||||
public void setMapKey(String mapKey) {
|
||||
if (mapKey != null && !mapKey.isEmpty()) {
|
||||
this.mapKey = mapKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order by clause used to order the fetching or the data for this
|
||||
* list, set or map.
|
||||
*/
|
||||
public void setFetchOrderBy(String orderBy) {
|
||||
if (orderBy != null && !orderBy.isEmpty()) {
|
||||
fetchOrderBy = orderBy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a draft table for intersection between 2 @Draftable entities.
|
||||
*/
|
||||
public String getIntersectionDraftTable() {
|
||||
return (intersectionDraftTable != null) ? intersectionDraftTable : intersectionJoin.getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* ManyToMany between 2 @Draftable entities to also need draft intersection table.
|
||||
*/
|
||||
public void setIntersectionDraftTable() {
|
||||
this.intersectionDraftTable = intersectionJoin.getTable()+"_draft";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user