From de17b8985a629b9c3ee1c6f8fdd71a723c5bb957 Mon Sep 17 00:00:00 2001 From: wea_ondara Date: Sat, 10 Sep 2022 11:19:06 +0200 Subject: [PATCH] fix issues with foreign key exposure and add some tests for it --- .../model/ForeignKeyExposeInitializer.java | 55 +++++--------- .../java/jef/model/ForeignKeyInitializer.java | 12 ++- .../ForeignKeyProcessorExposeTest.java | 20 ++--- ...oreignKeyProcessorExposeViaMethodTest.java | 74 +++++++++++++++++++ ...reignKeyProcessorInvalidFieldTypeTest.java | 49 ++++++++++++ ...rocessorNonExistingFieldOrGetterTest.java} | 2 +- 6 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeViaMethodTest.java create mode 100644 src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldTypeTest.java rename src/test/java/jef/model/annotations/processors/{ForeignKeyProcessorInvalidFieldOrGetterTest.java => ForeignKeyProcessorNonExistingFieldOrGetterTest.java} (95%) diff --git a/src/main/java/jef/model/ForeignKeyExposeInitializer.java b/src/main/java/jef/model/ForeignKeyExposeInitializer.java index 4bf817b..0dd4055 100644 --- a/src/main/java/jef/model/ForeignKeyExposeInitializer.java +++ b/src/main/java/jef/model/ForeignKeyExposeInitializer.java @@ -5,10 +5,7 @@ import jef.model.annotations.ForeignKey; import jef.serializable.SerializableObject; import jef.util.Util; -import java.util.Locale; - -class ForeignKeyExposeInitializer {//TODO testcases for this class - +class ForeignKeyExposeInitializer { static void initForeignKeyExposures(ModelBuilder mb) { var entities = mb.entities(); for (int i = 0; i < entities.size(); i++) { @@ -29,43 +26,29 @@ class ForeignKeyExposeInitializer {//TODO testcases for this class } //match with name in annotation var anno = e.getField().getField().getAnnotation(ForeignKey.class); - if (anno != null) { - if (anno.entity() != SerializableObject.class) { // not a foreign key exposure - return false; - } - var fieldname = anno.getterOrField(); //TODO test + if (anno == null) { + return false; + } + if (anno.entity() != SerializableObject.class) { // not a foreign key exposure, ignore + return false; + } + var fieldname = anno.getterOrField(); - //if getter, extract getter field - var method = Util.tryGet(() -> entityBuilder.type().orElseThrow().getMethod(anno.getterOrField())); //TODO test - if (method.isPresent()) { - var res = new OptimizedAsmParser(method.get()).parse(); - fieldname = res.getAccessedFields().stream().findFirst().orElseThrow().getName(); - } - return f.getField().getName().equals(fieldname); + //if getter, extract getter field + var method = Util.tryGet(() -> entityBuilder.type().orElseThrow().getMethod(anno.getterOrField())); + if (method.isPresent()) { + var res = new OptimizedAsmParser(method.get()).parse(); + fieldname = res.getAccessedFields().stream().findFirst().orElseThrow().getName(); } - //only allow fields with same prefix - if (!e.getField().getName().startsWith(f.getField().getName())) { - return false; + if (!SerializableObject.class.isAssignableFrom(entityBuilder.getEntity().getField(fieldname).getType())) { + throw new ModelException("The getter/field in " + entityBuilder.className() + "::" + f.getField().getField().getName() + " is not a " + SerializableObject.class.getSimpleName() + " (via @ForeignKey in TestClass::testFk)"); } - //match id field which ends in Id or Fk - if (e.getField().getName().equals(f.getField().getName() + "Id") //TODO test - || e.getField().getName().equals(f.getField().getName() + "ID") //TODO test - || e.getField().getName().equals(f.getField().getName() + "Fk") //TODO test - || e.getField().getName().equals(f.getField().getName() + "FK")) { //TODO test - return true; - } - //match id field which ends in the name of the primary key field of the referenced entity - var other = mb.getEntity(f.getField().getTypeName()); //TODO test - if (other == null || other.getPrimaryKey() == null) { - return false; - } - //assumption: initializing from models only has primary keys with 1 field (no composite keys) - var x = other.getPrimaryKey().getFields().get(0).getName(); //TODO test - x = x.substring(0, 1).toUpperCase(Locale.ROOT) + x.substring(1); - return e.getField().getName().equals(f.getField().getName() + x); - }).toList(); + return f.getField().getName().equals(fieldname); + }) + .toList(); idFields.forEach(e -> { e.getField().setExposingForeignKeyOf(f.getField()); + e.getField().setDatabaseField(idFields.stream().findFirst().orElseThrow() == e); }); } } diff --git a/src/main/java/jef/model/ForeignKeyInitializer.java b/src/main/java/jef/model/ForeignKeyInitializer.java index 67b9dda..5c4cc81 100644 --- a/src/main/java/jef/model/ForeignKeyInitializer.java +++ b/src/main/java/jef/model/ForeignKeyInitializer.java @@ -147,12 +147,20 @@ class ForeignKeyInitializer { // } //find objectId in other entity (1 to 1 relation) + //search for exposing foreign key field first + var entityIdField = entity.getFields().stream() + .filter(oef -> oef.getExposingForeignKeyOf() != null && oef.getExposingForeignKeyOf().getField().equals(f)) + .findFirst(); + + //search for field with name object field name + primary field name 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()) { + 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); + return new FieldSearchResult(entityIdField.get(), true);//entityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0); } var field = new DbField<>(entity, otherPrimaryField.getType(), null, idFieldName); diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java index 0a9e305..6356a58 100644 --- a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java @@ -28,15 +28,10 @@ class ForeignKeyProcessorExposeTest { //fields ------------------------ //TestClass - assertEquals(8, mb.getEntity(TestClass.class).getFields().size()); + assertEquals(3, mb.getEntity(TestClass.class).getFields().size()); assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d") && e.isModelField() && e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("f") && e.isModelField() && e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("l") && e.isModelField() && e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("o") && e.isModelField() && e.isDatabaseField()).count()); assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("test") && e.isModelField() && !e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testFk") && e.isModelField() && e.isDatabaseField()).count()); - assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testI") && !e.isModelField() && e.isDatabaseField()).count()); + assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField") && e.isModelField() && e.isDatabaseField()).count()); //TestClass2 assertEquals(1, mb.getEntity(TestClass2.class).getFields().size()); @@ -47,10 +42,13 @@ class ForeignKeyProcessorExposeTest { assertEquals(1, mb.getEntity(TestClass.class).getForeignKeys().size()); assertEquals(mb.getEntity(TestClass.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getEntity()); assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedEntity()); - assertEquals(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testI")).toList(), + assertEquals(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField")).toList(), mb.getEntity(TestClass.class).getForeignKeys().get(0).getFields()); assertEquals(mb.getEntity(TestClass2.class).getPrimaryKey().getFields(), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedFields()); // /keys ------------------------ + + //exposing fields + assertEquals(mb.getEntity(TestClass.class).getField("test"), mb.getEntity(TestClass.class).getField("testField").getExposingForeignKeyOf()); } public static class Ctx extends DbContext { @@ -63,13 +61,9 @@ class ForeignKeyProcessorExposeTest { @Getter public static class TestClass extends SerializableObject { public int i = 1; - public Object o = new Object(); - public double d; - public float f; - public long l; private TestClass2 test; @ForeignKey(getterOrField = "test") - public int testFk; + public int testField; } @Getter diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeViaMethodTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeViaMethodTest.java new file mode 100644 index 0000000..8cc3931 --- /dev/null +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeViaMethodTest.java @@ -0,0 +1,74 @@ +package jef.model.annotations.processors; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.ForeignKey; +import jef.model.annotations.Id; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ForeignKeyProcessorExposeViaMethodTest { + @Test + public void test() { + var mb = ModelBuilder.from(Ctx.class); + + assertEquals(2, mb.getEntities().size()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass2.class).getName()); + assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); + assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); + assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); + assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); + + //fields ------------------------ + //TestClass + assertEquals(3, mb.getEntity(TestClass.class).getFields().size()); + assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count()); + assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("test") && e.isModelField() && !e.isDatabaseField()).count()); + assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField") && e.isModelField() && e.isDatabaseField()).count()); + + //TestClass2 + assertEquals(1, mb.getEntity(TestClass2.class).getFields().size()); + assertEquals(1, mb.getEntity(TestClass2.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count()); + // /fields ------------------------ + + //keys ------------------------ + assertEquals(1, mb.getEntity(TestClass.class).getForeignKeys().size()); + assertEquals(mb.getEntity(TestClass.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getEntity()); + assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedEntity()); + assertEquals(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField")).toList(), + mb.getEntity(TestClass.class).getForeignKeys().get(0).getFields()); + assertEquals(mb.getEntity(TestClass2.class).getPrimaryKey().getFields(), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedFields()); + // /keys ------------------------ + + //exposing fields + assertEquals(mb.getEntity(TestClass.class).getField("test"), mb.getEntity(TestClass.class).getField("testField").getExposingForeignKeyOf()); + } + + public static class Ctx extends DbContext { + @Clazz(TestClass.class) + private DbSet objects1; + @Clazz(TestClass2.class) + private DbSet objects2; + } + + @Getter + public static class TestClass extends SerializableObject { + public int i = 1; + private TestClass2 test; + @ForeignKey(getterOrField = "getTest") + public int testField; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldTypeTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldTypeTest.java new file mode 100644 index 0000000..7dca6f8 --- /dev/null +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldTypeTest.java @@ -0,0 +1,49 @@ +package jef.model.annotations.processors; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.ModelException; +import jef.model.annotations.Clazz; +import jef.model.annotations.ForeignKey; +import jef.model.annotations.Id; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ForeignKeyProcessorInvalidFieldTypeTest { + @Test + public void test() { + assertEquals("The getter/field in TestClass::test is not a SerializableObject (via @ForeignKey in TestClass::testFk)", + assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage()); + } + + public static class Ctx extends DbContext { + @Clazz(TestClass.class) + private DbSet objects1; + @Clazz(TestClass2.class) + private DbSet objects2; + } + + @Getter + public static class TestClass extends SerializableObject { + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + private TestClass2 test; + @ForeignKey(getterOrField = "l") + public int testFk; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldOrGetterTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorNonExistingFieldOrGetterTest.java similarity index 95% rename from src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldOrGetterTest.java rename to src/test/java/jef/model/annotations/processors/ForeignKeyProcessorNonExistingFieldOrGetterTest.java index 2ac547b..74432d9 100644 --- a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorInvalidFieldOrGetterTest.java +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorNonExistingFieldOrGetterTest.java @@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; -class ForeignKeyProcessorInvalidFieldOrGetterTest { +class ForeignKeyProcessorNonExistingFieldOrGetterTest { @Test public void test() { assertEquals("Cannot find getter/field TestClass::x (via @ForeignKey in TestClass::ref)",