From 1c82fb1a86c81efee01ffd61a683d093f3f7008f Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Fri, 22 Dec 2023 23:33:09 +0100 Subject: [PATCH] [DEV] upgrade library start to be really cool --- pom.xml | 2 +- .../archidata/annotation/AnnotationTools.java | 49 ++++- .../kar/archidata/annotation/DataJson.java | 7 +- src/org/kar/archidata/api/DataResource.java | 3 +- .../kar/archidata/dataAccess/DataAccess.java | 143 ++++++++++---- .../archidata/dataAccess/QueryOptions.java | 7 +- .../dataAccess/addOn/AddOnManyToMany.java | 7 +- .../options/CheckFunctionInterface.java | 9 +- .../dataAccess/options/CheckFunctionVoid.java | 12 ++ .../dataAccess/options/CheckJPA.java | 174 +++++++++++++++--- .../dataAccess/options/Condition.java | 4 + src/org/kar/archidata/filter/CORSFilter.java | 2 +- .../archidata/migration/MigrationEngine.java | 2 +- src/org/kar/archidata/model/GenericData.java | 3 + .../model/GenericDataSoftDelete.java | 2 + src/org/kar/archidata/tools/DataTools.java | 10 +- src/org/kar/archidata/tools/RESTApi.java | 37 +++- .../test/kar/archidata/TestSimpleTable.java | 8 +- .../archidata/TestSimpleTableSoftDelete.java | 10 +- 19 files changed, 394 insertions(+), 97 deletions(-) create mode 100644 src/org/kar/archidata/dataAccess/options/CheckFunctionVoid.java diff --git a/pom.xml b/pom.xml index f6408b0..6f8fefb 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 3.1.1 - 3.1.1 + 3.1.5 2.3.1 4.1.1 diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index 6aa300f..38c2fb6 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -14,7 +14,10 @@ import jakarta.persistence.Column; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; @@ -70,6 +73,50 @@ public class AnnotationTools { return ((DataDefault) annotation[0]).value(); } + public static ManyToOne getManyToOne(final Field element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class); + if (annotation.length == 0) { + return null; + } + if (annotation.length > 1) { + throw new Exception("Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName()); + } + return (ManyToOne) annotation[0]; + } + + public static DataJson getDataJson(final Field element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class); + if (annotation.length == 0) { + return null; + } + if (annotation.length > 1) { + throw new Exception("Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName()); + } + return (DataJson) annotation[0]; + } + + public static Long getConstraintsMax(final Field element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class); + if (annotation.length == 0) { + return null; + } + if (annotation.length > 1) { + throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName()); + } + return ((Max) annotation[0]).value(); + } + + public static Long getConstraintsMin(final Field element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class); + if (annotation.length == 0) { + return null; + } + if (annotation.length > 1) { + throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName()); + } + return ((Min) annotation[0]).value(); + } + public static Integer getLimitSize(final Field element) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); if (annotation.length == 0) { @@ -289,7 +336,7 @@ public class AnnotationTools { } public static boolean isGenericField(final Field elem) throws Exception { - return AnnotationTools.isPrimaryKey(elem) || AnnotationTools.isCreatedAtField(elem) || AnnotationTools.isUpdateAtField(elem); + return AnnotationTools.isPrimaryKey(elem) || AnnotationTools.isCreatedAtField(elem) || AnnotationTools.isUpdateAtField(elem) || AnnotationTools.isDeletedField(elem); } public static Field getFieldOfId(final Class clazz) throws Exception { diff --git a/src/org/kar/archidata/annotation/DataJson.java b/src/org/kar/archidata/annotation/DataJson.java index 8a48006..2f36ad3 100644 --- a/src/org/kar/archidata/annotation/DataJson.java +++ b/src/org/kar/archidata/annotation/DataJson.java @@ -5,6 +5,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.kar.archidata.dataAccess.options.CheckFunctionInterface; +import org.kar.archidata.dataAccess.options.CheckFunctionVoid; + @Target({ ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) -public @interface DataJson {} +public @interface DataJson { + Class checker() default CheckFunctionVoid.class; +} diff --git a/src/org/kar/archidata/api/DataResource.java b/src/org/kar/archidata/api/DataResource.java index 6f7bd05..cfce4e5 100644 --- a/src/org/kar/archidata/api/DataResource.java +++ b/src/org/kar/archidata/api/DataResource.java @@ -24,6 +24,7 @@ import org.glassfish.jersey.media.multipart.FormDataParam; import org.kar.archidata.annotation.security.PermitTokenInURI; import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.QueryCondition; +import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.filter.GenericContext; import org.kar.archidata.model.Data; import org.kar.archidata.tools.ConfigBaseVariable; @@ -94,7 +95,7 @@ public class DataResource { public static Data getWithSha512(final String sha512) { LOGGER.info("find sha512 = {}", sha512); try { - return DataAccess.getWhere(Data.class, new QueryCondition("sha512", "=", sha512)); + return DataAccess.getWhere(Data.class, new Condition(new QueryCondition("sha512", "=", sha512))); } catch (final Exception e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/org/kar/archidata/dataAccess/DataAccess.java b/src/org/kar/archidata/dataAccess/DataAccess.java index 57c1130..956bff2 100644 --- a/src/org/kar/archidata/dataAccess/DataAccess.java +++ b/src/org/kar/archidata/dataAccess/DataAccess.java @@ -460,7 +460,7 @@ public class DataAccess { return null; } - public static List insertMultiple(final List data, final QueryOptions options) throws Exception { + public static List insertMultiple(final List data, final QueryOption... options) throws Exception { final List out = new ArrayList<>(); for (final T elem : data) { final T tmp = insert(elem, options); @@ -469,19 +469,14 @@ public class DataAccess { return out; } - public static T insert(final T data) throws Exception { - return insert(data, null); - } - - public static T insert(final T data, final QueryOptions options) throws Exception { + public static T insert(final T data, final QueryOption... option) throws Exception { final Class clazz = data.getClass(); + QueryOptions options = new QueryOptions(option); // External checker of data: - if (options != null) { - final CheckFunction check = options.get(CheckFunction.class); - if (check != null) { - check.getChecker().check(data, AnnotationTools.getFieldsNames(clazz)); - } + final CheckFunction check = options.get(CheckFunction.class); + if (check != null) { + check.getChecker().check(data, AnnotationTools.getFieldsNames(clazz)); } DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig); @@ -643,7 +638,7 @@ public class DataAccess { } // check the compatibility of the id and the declared ID final Class typeClass = idField.getType(); - if (idKey == typeClass) { + if (idKey.getClass() == typeClass) { throw new DataAccessException("Request update with the wrong type ..."); } return new QueryCondition(AnnotationTools.getFieldName(idField), "=", idKey); @@ -658,11 +653,22 @@ public class DataAccess { * @param jsonData Json data (partial) values to update * @return the number of object updated * @throws Exception */ - public static int updateWithJson(final Class clazz, final ID_TYPE id, final String jsonData, final QueryOptions options) throws Exception { - return updateWhereWithJson(clazz, getTableIdCondition(clazz, id), jsonData, options); + public static int updateWithJson(final Class clazz, final ID_TYPE id, final String jsonData, final QueryOption... option) throws Exception { + QueryOptions options = new QueryOptions(option); + Condition condition = options.get(Condition.class); + if (condition != null) { + throw new DataAccessException("request a updateWithJson with a condition"); + } + options.add(new Condition(getTableIdCondition(clazz, id))); + return updateWhereWithJson(clazz, jsonData, options.getAllArray()); } - public static int updateWhereWithJson(final Class clazz, final QueryItem condition, final String jsonData, final QueryOptions options) throws Exception { + public static int updateWhereWithJson(final Class clazz, final String jsonData, final QueryOption... option) throws Exception { + QueryOptions options = new QueryOptions(option); + Condition condition = options.get(Condition.class); + if (condition == null) { + throw new DataAccessException("request a updateWhereWithJson without any condition"); + } final ObjectMapper mapper = new ObjectMapper(); // parse the object to be sure the data are valid: final T data = mapper.readValue(jsonData, clazz); @@ -671,16 +677,12 @@ public class DataAccess { final List keys = new ArrayList<>(); final var iterator = root.fieldNames(); iterator.forEachRemaining(e -> keys.add(e)); - // TODO: set the filter in the Options... - return updateWhere(data, condition, options, keys); + options.add(new FilterValue(keys)); + return updateWhere(data, options.getAllArray()); } public static int update(final T data, final ID_TYPE id) throws Exception { - return update(data, id, null); - } - - public static int updateWhere(final T data, final QueryOptions options) throws Exception { - return updateWhere(data, options, null); + return update(data, id, AnnotationTools.getFieldsNames(data.getClass())); } /** @param @@ -689,8 +691,9 @@ public class DataAccess { * @param filterValue * @return the affected rows. * @throws Exception */ - public static int update(final T data, final ID_TYPE id, final List filterValue) throws Exception { - return updateWhere(data, new Condition(getTableIdCondition(data.getClass(), id)), filterValue); + public static int update(final T data, final ID_TYPE id, final List updateColomn) throws Exception { + QueryOptions options = new QueryOptions(new Condition(getTableIdCondition(data.getClass(), id)), new FilterValue(updateColomn)); + return updateWhere(data, options.getAllArray()); } // il y avait: final List filterValue @@ -712,7 +715,7 @@ public class DataAccess { if (options != null) { final CheckFunction check = options.get(CheckFunction.class); if (check != null) { - check.getChecker().check(data, filterValue); + check.getChecker().check(data, filter.getValues()); } } @@ -733,10 +736,8 @@ public class DataAccess { continue; } final String name = AnnotationTools.getFieldName(field); - if (filterValue != null) { - if (!filterValue.contains(name)) { - continue; - } + if (!filter.getValues().contains(name)) { + continue; } else if (AnnotationTools.isGenericField(field)) { continue; } @@ -778,10 +779,8 @@ public class DataAccess { continue; } final String name = AnnotationTools.getFieldName(field); - if (filterValue != null) { - if (!filterValue.contains(name)) { - continue; - } + if (!filter.getValues().contains(name)) { + continue; } else if (AnnotationTools.isGenericField(field)) { continue; } @@ -815,30 +814,43 @@ public class DataAccess { static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii) throws Exception { if (value instanceof final Long tmp) { + LOGGER.debug("Inject Long => {}", tmp); ps.setLong(iii.value, tmp); } else if (value instanceof final Integer tmp) { + LOGGER.debug("Inject Integer => {}", tmp); ps.setInt(iii.value, tmp); } else if (value instanceof final String tmp) { + LOGGER.debug("Inject String => {}", tmp); ps.setString(iii.value, tmp); } else if (value instanceof final Short tmp) { + LOGGER.debug("Inject Short => {}", tmp); ps.setShort(iii.value, tmp); } else if (value instanceof final Byte tmp) { + LOGGER.debug("Inject Byte => {}", tmp); ps.setByte(iii.value, tmp); } else if (value instanceof final Float tmp) { + LOGGER.debug("Inject Float => {}", tmp); ps.setFloat(iii.value, tmp); } else if (value instanceof final Double tmp) { + LOGGER.debug("Inject Double => {}", tmp); ps.setDouble(iii.value, tmp); } else if (value instanceof final Boolean tmp) { + LOGGER.debug("Inject Boolean => {}", tmp); ps.setBoolean(iii.value, tmp); } else if (value instanceof final Timestamp tmp) { + LOGGER.debug("Inject Timestamp => {}", tmp); ps.setTimestamp(iii.value, tmp); } else if (value instanceof final Date tmp) { + LOGGER.debug("Inject Date => {}", tmp); ps.setTimestamp(iii.value, java.sql.Timestamp.from((tmp).toInstant())); } else if (value instanceof final LocalDate tmp) { + LOGGER.debug("Inject LocalDate => {}", tmp); ps.setDate(iii.value, java.sql.Date.valueOf(tmp)); } else if (value instanceof final LocalTime tmp) { + LOGGER.debug("Inject LocalTime => {}", tmp); ps.setTime(iii.value, java.sql.Time.valueOf(tmp)); } else if (value.getClass().isEnum()) { + LOGGER.debug("Inject ENUM => {}", value.toString()); ps.setString(iii.value, value.toString()); } else { throw new DataAccessException("Not manage type ==> need to add it ..."); @@ -921,9 +933,6 @@ public class DataAccess { @SuppressWarnings("unchecked") public static List getsWhere(final Class clazz, final QueryOptions options) throws Exception { Condition condition = options.get(Condition.class); - if (condition == null) { - throw new DataAccessException("request a gets without any condition"); - } final List lazyCall = new ArrayList<>(); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig); @@ -942,7 +951,9 @@ public class DataAccess { generateSelectField(querySelect, query, clazz, options, count); querySelect.append(query.toString()); query = querySelect; - condition.whereAppendQuery(query, tableName, options, deletedFieldName); + if (condition != null) { + condition.whereAppendQuery(query, tableName, options, deletedFieldName); + } final OrderBy orders = options.get(OrderBy.class); if (orders != null) { orders.generateQuerry(query, tableName); @@ -955,7 +966,12 @@ public class DataAccess { // prepare the request: final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); final CountInOut iii = new CountInOut(1); - condition.injectQuerry(ps, iii); + if (condition != null) { + condition.injectQuerry(ps, iii); + } + if (limit != null) { + limit.injectQuerry(ps, iii); + } // execute the request final ResultSet rs = ps.executeQuery(); while (rs.next()) { @@ -1008,8 +1024,55 @@ public class DataAccess { return data; } - public static T get(final Class clazz, final ID_TYPE id) throws Exception { - return get(clazz, id, null); + public static long count(final Class clazz, final ID_TYPE id) throws Exception { + return DataAccess.countWhere(clazz, new Condition(getTableIdCondition(clazz, id))); + } + + public static long countWhere(final Class clazz, final QueryOption... option) throws Exception { + QueryOptions options = new QueryOptions(option); + Condition condition = options.get(Condition.class); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig); + long count = 0; + // real add in the BDD: + try { + StringBuilder query = new StringBuilder(); + final String tableName = AnnotationTools.getTableName(clazz, options); + query.append("SELECT COUNT(*) FROM `"); + query.append(tableName); + query.append("` "); + if (condition != null) { + condition.whereAppendQuery(query, tableName, options, deletedFieldName); + } + final Limit limit = options.get(Limit.class); + if (limit != null) { + limit.generateQuerry(query, tableName); + } + LOGGER.warn("generate the query: '{}'", query.toString()); + // prepare the request: + final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); + final CountInOut iii = new CountInOut(1); + if (condition != null) { + condition.injectQuerry(ps, iii); + } + if (limit != null) { + limit.injectQuerry(ps, iii); + } + // execute the request + final ResultSet rs = ps.executeQuery(); + if (rs.next()) { + count = rs.getLong("count"); + } + } catch (final SQLException ex) { + ex.printStackTrace(); + throw ex; + } catch (final Exception ex) { + ex.printStackTrace(); + } finally { + entry.close(); + entry = null; + } + return count; } public static T get(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { diff --git a/src/org/kar/archidata/dataAccess/QueryOptions.java b/src/org/kar/archidata/dataAccess/QueryOptions.java index d552e58..3cb5217 100644 --- a/src/org/kar/archidata/dataAccess/QueryOptions.java +++ b/src/org/kar/archidata/dataAccess/QueryOptions.java @@ -18,12 +18,15 @@ public class QueryOptions { private final List options = new ArrayList<>(); + public QueryOptions() {} + public QueryOptions(final QueryOption... elems) { + if (elems == null || elems.length == 0) { + return; + } Collections.addAll(this.options, elems); } - public QueryOptions() {} - public void add(final QueryOption option) { this.options.add(option); } diff --git a/src/org/kar/archidata/dataAccess/addOn/AddOnManyToMany.java b/src/org/kar/archidata/dataAccess/addOn/AddOnManyToMany.java index e0718ff..d4b6cd4 100644 --- a/src/org/kar/archidata/dataAccess/addOn/AddOnManyToMany.java +++ b/src/org/kar/archidata/dataAccess/addOn/AddOnManyToMany.java @@ -186,8 +186,7 @@ public class AddOnManyToMany implements DataAccessAddOn { final String tableName = AnnotationTools.getTableName(clazz); final String linkTableName = generateLinkTableName(tableName, column); final LinkTable insertElement = new LinkTable(localKey, remoteKey); - final QueryOptions options = new QueryOptions(new OverrideTableName(linkTableName)); - DataAccess.insert(insertElement, options); + DataAccess.insert(insertElement, new OverrideTableName(linkTableName)); } @@ -195,8 +194,8 @@ public class AddOnManyToMany implements DataAccessAddOn { final String tableName = AnnotationTools.getTableName(clazz); final String linkTableName = generateLinkTableName(tableName, column); final QueryOptions options = new QueryOptions(new OverrideTableName(linkTableName)); - final QueryAnd condition = new QueryAnd(new QueryCondition("object1Id", "=", localKey), new QueryCondition("object2Id", "=", remoteKey)); - return DataAccess.deleteWhere(LinkTable.class, condition, options); + options.add(new Condition(new QueryAnd(new QueryCondition("object1Id", "=", localKey), new QueryCondition("object2Id", "=", remoteKey)))); + return DataAccess.deleteWhere(LinkTable.class, options.getAllArray()); } @Override diff --git a/src/org/kar/archidata/dataAccess/options/CheckFunctionInterface.java b/src/org/kar/archidata/dataAccess/options/CheckFunctionInterface.java index 2b15c20..87b28f9 100644 --- a/src/org/kar/archidata/dataAccess/options/CheckFunctionInterface.java +++ b/src/org/kar/archidata/dataAccess/options/CheckFunctionInterface.java @@ -2,12 +2,19 @@ package org.kar.archidata.dataAccess.options; import java.util.List; +import org.kar.archidata.annotation.AnnotationTools; + /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ public interface CheckFunctionInterface { /** This function implementation is design to check if the updated class is valid of not for insertion + * @param baseName NAme of the object to be precise with the use of what fail. * @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(Object data, List filterValue) throws Exception; + void check(final String baseName, Object data, List filterValue) throws Exception; + + default void checkAll(final String baseName, final Object data) throws Exception { + check(baseName, data, AnnotationTools.getAllFieldsNames(data.getClass())); + } } diff --git a/src/org/kar/archidata/dataAccess/options/CheckFunctionVoid.java b/src/org/kar/archidata/dataAccess/options/CheckFunctionVoid.java new file mode 100644 index 0000000..78fc011 --- /dev/null +++ b/src/org/kar/archidata/dataAccess/options/CheckFunctionVoid.java @@ -0,0 +1,12 @@ +package org.kar.archidata.dataAccess.options; + +import java.util.List; + +/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ +public class CheckFunctionVoid implements CheckFunctionInterface { + @Override + public void check(final String baseName, Object data, List filterValue) { + + } + +} diff --git a/src/org/kar/archidata/dataAccess/options/CheckJPA.java b/src/org/kar/archidata/dataAccess/options/CheckJPA.java index c50f758..17ed318 100644 --- a/src/org/kar/archidata/dataAccess/options/CheckJPA.java +++ b/src/org/kar/archidata/dataAccess/options/CheckJPA.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.regex.Pattern; import org.kar.archidata.annotation.AnnotationTools; +import org.kar.archidata.annotation.DataJson; import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.exception.DataAccessException; @@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.ManyToOne; import jakarta.validation.constraints.Size; public class CheckJPA implements CheckFunctionInterface { @@ -34,7 +36,7 @@ public class CheckJPA implements CheckFunctionInterface { * @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; + void check(final String baseName, final K data) throws Exception; } private Map>> checking = null; @@ -63,34 +65,155 @@ public class CheckJPA implements CheckFunctionInterface { for (final Field field : this.clazz.getFields()) { final String fieldName = AnnotationTools.getFieldName(field); if (AnnotationTools.isPrimaryKey(field)) { - add(fieldName, (final T data) -> { - throw new InputException(fieldName, "This is a '@Id' (primaryKey) ==> can not be change"); + add(fieldName, (final String baseName, final T data) -> { + throw new InputException(baseName + fieldName, "This is a '@Id' (primaryKey) ==> can not be change"); }); } if (AnnotationTools.getConstraintsNotNull(field)) { - add(fieldName, (final T data) -> { + add(fieldName, (final String baseName, final T data) -> { if (field.get(data) == null) { - throw new InputException(fieldName, "Can not be null"); + throw new InputException(baseName + fieldName, "Can not be null"); } }); } if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) { - add(fieldName, (final T data) -> { - throw new InputException(fieldName, "It is forbidden to change this field"); + add(fieldName, (final String baseName, final T data) -> { + throw new InputException(baseName + fieldName, "It is forbidden to change this field"); }); } final Class type = field.getType(); if (type == Long.class || type == long.class) { + final Long maxValue = AnnotationTools.getConstraintsMax(field); + if (maxValue != null) { + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped > maxValue) { + throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); + } + }); + } + final Long minValue = AnnotationTools.getConstraintsMax(field); + if (minValue != null) { + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped < minValue) { + throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); + } + }); + } + final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field); + if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + long count = DataAccess.count(annotationManyToOne.targetEntity(), elem); + if (count == 0) { + throw new InputException(baseName + fieldName, "Foreign element does not exist in the DB:" + elem); + } + }); + } } else if (type == Integer.class || type == int.class) { + final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); + if (maxValueRoot != null) { + int maxValue = maxValueRoot.intValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped > maxValue) { + throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); + } + }); + } + final Long minValueRoot = AnnotationTools.getConstraintsMax(field); + if (minValueRoot != null) { + int minValue = minValueRoot.intValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped < minValue) { + throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); + } + }); + } } else if (type == Boolean.class || type == boolean.class) { } else if (type == Float.class || type == float.class) { - + final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); + if (maxValueRoot != null) { + float maxValue = maxValueRoot.floatValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped > maxValue) { + throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); + } + }); + } + final Long minValueRoot = AnnotationTools.getConstraintsMax(field); + if (minValueRoot != null) { + float minValue = minValueRoot.floatValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped < minValue) { + throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); + } + }); + } } else if (type == Double.class || type == double.class) { - + final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); + if (maxValueRoot != null) { + double maxValue = maxValueRoot.doubleValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped > maxValue) { + throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); + } + }); + } + final Long minValueRoot = AnnotationTools.getConstraintsMax(field); + if (minValueRoot != null) { + double minValue = minValueRoot.doubleValue(); + add(fieldName, (final String baseName, final T data) -> { + final Object elem = field.get(data); + if (elem == null) { + return; + } + final Long elemTyped = (Long) elem; + if (elemTyped < minValue) { + throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); + } + }); + } } else if (type == Date.class || type == Timestamp.class) { } else if (type == LocalDate.class) { @@ -100,59 +223,66 @@ public class CheckJPA implements CheckFunctionInterface { } else if (type == String.class) { final int maxSizeString = AnnotationTools.getLimitSize(field); if (maxSizeString > 0) { - add(fieldName, (final T data) -> { + add(fieldName, (final String baseName, final T data) -> { final Object elem = field.get(data); if (elem == null) { return; } final String elemTyped = (String) elem; if (elemTyped.length() > maxSizeString) { - throw new InputException(fieldName, "Too long size must be <= " + maxSizeString); + throw new InputException(baseName + fieldName, "Too long size must be <= " + maxSizeString); } }); } final Size limitSize = AnnotationTools.getConstraintsSize(field); if (limitSize != null) { - add(fieldName, (final T data) -> { + add(fieldName, (final String baseName, final T data) -> { final Object elem = field.get(data); if (elem == null) { return; } final String elemTyped = (String) elem; if (elemTyped.length() > limitSize.max()) { - throw new InputException(fieldName, "Too long size (constraints) must be <= " + limitSize.max()); + throw new InputException(baseName + fieldName, "Too long size (constraints) must be <= " + limitSize.max()); } if (elemTyped.length() < limitSize.min()) { - throw new InputException(fieldName, "Too small size (constraints) must be >= " + limitSize.max()); + throw new InputException(baseName + fieldName, "Too small size (constraints) must be >= " + limitSize.max()); } }); } final String patternString = AnnotationTools.getConstraintsPattern(field); if (patternString != null) { final Pattern pattern = Pattern.compile(patternString); - add(fieldName, (final T data) -> { + add(fieldName, (final String baseName, final T data) -> { final Object elem = field.get(data); if (elem == null) { return; } final String elemTyped = (String) elem; if (!pattern.matcher(elemTyped).find()) { - throw new InputException(fieldName, "does not match the required pattern (constraints) must be '" + patternString + "'"); + throw new InputException(baseName + fieldName, "does not match the required pattern (constraints) must be '" + patternString + "'"); } }); } } else if (type == JsonValue.class) { - + final DataJson jsonAnnotation = AnnotationTools.getDataJson(field); + if (jsonAnnotation != null && jsonAnnotation.checker() != CheckFunctionVoid.class) { + // Here if we have an error it crash at start and no new instance after creation... + CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor().newInstance(); + add(fieldName, (final String baseName, final T data) -> { + instance.checkAll(baseName + fieldName + ".", field.get(data)); + }); + } } else if (type.isEnum()) { // nothing to do. } // keep this is last ==> take more time... if (AnnotationTools.isUnique(field)) { // Create the request ... - add(fieldName, (final T data) -> { - final Object other = DataAccess.getWhere(this.clazz, new QueryCondition(fieldName, "==", field.get(data))); + add(fieldName, (final String baseName, final T data) -> { + final Object other = DataAccess.getWhere(this.clazz, new Condition(new QueryCondition(fieldName, "==", field.get(data)))); if (other != null) { - throw new InputException(fieldName, "Name already exist in the DB"); + throw new InputException(baseName + fieldName, "Name already exist in the DB"); } }); } @@ -165,7 +295,7 @@ public class CheckJPA implements CheckFunctionInterface { } @Override - public void check(final Object data, final List filterValue) throws Exception { + public void check(final String baseName, final Object data, final List filterValue) throws Exception { initialize(); if (!(this.clazz.isAssignableFrom(data.getClass()))) { throw new DataAccessException("Incompatatyble type of Object" + data.getClass().getCanonicalName()); @@ -178,7 +308,7 @@ public class CheckJPA implements CheckFunctionInterface { continue; } for (final CheckInterface action : actions) { - action.check(dataCasted); + action.check(baseName, dataCasted); } } checkTyped(dataCasted, filterValue); diff --git a/src/org/kar/archidata/dataAccess/options/Condition.java b/src/org/kar/archidata/dataAccess/options/Condition.java index bc1944b..e55f41b 100644 --- a/src/org/kar/archidata/dataAccess/options/Condition.java +++ b/src/org/kar/archidata/dataAccess/options/Condition.java @@ -15,6 +15,10 @@ public class Condition extends QueryOption { this.condition = items; } + public Condition() { + this.condition = null; + } + public void generateQuerry(final StringBuilder query, final String tableName) { if (this.condition != null) { this.condition.generateQuerry(query, tableName); diff --git a/src/org/kar/archidata/filter/CORSFilter.java b/src/org/kar/archidata/filter/CORSFilter.java index c197dcb..66c8245 100644 --- a/src/org/kar/archidata/filter/CORSFilter.java +++ b/src/org/kar/archidata/filter/CORSFilter.java @@ -18,6 +18,6 @@ public class CORSFilter implements ContainerResponseFilter { response.getHeaders().add("Access-Control-Allow-Headers", "*"); // "Origin, content-type, Content-type, Accept, authorization, mime-type, filename"); response.getHeaders().add("Access-Control-Allow-Credentials", "true"); - response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); + response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD"); } } diff --git a/src/org/kar/archidata/migration/MigrationEngine.java b/src/org/kar/archidata/migration/MigrationEngine.java index 8d4d5bf..b8d0b05 100644 --- a/src/org/kar/archidata/migration/MigrationEngine.java +++ b/src/org/kar/archidata/migration/MigrationEngine.java @@ -57,7 +57,7 @@ public class MigrationEngine { try { List data = null; try { - data = DataAccess.gets(Migration.class, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + data = DataAccess.gets(Migration.class, QueryOptions.READ_ALL_COLOMN); } catch (final Exception e) { // Previous version does not have the same timeCode... data = DataAccess.gets(Migration.class); diff --git a/src/org/kar/archidata/model/GenericData.java b/src/org/kar/archidata/model/GenericData.java index 2a64561..c3f632e 100644 --- a/src/org/kar/archidata/model/GenericData.java +++ b/src/org/kar/archidata/model/GenericData.java @@ -13,6 +13,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; +import jakarta.validation.constraints.NotNull; public class GenericData { @Id @@ -25,11 +26,13 @@ public class GenericData { @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP) @DataComment("Create time of the object") + @NotNull public Date createdAt = null; @DataNotRead @UpdateTimestamp @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP) @DataComment("When update the object") + @NotNull public Date updatedAt = null; } diff --git a/src/org/kar/archidata/model/GenericDataSoftDelete.java b/src/org/kar/archidata/model/GenericDataSoftDelete.java index 168cc97..1ddf47e 100644 --- a/src/org/kar/archidata/model/GenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/GenericDataSoftDelete.java @@ -6,6 +6,7 @@ import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataNotRead; import jakarta.persistence.Column; +import jakarta.validation.constraints.NotNull; public class GenericDataSoftDelete extends GenericData { @DataNotRead @@ -13,5 +14,6 @@ public class GenericDataSoftDelete extends GenericData { @DataDefault("'0'") @DataDeleted @DataComment("When delete, they are not removed, they are just set in a deleted state") + @NotNull public Boolean deleted = null; } diff --git a/src/org/kar/archidata/tools/DataTools.java b/src/org/kar/archidata/tools/DataTools.java index 45508ee..feb8faf 100644 --- a/src/org/kar/archidata/tools/DataTools.java +++ b/src/org/kar/archidata/tools/DataTools.java @@ -18,6 +18,7 @@ import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.QueryAnd; import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; +import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.model.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,7 +76,7 @@ public class DataTools { public static Data getWithSha512(final String sha512) { try { - return DataAccess.getWhere(Data.class, new QueryCondition("sha512", "=", sha512)); + return DataAccess.getWhere(Data.class, new Condition(new QueryCondition("sha512", "=", sha512))); } catch (final Exception e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -85,7 +86,7 @@ public class DataTools { public static Data getWithId(final long id) { try { - return DataAccess.getWhere(Data.class, new QueryAnd(List.of(new QueryCondition("deleted", "=", false), new QueryCondition("id", "=", id)))); + return DataAccess.getWhere(Data.class, new Condition(new QueryAnd(List.of(new QueryCondition("deleted", "=", false), new QueryCondition("id", "=", id))))); } catch (final Exception e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -109,15 +110,12 @@ public class DataTools { final String tmpPath = getTmpFileInData(tmpUID); final long fileSize = Files.size(Paths.get(tmpPath)); Data out = new Data(); - ; + try { out.sha512 = sha512; out.mimeType = mimeType; out.size = fileSize; out = DataAccess.insert(out); - } catch (final SQLException ex) { - ex.printStackTrace(); - return null; } catch (final Exception e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/org/kar/archidata/tools/RESTApi.java b/src/org/kar/archidata/tools/RESTApi.java index 63120b5..a37560c 100644 --- a/src/org/kar/archidata/tools/RESTApi.java +++ b/src/org/kar/archidata/tools/RESTApi.java @@ -118,20 +118,45 @@ public class RESTApi { } public T put(final Class clazz, final String urlOffset, final U data) throws RESTErrorResponseExeption, IOException, InterruptedException { - final String body = this.mapper.writeValueAsString(data); - return putJson(clazz, urlOffset, body); + return modelSend("PUT", clazz, urlOffset, data); } public T putJson(final Class clazz, final String urlOffset, final String body) throws RESTErrorResponseExeption, IOException, InterruptedException { + return modelSendJson("PUT", clazz, urlOffset, body); + } + + public T putMap(final Class clazz, final String urlOffset, final Map data) throws RESTErrorResponseExeption, IOException, InterruptedException { + return modelSendMap("PUT", clazz, urlOffset, data); + } + + public T patch(final Class clazz, final String urlOffset, final U data) throws RESTErrorResponseExeption, IOException, InterruptedException { + return modelSend("PATCH", clazz, urlOffset, data); + } + + public T patchJson(final Class clazz, final String urlOffset, final String body) throws RESTErrorResponseExeption, IOException, InterruptedException { + return modelSendJson("PATCH", clazz, urlOffset, body); + } + + public T patchMap(final Class clazz, final String urlOffset, final Map data) throws RESTErrorResponseExeption, IOException, InterruptedException { + return modelSendMap("PATCH", clazz, urlOffset, data); + } + + protected T modelSend(final String model, final Class clazz, final String urlOffset, final U data) throws RESTErrorResponseExeption, IOException, InterruptedException { + final String body = this.mapper.writeValueAsString(data); + return modelSendJson(model, clazz, urlOffset, body); + } + + protected T modelSendJson(final String model, final Class clazz, final String urlOffset, final String body) throws RESTErrorResponseExeption, IOException, InterruptedException { final HttpClient client = HttpClient.newHttpClient(); + // client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1).uri(URI.create(this.baseUrl + urlOffset)); - LOGGER.trace("call PUT: {}", URI.create(this.baseUrl + urlOffset)); + LOGGER.trace("call {}: {}", model, URI.create(this.baseUrl + urlOffset)); LOGGER.trace("DATA: {}", body); if (this.token != null) { requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + this.token); } requestBuilding = requestBuilding.header("Content-Type", "application/json"); - final HttpRequest request = requestBuilding.PUT(BodyPublishers.ofString(body)).build(); + final HttpRequest request = requestBuilding.method(model, BodyPublishers.ofString(body)).build(); final HttpResponse httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class); @@ -143,7 +168,7 @@ public class RESTApi { return this.mapper.readValue(httpResponse.body(), clazz); } - public T putMap(final Class clazz, final String urlOffset, final Map data) throws RESTErrorResponseExeption, IOException, InterruptedException { + protected T modelSendMap(final String model, final Class clazz, final String urlOffset, final Map data) throws RESTErrorResponseExeption, IOException, InterruptedException { final HttpClient client = HttpClient.newHttpClient(); final String body = this.mapper.writeValueAsString(data); Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1).uri(URI.create(this.baseUrl + urlOffset)); @@ -151,7 +176,7 @@ public class RESTApi { requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Yota " + this.token); } requestBuilding = requestBuilding.header("Content-Type", "application/json"); - final HttpRequest request = requestBuilding.PUT(BodyPublishers.ofString(body)).build(); + final HttpRequest request = requestBuilding.method(model, BodyPublishers.ofString(body)).build(); final HttpResponse httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), RESTErrorResponseExeption.class); diff --git a/test/src/test/kar/archidata/TestSimpleTable.java b/test/src/test/kar/archidata/TestSimpleTable.java index 31fb669..ccc5756 100644 --- a/test/src/test/kar/archidata/TestSimpleTable.java +++ b/test/src/test/kar/archidata/TestSimpleTable.java @@ -91,7 +91,7 @@ public class TestSimpleTable { @Test public void testReadAllValuesUnreadable() throws Exception { // check the full values - final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, QueryOptions.READ_ALL_COLOMN); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); @@ -114,7 +114,7 @@ public class TestSimpleTable { final SimpleTable test = new SimpleTable(); test.data = TestSimpleTable.DATA_INJECTED_2; DataAccess.update(test, TestSimpleTable.idOfTheObject, List.of("data")); - final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, QueryOptions.READ_ALL_COLOMN); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); Assertions.assertEquals(TestSimpleTable.idOfTheObject, retrieve.id); @@ -139,7 +139,7 @@ public class TestSimpleTable { public void testReadDeletedObject() throws Exception { // check if we set get deleted element - final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, new QueryOptions(QueryOptions.ACCESS_DELETED_ITEMS)); + final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, QueryOptions.ACCESS_DELETED_ITEMS); Assertions.assertNull(retrieve); } @@ -148,7 +148,7 @@ public class TestSimpleTable { @Test public void testReadAllValuesUnreadableOfDeletedObject() throws Exception { // check if we set get deleted element with all data - final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, new QueryOptions(QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN)); + final SimpleTable retrieve = DataAccess.get(SimpleTable.class, TestSimpleTable.idOfTheObject, QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN); Assertions.assertNull(retrieve); } diff --git a/test/src/test/kar/archidata/TestSimpleTableSoftDelete.java b/test/src/test/kar/archidata/TestSimpleTableSoftDelete.java index 165877d..34826cf 100644 --- a/test/src/test/kar/archidata/TestSimpleTableSoftDelete.java +++ b/test/src/test/kar/archidata/TestSimpleTableSoftDelete.java @@ -92,7 +92,7 @@ public class TestSimpleTableSoftDelete { @Test public void testReadAllValuesUnreadable() throws Exception { // check the full values - final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, QueryOptions.READ_ALL_COLOMN); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); @@ -118,8 +118,7 @@ public class TestSimpleTableSoftDelete { final SimpleTableSoftDelete test = new SimpleTableSoftDelete(); test.data = TestSimpleTableSoftDelete.DATA_INJECTED_2; DataAccess.update(test, TestSimpleTableSoftDelete.idOfTheObject, List.of("data")); - final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, - new QueryOptions(QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN)); + final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); Assertions.assertEquals(TestSimpleTableSoftDelete.idOfTheObject, retrieve.id); @@ -151,7 +150,7 @@ public class TestSimpleTableSoftDelete { public void testReadDeletedObject() throws Exception { // check if we set get deleted element - final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, new QueryOptions(QueryOptions.ACCESS_DELETED_ITEMS)); + final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, QueryOptions.ACCESS_DELETED_ITEMS); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); Assertions.assertEquals(TestSimpleTableSoftDelete.idOfTheObject, retrieve.id); @@ -166,8 +165,7 @@ public class TestSimpleTableSoftDelete { @Test public void testReadAllValuesUnreadableOfDeletedObject() throws Exception { // check if we set get deleted element with all data - final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, - new QueryOptions(QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN)); + final SimpleTableSoftDelete retrieve = DataAccess.get(SimpleTableSoftDelete.class, TestSimpleTableSoftDelete.idOfTheObject, QueryOptions.ACCESS_DELETED_ITEMS, QueryOptions.READ_ALL_COLOMN); Assertions.assertNotNull(retrieve); Assertions.assertNotNull(retrieve.id); Assertions.assertEquals(TestSimpleTableSoftDelete.idOfTheObject, retrieve.id);