package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.Aggregation;
import io.ebean.annotation.CreatedTimestamp;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbComment;
import io.ebean.annotation.DbDefault;
import io.ebean.annotation.DbJson;
import io.ebean.annotation.DbJsonB;
import io.ebean.annotation.DbMap;
import io.ebean.annotation.DbMigration;
import io.ebean.annotation.DocCode;
import io.ebean.annotation.DocEmbedded;
import io.ebean.annotation.DocProperty;
import io.ebean.annotation.DocSortable;
import io.ebean.annotation.Draft;
import io.ebean.annotation.DraftDirty;
import io.ebean.annotation.DraftOnly;
import io.ebean.annotation.DraftReset;
import io.ebean.annotation.Encrypted;
import io.ebean.annotation.Expose;
import io.ebean.annotation.Formula;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.Identity;
import io.ebean.annotation.Index;
import io.ebean.annotation.JsonIgnore;
import io.ebean.annotation.Length;
import io.ebean.annotation.SoftDelete;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.UnmappedJson;
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.EncryptDeploy;
import io.ebean.config.EncryptDeploy.Mode;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
import io.ebeaninternal.server.deploy.IndexDefinition;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.type.DataEncryptSupport;
import io.ebeaninternal.server.type.ScalarTypeBytesBase;
import io.ebeaninternal.server.type.ScalarTypeBytesEncrypted;
import io.ebeaninternal.server.type.ScalarTypeEncryptedWrapper;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceException;
import javax.persistence.SequenceGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
public class AnnotationFields extends AnnotationParser {
private final boolean jacksonAnnotationsPresent;
private final GeneratedPropertyFactory generatedPropFactory;
private FetchType defaultLobFetchType = FetchType.LAZY;
AnnotationFields(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info, readConfig);
this.jacksonAnnotationsPresent = readConfig.isJacksonAnnotations();
this.generatedPropFactory = readConfig.getGeneratedPropFactory();
if (readConfig.isEagerFetchLobs()) {
defaultLobFetchType = FetchType.EAGER;
}
}
@Override
public void parse() {
for (DeployBeanProperty prop : descriptor.propertiesAll()) {
prop.initMetaAnnotations(readConfig.getMetaAnnotations());
if (prop instanceof DeployBeanPropertyAssoc<?>) {
readAssocOne((DeployBeanPropertyAssoc<?>) prop);
} else {
readField(prop);
}
}
}
private void readAssocOne(DeployBeanPropertyAssoc<?> prop) {
readJsonAnnotations(prop);
if (has(prop, Id.class)) {
readIdAssocOne(prop);
}
if (has(prop, EmbeddedId.class)) {
prop.setId();
prop.setNullable(false);
prop.setEmbedded();
info.setEmbeddedId(prop);
}
DocEmbedded docEmbedded = get(prop, DocEmbedded.class);
if (docEmbedded != null) {
prop.setDocStoreEmbedded(docEmbedded.doc());
if (descriptor.isDocStoreOnly()) {
if (has(prop, ManyToOne.class)) {
prop.setEmbedded();
prop.setDbInsertable(true);
prop.setDbUpdateable(true);
}
}
}
if (prop instanceof DeployBeanPropertyAssocOne<?>) {
if (prop.isId() && !prop.isEmbedded()) {
prop.setEmbedded();
}
readEmbeddedAttributeOverrides((DeployBeanPropertyAssocOne<?>) prop);
}
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
}
initWhoProperties(prop);
initDbMigration(prop);
}
private void initWhoProperties(DeployBeanProperty prop) {
if (has(prop, WhoModified.class)) {
generatedPropFactory.setWhoModified(prop);
}
if (has(prop, WhoCreated.class)) {
generatedPropFactory.setWhoCreated(prop);
}
}
private void readField(DeployBeanProperty prop) {
boolean isEnum = prop.getPropertyType().isEnum();
Enumerated enumerated = get(prop, Enumerated.class);
if (isEnum || enumerated != null) {
util.setEnumScalarType(enumerated, prop);
}
prop.setDbRead(true);
prop.setDbInsertable(true);
prop.setDbUpdateable(true);
Column column = prop.getMetaAnnotation(Column.class);
if (column != null) {
readColumn(column, prop);
}
readJsonAnnotations(prop);
if (prop.getDbColumn() == null) {
prop.setDbColumn(namingConvention.getColumnFromProperty(beanType, prop.getName()));
}
initIdentity(prop);
initTenantId(prop);
initDbJson(prop);
initFormula(prop);
initVersion(prop);
initWhen(prop);
initWhoProperties(prop);
initDbMigration(prop);
if (has(prop, Transient.class)) {
prop.setDbRead(false);
prop.setDbInsertable(false);
prop.setDbUpdateable(false);
prop.setTransient();
}
initEncrypt(prop);
for (Index index : annotationIndexes(prop)) {
addIndex(prop, index);
}
}
private void initIdentity(DeployBeanProperty prop) {
Id id = get(prop, Id.class);
GeneratedValue gen = get(prop, GeneratedValue.class);
if (gen != null) {
readGenValue(gen, id, prop);
}
if (id != null) {
readIdScalar(prop);
}
Identity identity = get(prop, Identity.class);
if (identity != null) {
readIdentity(identity);
}
Temporal temporal = get(prop, Temporal.class);
if (temporal != null) {
readTemporal(temporal, prop);
} else if (has(prop, Lob.class)) {
util.setLobType(prop);
}
Length length = get(prop, Length.class);
if (length != null) {
prop.setDbLength(length.value());
}
if (has(prop, io.ebean.annotation.NotNull.class)) {
prop.setNullable(false);
}
}
private void initValidation(DeployBeanProperty prop) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
prop.setNullable(false);
}
if (!prop.isLob()) {
int maxSize = -1;
for (Size size : prop.getMetaAnnotationSize()) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
if (maxSize != -1) {
prop.setDbLength(maxSize);
}
}
}
private void initTenantId(DeployBeanProperty prop) {
if (validationAnnotations) {
initValidation(prop);
}
if (has(prop, TenantId.class)) {
prop.setTenantId();
}
if (has(prop, Draft.class)) {
prop.setDraft();
}
if (has(prop, DraftOnly.class)) {
prop.setDraftOnly();
}
if (has(prop, DraftDirty.class)) {
prop.setDraftDirty();
}
if (has(prop, DraftReset.class)) {
prop.setDraftReset();
}
if (has(prop, SoftDelete.class)) {
prop.setSoftDelete();
}
}
private void initDbJson(DeployBeanProperty prop) {
DbComment comment = get(prop, DbComment.class);
if (comment != null) {
prop.setDbComment(comment.value());
}
DbMap dbMap = get(prop, DbMap.class);
if (dbMap != null) {
util.setDbMap(prop, dbMap);
setColumnName(prop, dbMap.name());
}
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
util.setDbJsonType(prop, dbJson);
setColumnName(prop, dbJson.name());
} else {
DbJsonB dbJsonB = get(prop, DbJsonB.class);
if (dbJsonB != null) {
util.setDbJsonBType(prop, dbJsonB);
setColumnName(prop, dbJsonB.name());
}
}
DbArray dbArray = get(prop, DbArray.class);
if (dbArray != null) {
util.setDbArray(prop, dbArray);
setColumnName(prop, dbArray.name());
}
}
private void initFormula(DeployBeanProperty prop) {
DocCode docCode = get(prop, DocCode.class);
if (docCode != null) {
prop.setDocCode(docCode);
}
DocSortable docSortable = get(prop, DocSortable.class);
if (docSortable != null) {
prop.setDocSortable(docSortable);
}
DocProperty docProperty = get(prop, DocProperty.class);
if (docProperty != null) {
prop.setDocProperty(docProperty);
}
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
}
final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class);
if (aggregation != null) {
prop.setAggregation(aggregation.value().replace("$1", prop.getName()));
}
}
private void initVersion(DeployBeanProperty prop) {
if (has(prop, Version.class)) {
prop.setVersionColumn();
generatedPropFactory.setVersion(prop);
}
Basic basic = get(prop, Basic.class);
if (basic != null) {
prop.setFetchType(basic.fetch());
if (!basic.optional()) {
prop.setNullable(false);
}
} else if (prop.isLob()) {
prop.setFetchType(defaultLobFetchType);
}
}
private void initWhen(DeployBeanProperty prop) {
if (has(prop, WhenCreated.class) || has(prop, CreatedTimestamp.class)) {
generatedPropFactory.setInsertTimestamp(prop);
}
if (has(prop, WhenModified.class) || has(prop, UpdatedTimestamp.class)) {
generatedPropFactory.setUpdateTimestamp(prop);
}
}
private void initEncrypt(DeployBeanProperty prop) {
if (!prop.isTransient()) {
EncryptDeploy encryptDeploy = util.getEncryptDeploy(info.getDescriptor().getBaseTableFull(), prop.getDbColumn());
if (encryptDeploy == null || encryptDeploy.getMode() == Mode.MODE_ANNOTATION) {
Encrypted encrypted = get(prop, Encrypted.class);
if (encrypted != null) {
setEncryption(prop, encrypted.dbEncryption(), encrypted.dbLength());
}
} else if (Mode.MODE_ENCRYPT == encryptDeploy.getMode()) {
setEncryption(prop, encryptDeploy.isDbEncrypt(), encryptDeploy.getDbLength());
}
}
}
private void readIdentity(Identity identity) {
descriptor.setIdentityMode(identity);
}
private void initDbMigration(DeployBeanProperty prop) {
if (has(prop, HistoryExclude.class)) {
prop.setExcludedFromHistory();
}
DbDefault dbDefault = get(prop, DbDefault.class);
if (dbDefault != null) {
prop.setDbColumnDefault(dbDefault.value());
}
Set<DbMigration> dbMigration = annotationDbMigrations(prop);
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
}
private void addIndex(DeployBeanProperty prop, Index index) {
String[] columnNames;
if (index.columnNames().length == 0) {
columnNames = new String[]{prop.getDbColumn()};
} else {
columnNames = new String[index.columnNames().length];
int i = 0;
int found = 0;
for (String colName : index.columnNames()) {
if (colName.equals("${fa}") || colName.equals(prop.getDbColumn())) {
columnNames[i++] = prop.getDbColumn();
found++;
} else {
columnNames[i++] = colName;
}
}
if (found != 1) {
throw new RuntimeException("DB-columname has to be specified exactly one time in columnNames.");
}
}
if (columnNames.length == 1 && hasRelationshipItem(prop)) {
throw new RuntimeException("Can't use Index on foreign key relationships.");
}
descriptor.addIndex(new IndexDefinition(columnNames, index.name(), index.unique(), index.platforms(), index.concurrent(), index.definition()));
}
private void readJsonAnnotations(DeployBeanProperty prop) {
if (jacksonAnnotationsPresent) {
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(!jsonIgnore.value());
prop.setJsonDeserialize(!jsonIgnore.value());
}
}
Expose expose = get(prop, Expose.class);
if (expose != null) {
prop.setJsonSerialize(expose.serialize());
prop.setJsonDeserialize(expose.deserialize());
}
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(jsonIgnore.serialize());
prop.setJsonDeserialize(jsonIgnore.deserialize());
}
if (has(prop, UnmappedJson.class)) {
prop.setUnmappedJson();
}
}
private boolean hasRelationshipItem(DeployBeanProperty prop) {
return has(prop, OneToMany.class) || has(prop, ManyToOne.class) || has(prop, OneToOne.class);
}
private void setEncryption(DeployBeanProperty prop, boolean dbEncString, int dbLen) {
util.checkEncryptKeyManagerDefined(prop.getFullBeanName());
ScalarType<?> st = prop.getScalarType();
if (byte[].class.equals(st.getType())) {
ScalarTypeBytesBase baseType = (ScalarTypeBytesBase) st;
DataEncryptSupport support = createDataEncryptSupport(prop);
ScalarTypeBytesEncrypted encryptedScalarType = new ScalarTypeBytesEncrypted(baseType, support);
prop.setScalarType(encryptedScalarType);
prop.setLocalEncrypted();
return;
}
if (dbEncString) {
DbEncrypt dbEncrypt = util.getDbPlatform().getDbEncrypt();
if (dbEncrypt != null) {
int jdbcType = prop.getScalarType().getJdbcType();
DbEncryptFunction dbEncryptFunction = dbEncrypt.getDbEncryptFunction(jdbcType);
if (dbEncryptFunction != null) {
prop.setDbEncryptFunction(dbEncryptFunction, dbEncrypt, dbLen);
return;
}
}
}
prop.setScalarType(createScalarType(prop, st));
prop.setLocalEncrypted();
if (dbLen > 0) {
prop.setDbLength(dbLen);
}
}
@SuppressWarnings({"unchecked"})
private ScalarTypeEncryptedWrapper<?> createScalarType(DeployBeanProperty prop, ScalarType<?> st) {
DataEncryptSupport support = createDataEncryptSupport(prop);
ScalarTypeBytesBase byteType = getDbEncryptType(prop);
return new ScalarTypeEncryptedWrapper(st, byteType, support);
}
private ScalarTypeBytesBase getDbEncryptType(DeployBeanProperty prop) {
int dbType = prop.isLob() ? Types.BLOB : Types.VARBINARY;
return (ScalarTypeBytesBase) util.getTypeManager().getScalarType(dbType);
}
private DataEncryptSupport createDataEncryptSupport(DeployBeanProperty prop) {
String table = info.getDescriptor().getBaseTable();
String column = prop.getDbColumn();
return util.createDataEncryptSupport(table, column);
}
private void readGenValue(GeneratedValue gen, Id id, DeployBeanProperty prop) {
if (id == null) {
if (UUID.class.equals(prop.getPropertyType())) {
generatedPropFactory.setUuid(prop);
return;
}
}
descriptor.setIdGeneratedValue();
SequenceGenerator seq = get(prop, SequenceGenerator.class);
if (seq != null) {
String seqName = seq.sequenceName();
if (seqName.isEmpty()) {
seqName = namingConvention.getSequenceName(descriptor.getBaseTable(), prop.getDbColumn());
}
descriptor.setIdentitySequence(seq.initialValue(), seq.allocationSize(), seqName);
}
GenerationType strategy = gen.strategy();
if (strategy == GenerationType.IDENTITY) {
descriptor.setIdentityType(IdType.IDENTITY);
} else if (strategy == GenerationType.SEQUENCE) {
descriptor.setIdentityType(IdType.SEQUENCE);
if (!gen.generator().isEmpty()) {
descriptor.setIdentitySequenceGenerator(gen.generator());
}
} else if (strategy == GenerationType.AUTO) {
if (!gen.generator().isEmpty()) {
PlatformIdGenerator idGenerator = generatedPropFactory.getIdGenerator(gen.generator());
if (idGenerator == null) {
throw new IllegalStateException("No custom IdGenerator registered with name " + gen.generator());
}
descriptor.setCustomIdGenerator(idGenerator);
} else if (prop.getPropertyType().equals(UUID.class)) {
descriptor.setUuidGenerator();
}
}
}
private void readTemporal(Temporal temporal, DeployBeanProperty prop) {
TemporalType type = temporal.value();
if (type == TemporalType.DATE) {
prop.setDbType(Types.DATE);
} else if (type == TemporalType.TIMESTAMP) {
prop.setDbType(Types.TIMESTAMP);
} else if (type == TemporalType.TIME) {
prop.setDbType(Types.TIME);
} else {
throw new PersistenceException("Unhandled type " + type);
}
}
}