From 812ce76290c8f2be8450cb5eb6726b9bc0096086 Mon Sep 17 00:00:00 2001 From: Lukas Karsch <lk224@hdm-stuttgart.de> Date: Mon, 25 Dec 2023 17:31:45 +0100 Subject: [PATCH] #42 entity mapper --- .../hdm/mi/growbros/util/EntityMapper.java | 96 +++++++++++++++++++ .../mi/growbros/util/EntityMapperTest.java | 67 +++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/main/java/hdm/mi/growbros/util/EntityMapper.java create mode 100644 src/test/java/hdm/mi/growbros/util/EntityMapperTest.java diff --git a/src/main/java/hdm/mi/growbros/util/EntityMapper.java b/src/main/java/hdm/mi/growbros/util/EntityMapper.java new file mode 100644 index 0000000..333a09e --- /dev/null +++ b/src/main/java/hdm/mi/growbros/util/EntityMapper.java @@ -0,0 +1,96 @@ +package hdm.mi.growbros.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +@Component +public class EntityMapper { + private final static Logger log = LoggerFactory.getLogger(EntityMapper.class); + + /** + * Updates fields of an existing entity based on non-null values from an update request. + * This method is designed for use in PATCH operations within a Spring Boot application. + * + * <p> + * The {@code map} method facilitates the partial update of an existing entity by mapping non-null + * fields from an update request object to the corresponding fields in the existing entity. + * Fields in the update request with null values are excluded from the update. + * </p> + * + * <p> + * Usage example: + * <pre>{@code + * // Create an instance of the class containing the map method + * EntityMapper entityMapper = new EntityMapper(); + * + * // Create an instance of the existing entity and update request + * ExistingEntity existingEntity = // ... (initialize existing entity) + * UpdateRequest updateRequest = // ... (initialize update request) + * + * // Perform the partial update + * entityMapper.map(updateRequest, existingEntity); + * }</pre> + * </p> + * + * <p> + * Note: This class assumes proper usage in a Spring Boot application, particularly for PATCH + * operations, where only specified fields need to be updated without affecting the entire entity. + * </p> + * + * @param updateRequest The object containing fields with non-null values to be used for updating. + * @param existingEntity The entity object to be updated with non-null values from the update request. + */ + public void map(Object updateRequest, Object existingEntity) { + if (updateRequest == null) { + log.warn("Update request was null, no mapping will take place"); + return; + } + if (existingEntity == null) { + log.warn("Existing entity was null, no mapping will take place"); + return; + } + + final Field[] allUpdaterFields = updateRequest.getClass().getDeclaredFields(); + final List<Field> toUpdate = new ArrayList<>(); + + for (Field updaterField : allUpdaterFields) { + updaterField.setAccessible(true); + try { + if (updaterField.get(updateRequest) != null && !"this$0".equals(updaterField.getName())) { + toUpdate.add(updaterField); + } + } catch (IllegalAccessException e) { + log.error("Illegal access exception while mapping object", e); + } + } + + updateFields(toUpdate, updateRequest, existingEntity); + } + + private Field getFieldForName(String name, Object o) { + var fields = o.getClass().getDeclaredFields(); + for (Field f : fields) { + if (name.equals(f.getName())) return f; + } + throw new IllegalArgumentException("No field with declared name '" + name + "' is present on this object"); + } + + private void updateFields(List<Field> toUpdate, Object updateRequest, Object existingEntity) { + for (Field updateField : toUpdate) { + try { + updateField.setAccessible(true); + Field entityField = getFieldForName(updateField.getName(), existingEntity); + entityField.setAccessible(true); + final Object newValue = updateField.get(updateRequest); + entityField.set(existingEntity, newValue); + } catch (IllegalAccessException e) { + log.error("Illegal access exception while mapping object", e); + } + } + } +} diff --git a/src/test/java/hdm/mi/growbros/util/EntityMapperTest.java b/src/test/java/hdm/mi/growbros/util/EntityMapperTest.java new file mode 100644 index 0000000..6a34477 --- /dev/null +++ b/src/test/java/hdm/mi/growbros/util/EntityMapperTest.java @@ -0,0 +1,67 @@ +package hdm.mi.growbros.util; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EntityMapperTest { + private EntityMapper entityMapper; + + @BeforeEach + void setup() { + entityMapper = new EntityMapper(); + } + + @Test + void shouldIgnore_whenEitherArgument_isNull() { + //arrange + final Entity entity = getBaseEntity(); + final UpdaterObject updater = new UpdaterObject(); + + //act and assert + assertAll( + () -> assertDoesNotThrow(() -> entityMapper.map(null, entity)), + () -> assertDoesNotThrow(() -> entityMapper.map(updater, null)) + ); + } + + @Test + void shouldUpdate_toNewValues() { + //arrange + final Entity entity = getBaseEntity(); + + final UpdaterObject updater = new UpdaterObject(); + final String expected = "UPDATED value 1"; + updater.value1 = expected; + + //act + entityMapper.map(updater, entity); + + //assert + assertAll( + () -> assertEquals(1, entity.id), + () -> assertEquals(expected, entity.value1), + () -> assertEquals("Value 2", entity.value2) + ); + } + + private class UpdaterObject { + private String value1; + private String value2; + } + + private class Entity { + private int id; + private String value1; + private String value2; + } + + private Entity getBaseEntity() { + final Entity entity = new Entity(); + entity.id = 1; + entity.value1 = "Value 1"; + entity.value2 = "Value 2"; + return entity; + } +} -- GitLab