com.avaje.ebeaninternal.server.deploy.BeanProperty Maven / Gradle / Ivy
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import com.avaje.ebean.plugin.Property;
import com.avaje.ebean.text.StringParser;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenModified;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
import com.avaje.ebeaninternal.server.properties.BeanPropertySetter;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
import com.avaje.ebeaninternal.util.ValueUtil;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebeanservice.docstore.api.support.DocStructure;
import com.fasterxml.jackson.core.JsonToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Map;
/**
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue, Property {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
/**
* Flag to mark this at part of the unique id.
*/
final boolean id;
/**
* Flag to make this as a dummy property for unidirecitonal relationships.
*/
final boolean unidirectionalShadow;
/**
* Flag set if this maps to the inheritance discriminator column
*/
final boolean discriminator;
/**
* 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).
*/
final boolean embedded;
/**
* Flag indicating if this the version property.
*/
final boolean version;
final boolean naturalKey;
/**
* Set if this property is nullable.
*/
final boolean nullable;
final boolean unique;
/**
* Is this property include in database resultSet.
*/
final boolean dbRead;
/**
* Include in DB insert.
*/
final boolean dbInsertable;
/**
* Include in DB update.
*/
final boolean dbUpdatable;
/**
* True if the property is based on a SECONDARY table.
*/
final boolean secondaryTable;
final TableJoin secondaryTableJoin;
final String secondaryTableJoinPrefix;
/**
* The property is inherited from a super class.
*/
final boolean inherited;
final Class> owningType;
final boolean local;
/**
* True if the property is a Clob, Blob LongVarchar or LongVarbinary.
*/
final boolean lob;
final boolean fetchEager;
final boolean isTransient;
/**
* The logical bean property name.
*/
final String name;
final int propertyIndex;
/**
* The reflected field.
*/
final Field field;
/**
* The bean type.
*/
final Class> propertyType;
final String dbBind;
/**
* The database column. This can include quoted identifiers.
*/
final String dbColumn;
final String elPlaceHolder;
final String elPlaceHolderEncrypted;
/**
* Select part of a SQL Formula used to populate this property.
*/
final String sqlFormulaSelect;
/**
* Join part of a SQL Formula.
*/
final String sqlFormulaJoin;
final boolean formula;
/**
* Set to true if stored encrypted.
*/
final boolean dbEncrypted;
final boolean localEncrypted;
final int dbEncryptedType;
/**
* The jdbc data type this maps to.
*/
final int dbType;
final boolean excludedFromHistory;
/**
* Generator for insert or update timestamp etc.
*/
final GeneratedProperty generatedProperty;
final BeanPropertyGetter getter;
final BeanPropertySetter setter;
final BeanDescriptor> descriptor;
/**
* Used for non-jdbc native types (java.util.Date Enums etc). Converts from
* logical to jdbc types.
*/
@SuppressWarnings("rawtypes")
final ScalarType scalarType;
final DocPropertyOptions docOptions;
/**
* The length or precision for DB column.
*/
final int dbLength;
/**
* The scale for DB column (decimal).
*/
final int dbScale;
/**
* Deployment defined DB column definition.
*/
final String dbColumnDefn;
/**
* DB Column default value for DDL definition (FALSE, NOW etc).
*/
final String dbColumnDefault;
/**
* Database DDL column comment.
*/
final String dbComment;
/**
* DB Constraint (typically check constraint on enum)
*/
final String dbConstraintExpression;
final DbEncryptFunction dbEncryptFunction;
int deployOrder;
final boolean jsonSerialize;
final boolean jsonDeserialize;
final boolean draft;
final boolean draftOnly;
final boolean draftDirty;
final boolean draftReset;
final boolean softDelete;
final String softDeleteDbSet;
final String softDeleteDbPredicate;
final boolean indexed;
final String indexName;
public BeanProperty(DeployBeanProperty deploy) {
this(null, deploy);
}
public BeanProperty(BeanDescriptor> descriptor, DeployBeanProperty deploy) {
this.descriptor = descriptor;
this.name = InternString.intern(deploy.getName());
this.propertyIndex = deploy.getPropertyIndex();
this.indexed = deploy.isIndexed();
this.indexName = deploy.getIndexName();
this.unidirectionalShadow = deploy.isUndirectionalShadow();
this.discriminator = deploy.isDiscriminator();
this.localEncrypted = deploy.isLocalEncrypted();
this.dbEncrypted = deploy.isDbEncrypted();
this.dbEncryptedType = deploy.getDbEncryptedType();
this.dbEncryptFunction = deploy.getDbEncryptFunction();
this.dbBind = deploy.getDbBind();
this.dbRead = deploy.isDbRead();
this.dbInsertable = deploy.isDbInsertable();
this.dbUpdatable = deploy.isDbUpdateable();
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.draft = deploy.isDraft();
this.draftDirty = deploy.isDraftDirty();
this.draftOnly = deploy.isDraftOnly();
this.draftReset = deploy.isDraftReset();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
this.secondaryTableJoinPrefix = deploy.getSecondaryTableJoinPrefix();
} else {
this.secondaryTableJoin = null;
this.secondaryTableJoinPrefix = null;
}
this.fetchEager = deploy.isFetchEager();
this.isTransient = deploy.isTransient();
this.nullable = deploy.isNullable();
this.unique = deploy.isUnique();
this.naturalKey = deploy.isNaturalKey();
this.dbLength = deploy.getDbLength();
this.dbScale = deploy.getDbScale();
this.dbColumnDefn = InternString.intern(deploy.getDbColumnDefn());
this.dbConstraintExpression = InternString.intern(deploy.getDbConstraintExpression());
this.dbColumnDefault = deploy.getDbColumnDefault();
this.inherited = false;// deploy.isInherited();
this.owningType = deploy.getOwningType();
this.local = deploy.isLocal();
this.version = deploy.isVersionColumn();
this.embedded = deploy.isEmbedded();
this.id = deploy.isId();
this.generatedProperty = deploy.getGeneratedProperty();
this.getter = deploy.getGetter();
this.setter = deploy.getSetter();
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
this.dbComment = deploy.getDbComment();
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
this.formula = sqlFormulaSelect != null;
this.dbType = deploy.getDbType();
this.scalarType = deploy.getScalarType();
this.lob = isLobType(dbType);
this.propertyType = deploy.getPropertyType();
this.field = deploy.getField();
this.docOptions = deploy.getDocPropertyOptions();
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
this.softDelete = deploy.isSoftDelete();
if (softDelete) {
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
this.softDeleteDbPredicate = "."+dbColumn+","+boolType.getDbFalseLiteral()+")="+boolType.getDbFalseLiteral();
} else {
this.softDeleteDbSet = null;
this.softDeleteDbPredicate = null;
}
this.jsonSerialize = deploy.isJsonSerialize();
this.jsonDeserialize = deploy.isJsonDeserialize();
}
private String tableAliasIntern(BeanDescriptor> descriptor, String s, boolean dbEncrypted, String dbColumn) {
if (descriptor != null) {
s = StringHelper.replaceString(s, "${ta}.", "${}");
s = StringHelper.replaceString(s, "${ta}", "${}");
if (dbEncrypted) {
s = dbEncryptFunction.getDecryptSql(s);
String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn;
s = StringHelper.replaceString(s, "?", namedParam);
}
}
return InternString.intern(s);
}
/**
* Create a Matching BeanProperty with some attributes overridden.
*
* Primarily for supporting Embedded beans with overridden dbColumn
* mappings.
*
*/
public BeanProperty(BeanProperty source, BeanPropertyOverride override) {
this.descriptor = source.descriptor;
this.name = InternString.intern(source.getName());
this.propertyIndex = source.propertyIndex;
this.indexed = source.isIndexed();
this.indexName = source.getIndexName();
this.dbColumn = InternString.intern(override.getDbColumn());
// override with sqlFormula not currently supported
this.sqlFormulaJoin = null;
this.sqlFormulaSelect = null;
this.formula = false;
this.excludedFromHistory = source.excludedFromHistory;
this.draft = source.draft;
this.draftDirty = source.draftDirty;
this.draftOnly = source.draftOnly;
this.draftReset = source.draftReset;
this.softDelete = source.softDelete;
this.softDeleteDbSet = source.softDeleteDbSet;
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
this.fetchEager = source.fetchEager;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
this.localEncrypted = source.isLocalEncrypted();
this.isTransient = source.isTransient();
this.secondaryTable = source.isSecondaryTable();
this.secondaryTableJoin = source.secondaryTableJoin;
this.secondaryTableJoinPrefix = source.secondaryTableJoinPrefix;
this.dbComment = source.dbComment;
this.dbBind = source.getDbBind();
this.dbEncrypted = source.isDbEncrypted();
this.dbEncryptedType = source.getDbEncryptedType();
this.dbEncryptFunction = source.dbEncryptFunction;
this.dbRead = source.isDbRead();
this.dbInsertable = source.isDbInsertable();
this.dbUpdatable = source.isDbUpdatable();
this.nullable = source.isNullable();
this.unique = source.isUnique();
this.naturalKey = source.isNaturalKey();
this.dbLength = source.getDbLength();
this.dbScale = source.getDbScale();
this.dbColumnDefn = InternString.intern(source.getDbColumnDefn());
this.dbConstraintExpression = InternString.intern(source.getDbConstraintExpression());
this.dbColumnDefault = source.dbColumnDefault;
this.inherited = source.isInherited();
this.owningType = source.owningType;
this.local = owningType.equals(descriptor.getBeanType());
this.version = source.isVersion();
this.embedded = source.isEmbedded();
this.id = source.isId();
this.generatedProperty = source.getGeneratedProperty();
this.getter = source.getter;
this.setter = source.setter;
this.dbType = source.getDbType();
this.scalarType = source.scalarType;
this.lob = isLobType(dbType);
this.propertyType = source.getPropertyType();
this.field = source.getField();
this.docOptions = source.docOptions;
this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn);
this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn);
this.jsonSerialize = source.jsonSerialize;
this.jsonDeserialize = source.jsonDeserialize;
}
/**
* Initialise the property before returning to client code. Used to
* initialise variables that can't be done in construction due to recursive
* issues.
*/
public void initialise() {
// do nothing for normal BeanProperty
if (!isTransient && scalarType == null) {
String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName();
throw new RuntimeException(msg);
}
}
/**
* Return the order this property appears in the bean.
*/
public int getDeployOrder() {
return deployOrder;
}
/**
* Set the order this property appears in the bean.
*/
public void setDeployOrder(int deployOrder) {
this.deployOrder = deployOrder;
}
public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain,
boolean propertyDeploy) {
return null;
}
/**
* Return the BeanDescriptor that owns this property.
*/
public BeanDescriptor> getBeanDescriptor() {
return descriptor;
}
/**
* Return true is this is a simple scalar property.
*/
public boolean isScalar() {
return true;
}
/**
* Return true if this property is based on a formula.
*/
public boolean isFormula() {
return formula;
}
/**
* Return true if this property maps to the inheritance discriminator column.
*/
public boolean isDiscriminator() {
return discriminator;
}
/**
* Return true if the underlying type is mutable.
*/
public boolean isMutableScalarType() {
return scalarType != null && scalarType.isMutable();
}
/**
* Return the encrypt key for the column matching this property.
*/
public EncryptKey getEncryptKey() {
return descriptor.getEncryptKey(this);
}
public String getDecryptProperty(String propertyName) {
return dbEncryptFunction.getDecryptSql(propertyName);
}
public String getDecryptSql() {
return dbEncryptFunction.getDecryptSql(this.getDbColumn());
}
public String getDecryptSql(String tableAlias) {
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn());
}
/**
* Add any extra joins required to support this property. Generally a no
* operation except for a OneToOne exported.
*/
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (formula && sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
} else if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
secondaryTableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
/**
* Returns null unless this property is using a secondary table. In that
* case this returns the logical property prefix.
*/
public String getSecondaryTableJoinPrefix() {
return secondaryTableJoinPrefix;
}
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (formula) {
ctx.appendFormulaSelect(sqlFormulaSelect);
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
ctx.pushTableAlias(relativePrefix);
}
if (dbEncrypted) {
String decryptSql = getDecryptSql(ctx.peekTableAlias());
ctx.appendRawColumn(decryptSql);
ctx.addEncryptedProp(this);
} else {
ctx.appendColumn(dbColumn);
}
if (secondaryTableJoin != null) {
ctx.popTableAlias();
}
}
}
@Override
public boolean isMany() {
return false;
}
public boolean isAssignableFrom(Class> type) {
return owningType.isAssignableFrom(type);
}
public void loadIgnore(DbReadContext ctx) {
scalarType.loadIgnore(ctx.getDataReader());
}
public void load(SqlBeanLoad sqlBeanLoad) {
sqlBeanLoad.load(this);
}
public void buildRawSqlSelectChain(String prefix, List selectChain) {
if (prefix == null) {
selectChain.add(name);
} else {
selectChain.add(prefix + "." + name);
}
}
public Object read(DbReadContext ctx) throws SQLException {
return scalarType.read(ctx.getDataReader());
}
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
try {
Object value = scalarType.read(ctx.getDataReader());
if (bean != null) {
setValue(bean, value);
}
return value;
} catch (Exception e) {
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
}
}
@SuppressWarnings("unchecked")
public void bind(DataBind b, Object value) throws SQLException {
scalarType.bind(b, value);
}
@SuppressWarnings(value = "unchecked")
public void writeData(DataOutput dataOutput, Object value) throws IOException {
scalarType.writeData(dataOutput, value);
}
public Object readData(DataInput dataInput) throws IOException {
return scalarType.readData(dataInput);
}
public BeanProperty getBeanProperty() {
return this;
}
public boolean isIndexed() {
return indexed;
}
public String getIndexName() {
return indexName;
}
/**
* Return true if this object is part of an inheritance hierarchy.
*/
public boolean isInherited() {
return inherited;
}
/**
* Return true is this type is not from a super type.
*/
public boolean isLocal() {
return local;
}
/**
* Copy/set the property value from the draft bean to the live bean.
*/
public void publish(EntityBean draftBean, EntityBean liveBean) {
if (!version && !draftOnly) {
// set property value from draft to live
Object value = getValueIntercept(draftBean);
setValueIntercept(liveBean, value);
}
}
/**
* Return the DB literal expression to set the deleted state to true.
*/
public String getSoftDeleteDbSet() {
return softDeleteDbSet;
}
/**
* Return the DB literal predicate used to filter out soft deleted rows from a query.
*/
public String getSoftDeleteDbPredicate(String tableAlias) {
// use coalesce to handle null values from optional relationships
return "coalesce(" + tableAlias + softDeleteDbPredicate;
}
/**
* Set the soft delete property value on the bean without invoking lazy loading.
*/
public void setSoftDeleteValue(EntityBean bean) {
// assumes boolean deleted true being set which is ok limitation for now
setValue(bean, true);
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the changed value without invoking interception (lazy loading etc).
* Typically used to set generated values on update.
*/
public void setValueChanged(EntityBean bean, Object value) {
setValue(bean, value);
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
*/
public void setValue(EntityBean bean, Object value) {
try {
setter.set(bean, value);
} catch (Exception ex) {
throw new RuntimeException(setterErrorMsg(bean, value, "set "), ex);
}
}
/**
* Set the value of the property.
*/
public void setValueIntercept(EntityBean bean, Object value) {
try {
setter.setIntercept(bean, value);
} catch (Exception ex) {
throw new RuntimeException(setterErrorMsg(bean, value, "setIntercept "), ex);
}
}
/**
* Return an error message when calling a setter.
*/
private String setterErrorMsg(EntityBean bean, Object value, String prefix) {
String beanType = bean == null ? "null" : bean.getClass().getName();
return prefix + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + "] threw error";
}
public Object getCacheDataValue(EntityBean bean) {
return getValue(bean);
}
public void setCacheDataValue(EntityBean bean, Object cacheData) {
setValue(bean, cacheData);
}
/**
* Get the property value from a compound value type.
*/
public Object getValueObject(Object bean) {
throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar");
}
public Object getVal(Object bean) {
return getValue((EntityBean)bean);
}
/**
* Return the value of the property method.
*/
public Object getValue(EntityBean bean) {
try {
return getter.get(bean);
} catch (Exception ex) {
String beanType = bean == null ? "null" : bean.getClass().getName();
String msg = "get " + name + " on [" + descriptor + "] type[" + beanType + "] threw error.";
throw new RuntimeException(msg, ex);
}
}
public Object getValueIntercept(EntityBean bean) {
try {
return getter.getIntercept(bean);
} catch (Exception ex) {
String beanType = bean == null ? "null" : bean.getClass().getName();
String msg = "getIntercept " + name + " on [" + descriptor + "] type[" + beanType + "] threw error.";
throw new RuntimeException(msg, ex);
}
}
public Object convert(Object value) {
if (value == null) {
return null;
}
return convertToLogicalType(value);
}
@Override
public void pathSet(Object bean, Object value) {
if (bean != null) {
Object logicalVal = convertToLogicalType(value);
setValue((EntityBean)bean, logicalVal);
}
}
@Override
public Object pathGet(Object bean) {
if (bean == null) {
return null;
}
return getValueIntercept((EntityBean)bean);
}
@Override
public Object pathGetNested(Object bean) {
throw new RuntimeException("Not expected to call this");
}
/**
* Return the name of the property.
*/
public String getName() {
return name;
}
/**
* Return the position of this property in the enhanced bean.
*/
public int getPropertyIndex() {
return propertyIndex;
}
public String getElName() {
return name;
}
@Override
public boolean containsFormulaWithJoin() {
return formula && sqlFormulaJoin != null;
}
public boolean containsManySince(String sinceProperty) {
return containsMany();
}
public boolean containsMany() {
return false;
}
public Object[] getAssocIdValues(EntityBean bean) {
// Returns null as not an AssocOne.
return null;
}
public String getAssocIdExpression(String prefix, String operator) {
// Returns null as not an AssocOne.
return null;
}
public String getAssocIdInExpr(String prefix) {
// Returns null as not an AssocOne.
return null;
}
public String getAssocIdInValueExpr(int size) {
// Returns null as not an AssocOne.
return null;
}
public boolean isAssocId() {
// Returns false - override in BeanPropertyAssocOne.
return false;
}
public boolean isAssocProperty() {
// Returns false - override in BeanPropertyAssocOne.
return false;
}
public String getElPlaceholder(boolean encrypted) {
return encrypted ? elPlaceHolderEncrypted : elPlaceHolder;
}
public String getElPrefix() {
return secondaryTableJoinPrefix;
}
/**
* Return the full name of this property.
*/
public String getFullBeanName() {
return descriptor.getFullName() + "." + name;
}
/**
* Return true if the mutable value is considered dirty.
* This is only used for 'mutable' scalar types like hstore etc.
*/
public boolean isDirtyValue(Object value) {
return scalarType.isDirty(value);
}
/**
* Return the scalarType.
*/
@SuppressWarnings(value = "unchecked")
public ScalarType