From 5fc45a23d302b789bf47a462798617fa12af9a23 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sun, 10 Dec 2023 23:58:38 +0100 Subject: [PATCH] [DEV] add a simple JPA checker to simplify client code --- .../archidata/annotation/AnnotationTools.java | 7 + .../dataAccess/options/CheckJPA.java | 158 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/org/kar/archidata/dataAccess/options/CheckJPA.java diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index de63433..a7c1eda 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -129,6 +129,13 @@ public class AnnotationTools { } public static boolean isPrimaryKey(final Field element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Id.class); + if (annotation.length == 0) { + return false; + } + return true; + } + public static boolean isUnique(final Field element) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); if (annotation.length == 0) { return false; diff --git a/src/org/kar/archidata/dataAccess/options/CheckJPA.java b/src/org/kar/archidata/dataAccess/options/CheckJPA.java new file mode 100644 index 0000000..98dac7b --- /dev/null +++ b/src/org/kar/archidata/dataAccess/options/CheckJPA.java @@ -0,0 +1,158 @@ +package org.kar.archidata.dataAccess.options; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kar.archidata.annotation.AnnotationTools; +import org.kar.archidata.dataAccess.DataAccess; +import org.kar.archidata.dataAccess.QueryCondition; +import org.kar.archidata.exception.DataAccessException; +import org.kar.archidata.exception.InputException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonValue; + +public class CheckJPA implements CheckFunctionInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(CheckJPA.class); + private final Class clazz; + + /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ + public interface CheckInterface { + /** This function implementation is design to check if the updated class is valid of not for insertion + * @param data The object that might be injected. + * @param filterValue List of fields that might be check. If null, then all column must be checked. + * @throws Exception Exception is generate if the data are incorrect. */ + void check(K data) throws Exception; + } + + private Map>> checking = null; + + protected void add(String field, CheckInterface checkFunction) { + List> actions = this.checking.get(field); + if (actions == null) { + actions = new ArrayList<>(); + this.checking.put(field, actions); + } + actions.add(checkFunction); + } + + public CheckJPA(Class clazz) { + this.clazz = clazz; + } + + public void initialize() throws Exception { + if (this.checking != null) { + return; + } + try { + this.checking = new HashMap<>(); + // create Table: + final List primaryKeys = new ArrayList<>(); + for (final Field field : this.clazz.getFields()) { + String fieldName = AnnotationTools.getFieldName(field); + if (AnnotationTools.isPrimaryKey(field)) { + add(fieldName, (T data) -> { + throw new InputException(fieldName, "This is a '@Id' (primaryKey) ==> can not be change"); + }); + } + if (AnnotationTools.getNotNull(field)) { + add(fieldName, (T data) -> { + if (field.get(data) == null) { + throw new InputException(fieldName, "Can not be null"); + } + }); + } + if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) { + add(fieldName, (T data) -> { + throw new InputException(fieldName, "It is forbidden to change this field"); + }); + } + + Class type = field.getType(); + if (type == Long.class || type == long.class) { + + } else if (type == Integer.class || type == int.class) { + + } else if (type == Boolean.class || type == boolean.class) { + + } else if (type == Float.class || type == float.class) { + + } else if (type == Double.class || type == double.class) { + + } else if (type == Date.class || type == Timestamp.class) { + + } else if (type == LocalDate.class) { + + } else if (type == LocalTime.class) { + + } else if (type == String.class) { + int maxSizeString = AnnotationTools.getLimitSize(field); + + if (maxSizeString > 0) { + add(fieldName, (T data) -> { + Object elem = field.get(data); + if (elem == null) { + return; + } + String elemTyped = (String) elem; + if (elemTyped.length() > maxSizeString) { + throw new InputException("name", "Too long size must be <= " + maxSizeString); + } + }); + } + } else if (type == JsonValue.class) { + + } else if (type.isEnum()) { + // nothing to do. + } + // keep this is last ==> take more time... + if (AnnotationTools.isUnique(field)) { + // Create the request ... + add(fieldName, (T data) -> { + final Object other = DataAccess.getWhere(this.clazz, new QueryCondition(fieldName, "==", field.get(data))); + if (other != null) { + throw new InputException(fieldName, "Name already exist in the DB"); + } + }); + } + + } + } catch (Exception ex) { + this.checking = null; + throw ex; + } + } + + @Override + public void check(Object data, List filterValue) throws Exception { + initialize(); + if (!(this.clazz.isAssignableFrom(data.getClass()))) { + throw new DataAccessException("Incompatatyble type of Object" + data.getClass().getCanonicalName()); + } + @SuppressWarnings("unchecked") + T dataCasted = (T) data; + for (String filter : filterValue) { + List> actions = this.checking.get(filter); + if (actions == null) { + continue; + } + for (CheckInterface action : actions) { + action.check(dataCasted); + } + } + checkTyped(dataCasted, filterValue); + } + + public void checkTyped(T data, List filterValue) throws Exception { + // nothing to do ... + } +}