added foreign keys

This commit is contained in:
wea_ondara
2022-07-24 13:13:13 +02:00
parent 7ac7799d57
commit ff4385aa5c
48 changed files with 2139 additions and 549 deletions

View File

@@ -4,6 +4,7 @@ import jef.expressions.Expression;
public class DatabaseFunctionExpression implements Expression, SelectableExpression {
private final String function;
public DatabaseFunctionExpression(String function) {
this.function = function;
}

View File

@@ -31,11 +31,11 @@ public class DbEntity<T extends SerializableObject> {
private final List<KeyConstraint> keys = new ArrayList<>();
private final List<IndexConstraint> indexes = new ArrayList<>();
public DbEntity(Class<T> type) {
DbEntity(Class<T> type) {
this(type, new ArrayList<>());
}
public DbEntity(Class<T> type, List<DbField<?>> fields) {
DbEntity(Class<T> type, List<DbField<?>> fields) {
this.type = type;
this.fields = fields;
this.name = type.getSimpleName();
@@ -93,7 +93,7 @@ public class DbEntity<T extends SerializableObject> {
}
}
public <R> DbField<R> getOrAddField(DbField<R> field) {
public <R> DbField<R> addIfAbsent(DbField<R> field) {
try {
var prop = (DbField<R>) fields.stream().filter(e -> e.getName().equals(field.getName())).findFirst().orElse(null);
if (prop == null) {
@@ -120,18 +120,34 @@ public class DbEntity<T extends SerializableObject> {
}
public void addForeignKey(ForeignKeyConstraint foreignKey) {
foreignKeys.add(foreignKey);
if (!foreignKeys.contains(foreignKey)) {
foreignKeys.add(foreignKey);
}
}
public void addUniqueContstraint(UniqueConstraint uniqueConstraint) {
uniqueKeys.add(uniqueConstraint);
if (!uniqueKeys.contains(uniqueConstraint)) {
uniqueKeys.add(uniqueConstraint);
}
}
public void addIndex(IndexConstraint index) {
indexes.add(index);
if (!indexes.contains(index)) {
indexes.add(index);
}
}
public void addKey(KeyConstraint key) {
keys.add(key);
if (!keys.contains(key)) {
keys.add(key);
}
}
@Override
public String toString() {
return "DbEntity{" +
"type=" + type +
", name='" + name + '\'' +
'}';
}
}

View File

@@ -9,6 +9,7 @@ import lombok.Setter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Getter
@@ -17,21 +18,27 @@ public class DbField<T> {
private final DbEntity<? extends SerializableObject> entity;
private final Class<T> type;
private final Field field;
private boolean isModelField;
private boolean isDatabaseField;
private DbField<?> foreignKeyModelLink;
private String name;
private boolean notNull = false;
public DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
this.entity = entity;
this.type = type;
this.field = field;
this.name = field.getName();
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
this(entity, type, field, field.getName());
}
public DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
this.entity = entity;
this.type = type;
this.field = field;
this.name = name;
this.isModelField = field != null;
this.isDatabaseField = !Collection.class.isAssignableFrom(type) && !SerializableObject.class.isAssignableFrom(type);
}
public boolean isForeignKey() {
return entity.getForeignKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
}
public boolean isUnique() {
@@ -72,4 +79,13 @@ public class DbField<T> {
entity.getKeys().remove(constr.get());
} //else do nothing
}
@Override
public String toString() {
return "DbField{" +
"name=" + name +
", type=" + type.getSimpleName() + (notNull ? "" : "?") +
", entity=" + entity +
'}';
}
}

View File

@@ -0,0 +1,62 @@
package jef.model;
import jef.DbSet;
import jef.model.annotations.Clazz;
import jef.model.annotations.Transient;
import jef.serializable.SerializableObject;
import java.lang.reflect.Field;
import java.util.Collection;
class EntityInitializer {
static void initEntities(ModelBuilder mb, Class<? extends DbContext> context) {
for (Field ctxfield : context.getDeclaredFields()) {
if (!DbSet.class.isAssignableFrom(ctxfield.getType())) {
continue;
}
Clazz clazzAnnotation = ctxfield.getAnnotation(Clazz.class);
if (clazzAnnotation == null) {
throw new ModelException("DbSet " + ctxfield.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var dbsetClazz = (Class<? extends SerializableObject>) clazzAnnotation.clazz();
initEntity(mb, dbsetClazz);
}
}
static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz) {
var entity = mb.getOrCreateEntity(clazz);
var fields = ReflectionUtil.getFieldsRecursive(clazz);
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (Collection.class.isAssignableFrom(f.getType())) {
//find a Collection field with the same Model
//e.g. class Entity { @Clazz(Entity2.class) List<Entity2> ent; @Clazz(Entity2.class) Set<Entity2> ent2; }
var clazzAnnotation = f.getAnnotationsByType(Clazz.class);
if (clazzAnnotation.length == 0) {
throw new ModelException("Field " + f.getClass().getSimpleName() + "::" + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var fClazz = clazzAnnotation[0].clazz();
var foundCollection = entity.getFields().stream()
.filter(e -> Collection.class.isAssignableFrom(e.getType())
&& e.isModelField()
&& e.getField().getAnnotationsByType(Clazz.class).length > 0
&& e.getField().getAnnotationsByType(Clazz.class)[0].clazz() == fClazz)
.findFirst();
if (foundCollection.isPresent()) {
throw new ModelException("Model " + entity.getType().getSimpleName() + " multiple contains a 1 to N relation with type " + fClazz.getSimpleName());
}
entity.getOrCreateField(f);
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {
entity.getOrCreateField(f);
} else {
var dbField = entity.getOrCreateField(f);
if (f.getType().isPrimitive()) {
dbField.setNotNull(true);
}
}
}
}
}

View File

@@ -0,0 +1,153 @@
package jef.model;
import jef.model.annotations.Clazz;
import jef.model.annotations.ForeignKey;
import jef.model.annotations.Transient;
import jef.model.constraints.ForeignKeyConstraint;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
class ForeignKeyInitializer {
static void initForeignKeys(ModelBuilder mb) {
for (int i = 0; i < mb.getEntities().size(); i++) {
var entity = mb.getEntities().get(i);
initForeignKeys(mb, entity);
}
}
static void initForeignKeys(ModelBuilder mb, DbEntity<?> entity) {
var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (Collection.class.isAssignableFrom(f.getType())) {
Clazz clazzAnnotation = f.getAnnotation(Clazz.class);
if (clazzAnnotation == null) {
throw new ModelException("Collection " + entity.getType().getSimpleName() + "." + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
if (otherEntity == null) {
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.clazz());
otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity);
}
var primary = entity.getPrimaryKey();
if (primary == null) {
throw new ModelException("Entity " + entity.getType().getSimpleName() + " is missing a primary key and therefore cannot be referenced by " + otherEntity.getType().getSimpleName());
}
var otherEntityF = otherEntity;
var otherFields = primary.getFields().stream()
.map(e -> {
//find list<object> in other entity (N to N relation)
if (otherEntityF != entity) { //ignore on recursive models
var otherEntityListField = otherEntityF.getFields().stream()
.filter(oef -> Collection.class.isAssignableFrom(oef.getType())
&& oef.getField().getAnnotationsByType(Clazz.class).length > 0
&& oef.getField().getAnnotationsByType(Clazz.class)[0].clazz() == entity.getType()).findFirst();
if (otherEntityListField.isPresent()) {
throw new ModelException("N to N relations need to explicitly defined via a mapping model ("
+ otherEntityF.getType().getSimpleName() + "::" + otherEntityListField.get().getType().getName() + " and "
+ entity.getType().getSimpleName() + "::" + f.getType().getName() + ")");
}
}
//find object in other entity (1 to N relation)
var otherEntityObjectField = otherEntityF.getFields().stream().filter(oef -> oef.getType() == entity.getType()).findFirst();
if (otherEntityObjectField.isPresent()) {
return new FieldSearchResult(otherEntityObjectField.get(), false);
}
//find objectId in other entity (1 to N relation)
// var idFieldName = entity.getType().getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT)
// + entity.getType().getSimpleName().substring(1)
// + e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
// + e.getName().substring(1);
var idFieldName = f.getName()
+ e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
+ e.getName().substring(1);
var otherEntityIdField = otherEntityF.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
if (otherEntityIdField.isPresent()) {
return new FieldSearchResult(otherEntityIdField.get(), otherEntityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
}
var field = new DbField<>(otherEntityF, e.getType(), null, idFieldName);
field = otherEntityF.addIfAbsent(field);
return new FieldSearchResult(field, true);
})
.toList();
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
otherEntity.addForeignKey(new ForeignKeyConstraint(otherEntity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
entity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
}
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
if (otherEntity == null) {
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) f.getType());
otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity);
}
var primary = otherEntity.getPrimaryKey();
if (primary == null) {
throw new ModelException("Entity " + otherEntity.getType().getSimpleName() + " is missing a primary key and therefore cannot be referenced by " + entity.getType().getSimpleName());
}
var otherEntityF = otherEntity;
var otherFields = primary.getFields().stream()
.map(otherPrimaryField -> {
//find list<object> in other entity (N to 1 relation)
var entityListField = entity.getFields().stream()
.filter(oef -> Collection.class.isAssignableFrom(oef.getType())
&& oef.getField().getAnnotationsByType(Clazz.class).length > 0
&& oef.getField().getAnnotationsByType(Clazz.class)[0].clazz() == otherEntityF.getType()).toList();
// if (entityListField.size() == 1) {
// return new FieldSearchResult(entityListField.get(0), true);
// }
//find object in other entity (1 to 1 relation)
var entityObjectFields = entity.getFields().stream().filter(oef -> oef.getType() == otherEntityF.getType()).toList();
// if (entityObjectField.isPresent()) {
// return new FieldSearchResult(entityObjectField.get(), false);
// }
//find objectId in other entity (1 to 1 relation)
var idFieldName = f.getName()
+ otherPrimaryField.getName().substring(0, 1).toUpperCase(Locale.ROOT)
+ otherPrimaryField.getName().substring(1);
var entityIdField = entity.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
if (entityIdField.isPresent()) {
return new FieldSearchResult(entityIdField.get(), entityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
}
var field = new DbField<>(entity, otherPrimaryField.getType(), null, idFieldName);
field = entity.addIfAbsent(field);
if (entityListField.size() == 1) {
field.setForeignKeyModelLink(entityListField.get(0));
entityListField.get(0).setForeignKeyModelLink(field);
} else if (entityObjectFields.size() == 1) {
field.setForeignKeyModelLink(entityObjectFields.get(0));
entityObjectFields.get(0).setForeignKeyModelLink(field);
}
return new FieldSearchResult(field, true);
// return new DbField<>(entity, e.getType(), null, f.getName() + e.getName().substring(0, 1).toUpperCase(Locale.ROOT) + e.getName().substring(1))
})
.toList();
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
entity.addForeignKey(new ForeignKeyConstraint(entity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
otherEntity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
}
}
}
}
@AllArgsConstructor
@Getter
private static class FieldSearchResult {
private final DbField<?> field;
private final boolean createForeignKey;
}
}

View File

@@ -1,24 +1,16 @@
package jef.model;
import jef.DbSet;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.annotations.Transient;
import jef.model.annotations.processors.AnnotationProcessor;
import jef.model.annotations.processors.ForeignKeyProcessor;
import jef.model.annotations.processors.IndexProcessor;
import jef.model.annotations.processors.KeyProcessor;
import jef.model.annotations.processors.NotNullProcessor;
import jef.model.annotations.processors.UniqueProcessor;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint;
import jef.serializable.SerializableObject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class ModelBuilder {
private static final List<AnnotationProcessor> annotationProcessors = new ArrayList<>();
@@ -28,6 +20,7 @@ public class ModelBuilder {
annotationProcessors.add(UniqueProcessor.INSTANCE);
annotationProcessors.add(IndexProcessor.INSTANCE);
annotationProcessors.add(KeyProcessor.INSTANCE);
annotationProcessors.add(ForeignKeyProcessor.INSTANCE);
}
public static ModelBuilder from(Class<? extends DbContext> context) {
@@ -40,9 +33,9 @@ public class ModelBuilder {
private static ModelBuilder from0(Class<? extends DbContext> context) throws Exception {
var mb = new ModelBuilder(new ArrayList<>());
initEntities(mb, context);
addPrimaryKeys(mb);
addForeignKeys(mb);
EntityInitializer.initEntities(mb, context);
PrimaryKeyInitializer.initPrimaryKeys(mb);
ForeignKeyInitializer.initForeignKeys(mb);
for (AnnotationProcessor processor : annotationProcessors) {
processor.apply(mb);
@@ -50,128 +43,6 @@ public class ModelBuilder {
return mb;
}
private static void initEntities(ModelBuilder mb, Class<? extends DbContext> context) {
for (Field ctxfield : context.getDeclaredFields()) {
if (!DbSet.class.isAssignableFrom(ctxfield.getType())) {
continue;
}
Clazz clazzAnnotation = ctxfield.getAnnotation(Clazz.class);
if (clazzAnnotation == null) {
throw new ModelException("DbSet " + ctxfield.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var dbsetClazz = (Class<? extends SerializableObject>) clazzAnnotation.clazz();
initEntity(mb, dbsetClazz);
}
}
private static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz) {
var entity = mb.getOrCreateEntity(clazz);
var fields = ReflectionUtil.getFieldsRecursive(clazz);
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (Collection.class.isAssignableFrom(f.getType())) {
} else {
var dbField = entity.getOrCreateField(f);
if (f.getType().isPrimitive()) {
dbField.setNotNull(true);
}
}
}
}
private static void addPrimaryKeys(ModelBuilder mb) {
for (int i = 0; i < mb.getEntities().size(); i++) {
var entity = mb.getEntities().get(i);
addPrimaryKeys(mb, entity);
}
}
private static void addPrimaryKeys(ModelBuilder mb, DbEntity<?> entity) {
var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
var idFields = new ArrayList<Field>();
//search for fields wuth @Id annotation
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (!f.getType().isPrimitive()) {
continue;
}
if (f.getAnnotationsByType(Id.class).length == 0) {
continue;
}
//fields in same class check
if (idFields.size() == 0 || idFields.get(0).getDeclaringClass() == f.getDeclaringClass()) {
idFields.add(f);
} else {
break;
}
}
if (idFields.isEmpty()) {
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (!f.getType().isPrimitive()) {
continue;
}
if (f.getName().equals("id")) {
idFields.add(f);
break;
}
}
}
if (!idFields.isEmpty()) {
var dbfields = idFields.stream().map(entity::getField).toList();
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) dbfields));
}
}
private static void addForeignKeys(ModelBuilder mb) {
for (int i = 0; i < mb.getEntities().size(); i++) {
var entity = mb.getEntities().get(i);
addForeignKeys(mb, entity);
}
}
private static void addForeignKeys(ModelBuilder mb, DbEntity<?> entity) {
var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (Collection.class.isAssignableFrom(f.getType())) {
Clazz clazzAnnotation = f.getAnnotation(Clazz.class);
if (clazzAnnotation == null) {
throw new ModelException("Collection " + entity.getType().getSimpleName() + "." + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var refEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
if (refEntity == null) {
initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.clazz());
refEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
addPrimaryKeys(mb, refEntity);
}
var primary = refEntity.getPrimaryKey();
if (primary == null) {
throw new ModelException("Entity " + refEntity.getType().getSimpleName() + " is missing a primary key and therefore cannot be referenced by " + entity.getType().getSimpleName());
}
var refFields = primary.getFields().stream()
.map(e -> new DbField<>(entity, e.getType(), null, f.getName() + e.getName().substring(0, 1).toUpperCase(Locale.ROOT) + e.getName().substring(1)))
.map(entity::getOrAddField)
.toList();
entity.addForeignKey(new ForeignKeyConstraint(entity, (List) refFields, refEntity, primary.getFields()));
}
}
}
private final List<DbEntity<? extends SerializableObject>> entities;
public ModelBuilder(List<DbEntity<? extends SerializableObject>> entities) {

View File

@@ -0,0 +1,63 @@
package jef.model;
import jef.model.annotations.Id;
import jef.model.annotations.Transient;
import jef.model.constraints.PrimaryKeyConstraint;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
class PrimaryKeyInitializer {
static void initPrimaryKeys(ModelBuilder mb) {
for (int i = 0; i < mb.getEntities().size(); i++) {
var entity = mb.getEntities().get(i);
initPrimaryKeys(mb, entity);
}
}
static void initPrimaryKeys(ModelBuilder mb, DbEntity<?> entity) {
var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
var idFields = new ArrayList<Field>();
//search for fields with @Id annotation
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (!f.getType().isPrimitive()) {
continue;
}
if (f.getAnnotationsByType(Id.class).length == 0) {
continue;
}
//fields in same class check
if (idFields.size() == 0 || idFields.get(0).getDeclaringClass() == f.getDeclaringClass()) {
idFields.add(f);
} else {
break;
}
}
if (idFields.isEmpty()) {
for (var f : fields) {
if (f.getAnnotationsByType(Transient.class).length > 0) {
continue;
}
if (!f.getType().isPrimitive()) {
continue;
}
if (f.getName().equals("id")) {
idFields.add(f);
break;
}
}
}
if (!idFields.isEmpty()) {
var dbfields = idFields.stream().map(entity::getField).toList();
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) dbfields));
}
}
}

View File

@@ -0,0 +1,23 @@
package jef.model.annotations;
import jef.model.constraints.ForeignKeyConstraint;
import jef.serializable.SerializableObject;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ForeignKey {
String getterOrField();
Class<? extends SerializableObject> entity() default SerializableObject.class;
ForeignKeyConstraint.Action onUpdate() default ForeignKeyConstraint.Action.RESTRICT;
ForeignKeyConstraint.Action onDelete() default ForeignKeyConstraint.Action.CASCADE;
}

View File

@@ -0,0 +1,99 @@
package jef.model.annotations.processors;
import jef.model.DbEntity;
import jef.model.DbField;
import jef.model.ModelBuilder;
import jef.model.ModelException;
import jef.model.annotations.ForeignKey;
import jef.model.constraints.ForeignKeyConstraint;
import jef.serializable.SerializableObject;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ForeignKeyProcessor implements AnnotationProcessor {
public static final ForeignKeyProcessor INSTANCE = new ForeignKeyProcessor();
@Override
public void apply(ModelBuilder mb) {
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
//annotation on fields
for (DbField<?> field : entity.getFields()) {
if (field.isModelField() && field.getField().getAnnotationsByType(ForeignKey.class).length > 0) {
var anno = field.getField().getAnnotationsByType(ForeignKey.class)[0];
// var dbfield = entity.getField(field.getField());
// if (dbfield == null) {
// throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
// }
var exposeForeignKey = anno.entity() == SerializableObject.class;
var refEntity = exposeForeignKey ? entity : mb.getEntity(anno.entity());
if (!exposeForeignKey && refEntity == null) {
throw new ModelException("Could not find referenced entity " + anno.entity().getSimpleName() + " (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")");
}
Field refClassField;
try {
refClassField = getField(refEntity, anno.getterOrField());
} catch (ModelException e) {
throw new ModelException(e.getMessage() + " (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")");
}
var refField = refEntity.getField(refClassField);
if (exposeForeignKey) {
field.setForeignKeyModelLink(refField);
refField.setForeignKeyModelLink(field);
continue;
}
//check for primary key
if (refEntity.getPrimaryKey() == null) {
throw new ModelException("Entity " + refEntity.getType().getSimpleName() + " does not have a primary key and can "
+ "therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")");
}
//check if references field is the primary key of the refeneced entity
if (!refEntity.getPrimaryKey().getFields().equals(List.of(refField))) {
throw new ModelException(refEntity.getType().getSimpleName() + "::" + refField.getField().getName()
+ " is not equal to the primary key of entity " + refEntity.getType().getSimpleName()
+ " (" + refEntity.getPrimaryKey().getFields().stream().map(e -> e.getField().getName()).collect(Collectors.joining(", ")) + ")"
+ " and can therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")");
}
entity.addForeignKey(new ForeignKeyConstraint(entity, List.of(field), refEntity, List.of(refField), anno.onUpdate(), anno.onDelete()));
}
}
}
}
private Field getField(DbEntity<? extends SerializableObject> entity, String fieldOrGetter) {
Method getter = null;
try {
getter = entity.getType().getDeclaredMethod(fieldOrGetter);
} catch (NoSuchMethodException e) {
}
Field field = null;
try {
if (getter != null && getter.getName().length() > 3 && getter.getName().startsWith("get")) {
var name = getter.getName().substring(3, 4).toLowerCase(Locale.ROOT) + getter.getName().substring(4); //HACK
field = entity.getType().getDeclaredField(name);
} else {
field = entity.getType().getDeclaredField(fieldOrGetter);
}
} catch (NoSuchFieldException e) {
}
if (field == null) {
throw new ModelException("Cannot find getter/field " + entity.getType().getSimpleName() + "::" + fieldOrGetter);
}
return field;
}
}

View File

@@ -14,7 +14,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor {
abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor {
private final Class<T> annotationClass;
protected KeyProcessorBase(Class<T> annotationClass) {

View File

@@ -3,6 +3,7 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
@@ -10,14 +11,30 @@ import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class ForeignKeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
private final DbEntity<?> referencedEntity;
private final List<DbField<?>> referencedFields;
private final Action onUpdate;
private final Action onDelete;
@Override
public String getName() {
return "FK_" + entity.getName() + "_" + referencedEntity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
}
@Override
public String toString() {
return "CONSTRAINT " + getName() + " FOREIGN KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ") "
+ "REFERENCES " + referencedEntity.getName() + "(" + referencedFields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
public enum Action {
CASCADE,
RESTRICT,
SET_NULL,
NO_ACTION,
}
}

View File

@@ -3,6 +3,7 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
@@ -10,6 +11,7 @@ import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class IndexConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@@ -18,4 +20,9 @@ public class IndexConstraint implements Constraint {
public String getName() {
return "I_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
}
@Override
public String toString() {
return "CONSTRAINT " + getName() + " INDEX (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
}

View File

@@ -3,6 +3,7 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
@@ -10,6 +11,7 @@ import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class KeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@@ -18,4 +20,9 @@ public class KeyConstraint implements Constraint {
public String getName() {
return "K_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
}
@Override
public String toString() {
return "CONSTRAINT " + getName() + " KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
}

View File

@@ -3,12 +3,15 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class PrimaryKeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@@ -17,4 +20,9 @@ public class PrimaryKeyConstraint implements Constraint {
public String getName() {
return "PRIMARY";
}
@Override
public String toString() {
return getName() + " KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
}

View File

@@ -3,6 +3,7 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
@@ -10,6 +11,7 @@ import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class UniqueConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@@ -18,4 +20,9 @@ public class UniqueConstraint implements Constraint {
public String getName() {
return "U_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
}
@Override
public String toString() {
return "CONSTRAINT " + getName() + " UNIQUE KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
}