From c144cd378e260a5cbba4d7174b9c07d5a0f554d5 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 5 Oct 2024 12:28:49 +0200 Subject: [PATCH] [DEV] start full refacto of SQL interface --- .../archidata/annotation/AnnotationTools.java | 34 +- .../kar/archidata/dataAccess/DataAccess.java | 844 +------- .../dataAccess/DataAccessMorphia.java | 1579 ++++++++++++++ .../archidata/dataAccess/DataAccessSQL.java | 1904 +++++++++++++++++ .../kar/archidata/dataAccess/QueryAnd.java | 27 +- .../archidata/dataAccess/QueryCondition.java | 34 +- .../kar/archidata/dataAccess/QueryInList.java | 9 + .../kar/archidata/dataAccess/QueryItem.java | 8 + .../archidata/dataAccess/QueryNotNull.java | 10 + .../kar/archidata/dataAccess/QueryNull.java | 17 +- src/org/kar/archidata/dataAccess/QueryOr.java | 22 +- .../dataAccess/options/CheckJPA.java | 38 +- .../dataAccess/options/Condition.java | 37 +- .../archidata/dataAccess/options/Limit.java | 10 +- .../archidata/dataAccess/options/OrderBy.java | 16 +- src/org/kar/archidata/db/DBConfig.java | 29 +- src/org/kar/archidata/db/DBEntry.java | 95 - src/org/kar/archidata/db/DbInterface.java | 3 + .../kar/archidata/db/DbInterfaceMorphia.java | 68 + src/org/kar/archidata/db/DbInterfaceSQL.java | 42 + .../kar/archidata/model/UUIDGenericData.java | 1 + 21 files changed, 3897 insertions(+), 930 deletions(-) create mode 100644 src/org/kar/archidata/dataAccess/DataAccessMorphia.java create mode 100644 src/org/kar/archidata/dataAccess/DataAccessSQL.java delete mode 100644 src/org/kar/archidata/db/DBEntry.java create mode 100644 src/org/kar/archidata/db/DbInterface.java create mode 100644 src/org/kar/archidata/db/DbInterfaceMorphia.java create mode 100644 src/org/kar/archidata/db/DbInterfaceSQL.java diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index 1017635..341a73b 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -11,6 +11,7 @@ import org.kar.archidata.exception.DataAccessException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.morphia.annotations.Entity; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.persistence.Column; @@ -31,6 +32,7 @@ import jakarta.ws.rs.DefaultValue; public class AnnotationTools { static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class); + // For SQL declaration table Name public static String getTableName(final Class clazz, final QueryOptions options) throws DataAccessException { if (options != null) { final List data = options.get(OverrideTableName.class); @@ -41,16 +43,13 @@ public class AnnotationTools { return AnnotationTools.getTableName(clazz); } - public static String getTableName(final Class element) throws DataAccessException { + // For SQL declaration table Name + public static String getTableName(final Class element) { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class); if (annotation.length == 0) { // when no annotation is detected, then the table name is the class name return element.getSimpleName(); } - if (annotation.length > 1) { - throw new DataAccessException( - "Must not have more than 1 element @Table on " + element.getClass().getCanonicalName()); - } final String tmp = ((Table) annotation[0]).name(); if (tmp == null) { return element.getSimpleName(); @@ -58,6 +57,31 @@ public class AnnotationTools { return tmp; } + public static String getCollectionName(final Class clazz, final QueryOptions options) { + if (options != null) { + // TODO: maybe change OverrideTableName with OverrideCollectionName + final List data = options.get(OverrideTableName.class); + if (data.size() == 1) { + return data.get(0).getName(); + } + } + return AnnotationTools.getCollectionName(clazz); + } + + // For No-SQL Table/Collection Name + public static String getCollectionName(final Class clazz) { + final Annotation[] annotation = clazz.getDeclaredAnnotationsByType(Entity.class); + if (annotation.length == 0) { + // when no annotation is detected, then the table name is the class name + return clazz.getSimpleName(); + } + final String tmp = ((Entity) annotation[0]).value(); + if (tmp == null) { + return clazz.getSimpleName(); + } + return tmp; + } + public static boolean getSchemaReadOnly(final Field element) throws DataAccessException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); if (annotation.length == 0) { diff --git a/src/org/kar/archidata/dataAccess/DataAccess.java b/src/org/kar/archidata/dataAccess/DataAccess.java index 48ff408..14d382a 100644 --- a/src/org/kar/archidata/dataAccess/DataAccess.java +++ b/src/org/kar/archidata/dataAccess/DataAccess.java @@ -9,12 +9,9 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.UUID; @@ -22,24 +19,19 @@ import java.util.UUID; import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.CreationTimestamp; import org.kar.archidata.annotation.UpdateTimestamp; -import org.kar.archidata.dataAccess.addOn.AddOnDataJson; -import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; -import org.kar.archidata.dataAccess.addOn.AddOnManyToOne; -import org.kar.archidata.dataAccess.addOn.AddOnOneToMany; import org.kar.archidata.dataAccess.options.CheckFunction; import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.DBInterfaceOption; -import org.kar.archidata.dataAccess.options.DBInterfaceRoot; import org.kar.archidata.dataAccess.options.FilterValue; import org.kar.archidata.dataAccess.options.GroupBy; import org.kar.archidata.dataAccess.options.Limit; import org.kar.archidata.dataAccess.options.OrderBy; import org.kar.archidata.dataAccess.options.QueryOption; import org.kar.archidata.dataAccess.options.TransmitKey; -import org.kar.archidata.db.DBEntry; +import org.kar.archidata.db.DbInterface; +import org.kar.archidata.db.DbInterfaceMorphia; +import org.kar.archidata.db.DbInterfaceSQL; import org.kar.archidata.exception.DataAccessException; -import org.kar.archidata.tools.ConfigBaseVariable; -import org.kar.archidata.tools.DateTools; import org.kar.archidata.tools.UuidUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,737 +49,31 @@ import jakarta.ws.rs.InternalServerErrorException; /** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite * back-end. */ -public class DataAccess { +public abstract class DataAccess { static final Logger LOGGER = LoggerFactory.getLogger(DataAccess.class); - // by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...) - static final List addOn = new ArrayList<>(); - static { - addOn.add(new AddOnManyToMany()); - addOn.add(new AddOnManyToOne()); - addOn.add(new AddOnOneToMany()); - addOn.add(new AddOnDataJson()); + static public final DataAccess greateInterface(final DbInterface io) { + if (io instanceof final DbInterfaceMorphia ioMorphia) { + return DataAccessMorphia(ioMorphia); + } else if (io instanceof final DbInterfaceSQL ioSQL) { + + } } - - /** Add a new add-on on the current management. - * @param addOn instantiate object on the Add-on */ - public static void addAddOn(final DataAccessAddOn addOn) { - DataAccess.addOn.add(addOn); - } - - public DataAccess() { - - } - + public static boolean isDBExist(final String name, final QueryOption... option) throws InternalServerErrorException { - final QueryOptions options = new QueryOptions(option); - if ("sqlite".equals(ConfigBaseVariable.getDBType())) { - // no base manage in sqLite ... - // TODO: check if the file exist or not ... - return true; - } - DBEntry entry; - try { - entry = DBInterfaceOption.getAutoEntry(options); - } catch (final IOException ex) { - ex.printStackTrace(); - LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); - - // TODO: TO test - - return false; - } - try { - // TODO : Maybe connect with a temporary not specified connection interface to a db ... - final PreparedStatement ps = entry.connection.prepareStatement("show databases"); - final ResultSet rs = ps.executeQuery(); - // LOGGER.info("List all tables: equals? '{}'", name); - while (rs.next()) { - final String data = rs.getString(1); - // LOGGER.info(" - '{}'", data); - if (name.equals(data)) { - return true; - } - } - return false; - } catch (final SQLException ex) { - LOGGER.error("Can not check if the DB exist SQL-error !!! {}", ex.getMessage()); - } finally { - try { - entry.close(); - } catch (final IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - entry = null; - } throw new InternalServerErrorException("Can Not manage the DB-access"); } - + public static boolean createDB(final String name) { - if ("sqlite".equals(ConfigBaseVariable.getDBType())) { - // no base manage in sqLite ... - // TODO: check if the file exist or not ... - return true; - } - try { - return 1 == DataAccess.executeSimpleQuery("CREATE DATABASE `" + name + "`;", new DBInterfaceRoot(true)); - } catch (final SQLException | IOException ex) { - ex.printStackTrace(); - LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); - return false; - } + throw new InternalServerErrorException("Can Not manage the DB-access"); } - + public static boolean isTableExist(final String name, final QueryOption... option) throws InternalServerErrorException { - final QueryOptions options = new QueryOptions(option); - try { - String request = ""; - if ("sqlite".equals(ConfigBaseVariable.getDBType())) { - request = """ - SELECT COUNT(*) AS total - FROM sqlite_master - WHERE type = 'table' - AND name = ?; - """; - // PreparedStatement ps = entry.connection.prepareStatement("show tables"); - final DBEntry entry = DBInterfaceOption.getAutoEntry(options); - final PreparedStatement ps = entry.connection.prepareStatement(request); - ps.setString(1, name); - final ResultSet ret = ps.executeQuery(); - final int count = ret.getInt("total"); - return count == 1; - } else { - final DBEntry entry = DBInterfaceOption.getAutoEntry(options); - // TODO : Maybe connect with a temporary not specified connection interface to a db ... - final PreparedStatement ps = entry.connection.prepareStatement("show tables"); - final ResultSet rs = ps.executeQuery(); - // LOGGER.info("List all tables: equals? '{}'", name); - while (rs.next()) { - final String data = rs.getString(1); - // LOGGER.info(" - '{}'", data); - if (name.equals(data)) { - return true; - } - } - return false; - } - } catch (final SQLException ex) { - LOGGER.error("Can not check if the table exist SQL-error !!! {}", ex.getMessage()); - } catch (final IOException ex) { - LOGGER.error("Can not check if the table exist!!! {}", ex.getMessage()); - } throw new InternalServerErrorException("Can Not manage the DB-access"); } - - /** Extract a list of Long with "-" separated element from a SQL input data. - * @param rs Result Set of the BDD - * @param iii Id in the result set - * @return The list of Long value - * @throws SQLException if an error is generated in the SQL request. */ - public static List getListOfIds(final ResultSet rs, final int iii, final String separator) - throws SQLException { - final String trackString = rs.getString(iii); - if (rs.wasNull()) { - return null; - } - final List out = new ArrayList<>(); - final String[] elements = trackString.split(separator); - for (final String elem : elements) { - final Long tmp = Long.parseLong(elem); - out.add(tmp); - } - return out; - } - - /** Extract a list of UUID with "-" separated element from a SQL input data. - * @param rs Result Set of the BDD - * @param iii Id in the result set - * @return The list of Long value - * @throws SQLException if an error is generated in the SQL request. */ - public static List getListOfUUIDs(final ResultSet rs, final int iii, final String separator) - throws SQLException { - final String trackString = rs.getString(iii); - if (rs.wasNull()) { - return null; - } - final List out = new ArrayList<>(); - final String[] elements = trackString.split(separator); - for (final String elem : elements) { - final UUID tmp = UUID.fromString(elem); - out.add(tmp); - } - return out; - } - - public static byte[][] splitIntoGroupsOf16Bytes(final byte[] input) { - final int inputLength = input.length; - final int numOfGroups = (inputLength + 15) / 16; // Calculate the number of groups needed - final byte[][] groups = new byte[numOfGroups][16]; - - for (int i = 0; i < numOfGroups; i++) { - final int startIndex = i * 16; - final int endIndex = Math.min(startIndex + 16, inputLength); - groups[i] = Arrays.copyOfRange(input, startIndex, endIndex); - } - - return groups; - } - - public static List getListOfRawUUIDs(final ResultSet rs, final int iii) - throws SQLException, DataAccessException { - final byte[] trackString = rs.getBytes(iii); - if (rs.wasNull()) { - return null; - } - final byte[][] elements = splitIntoGroupsOf16Bytes(trackString); - final List out = new ArrayList<>(); - for (final byte[] elem : elements) { - final UUID tmp = UuidUtils.asUuid(elem); - out.add(tmp); - } - return out; - } - - public static UUID getListOfRawUUID(final ResultSet rs, final int iii) throws SQLException, DataAccessException { - final byte[] elem = rs.getBytes(iii); - if (rs.wasNull()) { - return null; - } - return UuidUtils.asUuid(elem); - } - - protected static void setValuedb( - final Class type, - final T data, - final CountInOut iii, - final Field field, - final PreparedStatement ps) throws Exception { - if (type == UUID.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.BINARY); - } else { - final byte[] dataByte = UuidUtils.asBytes((UUID) tmp); - ps.setBytes(iii.value, dataByte); - } - } else if (type == Long.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.BIGINT); - } else { - ps.setLong(iii.value, (Long) tmp); - } - } else if (type == long.class) { - ps.setLong(iii.value, field.getLong(data)); - } else if (type == Integer.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - ps.setInt(iii.value, (Integer) tmp); - } - } else if (type == int.class) { - ps.setInt(iii.value, field.getInt(data)); - } else if (type == Float.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.FLOAT); - } else { - ps.setFloat(iii.value, (Float) tmp); - } - } else if (type == float.class) { - ps.setFloat(iii.value, field.getFloat(data)); - } else if (type == Double.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.DOUBLE); - } else { - ps.setDouble(iii.value, (Double) tmp); - } - } else if (type == Double.class) { - ps.setDouble(iii.value, field.getDouble(data)); - } else if (type == Boolean.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - ps.setBoolean(iii.value, (Boolean) tmp); - } - } else if (type == boolean.class) { - ps.setBoolean(iii.value, field.getBoolean(data)); - } else if (type == Timestamp.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - ps.setTimestamp(iii.value, (Timestamp) tmp); - } - } else if (type == Date.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant()); - ps.setTimestamp(iii.value, sqlDate); - } - } else if (type == Instant.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - final String sqlDate = ((Instant) tmp).toString(); - ps.setString(iii.value, sqlDate); - } - } else if (type == LocalDate.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - final java.sql.Date sqlDate = java.sql.Date.valueOf((LocalDate) tmp); - ps.setDate(iii.value, sqlDate); - } - } else if (type == LocalTime.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.INTEGER); - } else { - final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp); - ps.setTime(iii.value, sqlDate); - } - } else if (type == String.class) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.VARCHAR); - } else { - ps.setString(iii.value, (String) tmp); - } - } else if (type.isEnum()) { - final Object tmp = field.get(data); - if (tmp == null) { - ps.setNull(iii.value, Types.VARCHAR); - } else { - ps.setString(iii.value, tmp.toString()); - } - } else { - throw new DataAccessException("Unknown Field Type"); - } - iii.inc(); - } - - protected static void setValueFromDb( - final Class type, - final Object data, - final CountInOut count, - final Field field, - final ResultSet rs, - final CountInOut countNotNull) throws Exception { - if (type == UUID.class) { - final byte[] tmp = rs.getBytes(count.value); - // final UUID tmp = rs.getObject(count.value, UUID.class); - if (rs.wasNull()) { - field.set(data, null); - } else { - // field.set(data, tmp); - final UUID uuid = UuidUtils.asUuid(tmp); - field.set(data, uuid); - countNotNull.inc(); - } - } else if (type == Long.class) { - final Long tmp = rs.getLong(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == long.class) { - final Long tmp = rs.getLong(count.value); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setLong(data, tmp); - countNotNull.inc(); - } - } else if (type == Integer.class) { - final Integer tmp = rs.getInt(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == int.class) { - final Integer tmp = rs.getInt(count.value); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setInt(data, tmp); - countNotNull.inc(); - } - } else if (type == Float.class) { - final Float tmp = rs.getFloat(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == float.class) { - final Float tmp = rs.getFloat(count.value); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setFloat(data, tmp); - countNotNull.inc(); - } - } else if (type == Double.class) { - final Double tmp = rs.getDouble(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == double.class) { - final Double tmp = rs.getDouble(count.value); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setDouble(data, tmp); - countNotNull.inc(); - } - } else if (type == Boolean.class) { - final Boolean tmp = rs.getBoolean(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == boolean.class) { - final Boolean tmp = rs.getBoolean(count.value); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setBoolean(data, tmp); - countNotNull.inc(); - } - } else if (type == Timestamp.class) { - final Timestamp tmp = rs.getTimestamp(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type == Date.class) { - try { - final Timestamp tmp = rs.getTimestamp(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, Date.from(tmp.toInstant())); - countNotNull.inc(); - } - } catch (final SQLException ex) { - final String tmp = rs.getString(count.value); - LOGGER.error("Fail to parse the SQL time !!! {}", tmp); - if (rs.wasNull()) { - field.set(data, null); - } else { - final Date date = DateTools.parseDate(tmp); - LOGGER.error("Fail to parse the SQL time !!! {}", date); - field.set(data, date); - countNotNull.inc(); - } - } - } else if (type == Instant.class) { - final String tmp = rs.getString(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, Instant.parse(tmp)); - countNotNull.inc(); - } - } else if (type == LocalDate.class) { - final java.sql.Date tmp = rs.getDate(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp.toLocalDate()); - countNotNull.inc(); - } - } else if (type == LocalTime.class) { - final java.sql.Time tmp = rs.getTime(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp.toLocalTime()); - countNotNull.inc(); - } - } else if (type == String.class) { - final String tmp = rs.getString(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - field.set(data, tmp); - countNotNull.inc(); - } - } else if (type.isEnum()) { - final String tmp = rs.getString(count.value); - if (rs.wasNull()) { - field.set(data, null); - } else { - boolean find = false; - final Object[] arr = type.getEnumConstants(); - for (final Object elem : arr) { - if (elem.toString().equals(tmp)) { - field.set(data, elem); - countNotNull.inc(); - find = true; - break; - } - } - if (!find) { - throw new DataAccessException("Enum value does not exist in the Model: '" + tmp + "'"); - } - } - } else { - throw new DataAccessException("Unknown Field Type"); - } - count.inc(); - } - - // TODO: this function will replace the previous one !!! - protected static RetreiveFromDB createSetValueFromDbCallback(final int count, final Field field) throws Exception { - final Class type = field.getType(); - if (type == UUID.class) { - return (final ResultSet rs, final Object obj) -> { - - final byte[] tmp = rs.getBytes(count); - // final UUID tmp = rs.getObject(count, UUID.class); - if (rs.wasNull()) { - field.set(obj, null); - } else { - // field.set(obj, tmp); - final UUID uuid = UuidUtils.asUuid(tmp); - field.set(obj, uuid); - } - }; - } - if (type == Long.class) { - return (final ResultSet rs, final Object obj) -> { - final Long tmp = rs.getLong(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == long.class) { - return (final ResultSet rs, final Object obj) -> { - final Long tmp = rs.getLong(count); - if (rs.wasNull()) { - // field.set(data, null); - } else { - field.setLong(obj, tmp); - } - }; - } - if (type == Integer.class) { - return (final ResultSet rs, final Object obj) -> { - final Integer tmp = rs.getInt(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == int.class) { - return (final ResultSet rs, final Object obj) -> { - final Integer tmp = rs.getInt(count); - if (rs.wasNull()) { - // field.set(obj, null); - } else { - field.setInt(obj, tmp); - } - }; - } - if (type == Float.class) { - return (final ResultSet rs, final Object obj) -> { - final Float tmp = rs.getFloat(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == float.class) { - return (final ResultSet rs, final Object obj) -> { - final Float tmp = rs.getFloat(count); - if (rs.wasNull()) { - // field.set(obj, null); - } else { - field.setFloat(obj, tmp); - } - }; - } - if (type == Double.class) { - return (final ResultSet rs, final Object obj) -> { - final Double tmp = rs.getDouble(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == double.class) { - return (final ResultSet rs, final Object obj) -> { - final Double tmp = rs.getDouble(count); - if (rs.wasNull()) { - // field.set(obj, null); - } else { - field.setDouble(obj, tmp); - } - }; - } - if (type == Boolean.class) { - return (final ResultSet rs, final Object obj) -> { - final Boolean tmp = rs.getBoolean(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == boolean.class) { - return (final ResultSet rs, final Object obj) -> { - final Boolean tmp = rs.getBoolean(count); - if (rs.wasNull()) { - // field.set(obj, null); - } else { - field.setBoolean(obj, tmp); - } - }; - } - if (type == Timestamp.class) { - return (final ResultSet rs, final Object obj) -> { - final Timestamp tmp = rs.getTimestamp(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type == Date.class) { - return (final ResultSet rs, final Object obj) -> { - try { - final Timestamp tmp = rs.getTimestamp(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, Date.from(tmp.toInstant())); - } - } catch (final SQLException ex) { - final String tmp = rs.getString(count); - LOGGER.error("Fail to parse the SQL time !!! {}", tmp); - if (rs.wasNull()) { - field.set(obj, null); - } else { - final Date date = DateTools.parseDate(tmp); - LOGGER.error("Fail to parse the SQL time !!! {}", date); - field.set(obj, date); - } - } - }; - } - if (type == Instant.class) { - return (final ResultSet rs, final Object obj) -> { - final String tmp = rs.getString(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, Instant.parse(tmp)); - } - }; - } - if (type == LocalDate.class) { - return (final ResultSet rs, final Object obj) -> { - final java.sql.Date tmp = rs.getDate(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp.toLocalDate()); - } - }; - } - if (type == LocalTime.class) { - return (final ResultSet rs, final Object obj) -> { - final java.sql.Time tmp = rs.getTime(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp.toLocalTime()); - } - }; - } - if (type == String.class) { - return (final ResultSet rs, final Object obj) -> { - final String tmp = rs.getString(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - field.set(obj, tmp); - } - }; - } - if (type.isEnum()) { - return (final ResultSet rs, final Object obj) -> { - final String tmp = rs.getString(count); - if (rs.wasNull()) { - field.set(obj, null); - } else { - boolean find = false; - final Object[] arr = type.getEnumConstants(); - for (final Object elem : arr) { - if (elem.toString().equals(tmp)) { - field.set(obj, elem); - find = true; - break; - } - } - if (!find) { - throw new DataAccessException("Enum value does not exist in the Model: '" + tmp + "'"); - } - } - }; - } - throw new DataAccessException("Unknown Field Type"); - - } - - public static boolean isAddOnField(final Field field) { - return findAddOnforField(field) != null; - } - - public static DataAccessAddOn findAddOnforField(final Field field) { - for (final DataAccessAddOn elem : addOn) { - if (elem.isCompatibleField(field)) { - return elem; - } - } - return null; - } - + // TODO: manage insert batch... public static List insertMultiple(final List data, final QueryOption... options) throws Exception { final List out = new ArrayList<>(); @@ -797,18 +83,18 @@ public class DataAccess { } return out; } - + @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") public static T insert(final T data, final QueryOption... option) throws Exception { final Class clazz = data.getClass(); final QueryOptions options = new QueryOptions(option); - + // External checker of data: final List checks = options.get(CheckFunction.class); for (final CheckFunction check : checks) { check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz), options); } - + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); final List asyncFieldUpdate = new ArrayList<>(); Long uniqueSQLID = null; @@ -823,7 +109,7 @@ public class DataAccess { query.append("INSERT INTO `"); query.append(tableName); query.append("` ("); - + boolean firstField = true; int count = 0; for (final Field field : clazz.getFields()) { @@ -909,7 +195,7 @@ public class DataAccess { // prepare the request: final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); - + final CountInOut iii = new CountInOut(1); UUID uuid = null; if (generateUUID) { @@ -1020,7 +306,7 @@ public class DataAccess { } return data; } - + // seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated... public static T insertWithJson(final Class clazz, final String jsonData) throws Exception { final ObjectMapper mapper = new ObjectMapper(); @@ -1028,7 +314,7 @@ public class DataAccess { final T data = mapper.readValue(jsonData, clazz); return insert(data); } - + public static QueryCondition getTableIdCondition(final Class clazz, final ID_TYPE idKey) throws DataAccessException { // Find the ID field type .... @@ -1051,7 +337,7 @@ public class DataAccess { } return new QueryCondition(AnnotationTools.getFieldName(idField), "=", idKey); } - + /** Update an object with the inserted json data * * @param Type of the object to insert @@ -1071,7 +357,7 @@ public class DataAccess { options.add(new TransmitKey(id)); return updateWhereWithJson(clazz, jsonData, options.getAllArray()); } - + public static int updateWhereWithJson(final Class clazz, final String jsonData, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); @@ -1089,11 +375,11 @@ public class DataAccess { 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, AnnotationTools.getFieldsNames(data.getClass())); } - + /** @param * @param data * @param id @@ -1111,12 +397,12 @@ public class DataAccess { options.add(new TransmitKey(id)); return updateWhere(data, options); } - + public static int updateWhere(final T data, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); return updateWhere(data, options); } - + @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") public static int updateWhere(final T data, QueryOptions options) throws Exception { final Class clazz = data.getClass(); @@ -1146,7 +432,7 @@ public class DataAccess { query.append("UPDATE `"); query.append(tableName); query.append("` SET "); - + boolean firstField = true; for (final Field field : clazz.getFields()) { // static field is only for internal global declaration ==> remove it .. @@ -1194,7 +480,7 @@ public class DataAccess { query.append(" "); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); condition.whereAppendQuery(query, tableName, null, deletedFieldName); - + // If the first field is not set, then nothing to update n the main base: if (!firstField) { LOGGER.debug("generate update query: '{}'", query.toString()); @@ -1243,7 +529,7 @@ public class DataAccess { } return 0; } - + public static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii) throws Exception { if (value instanceof final UUID tmp) { @@ -1292,7 +578,7 @@ public class DataAccess { throw new DataAccessException("Not manage type ==> need to add it ..."); } } - + public static int executeSimpleQuery(final String query, final QueryOption... option) throws SQLException, IOException { final QueryOptions options = new QueryOptions(option); @@ -1302,7 +588,7 @@ public class DataAccess { return stmt.executeUpdate(query); } } - + public static boolean executeQuery(final String query, final QueryOption... option) throws SQLException, IOException { final QueryOptions options = new QueryOptions(option); @@ -1311,7 +597,7 @@ public class DataAccess { return stmt.execute(query); } } - + public static T getWhere(final Class clazz, final QueryOptions options) throws Exception { options.add(new Limit(1)); final List values = getsWhere(clazz, options); @@ -1320,12 +606,12 @@ public class DataAccess { } return values.get(0); } - + public static T getWhere(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); return getWhere(clazz, options); } - + public static void generateSelectField(// final StringBuilder querySelect, // final StringBuilder query, // @@ -1337,7 +623,7 @@ public class DataAccess { final String tableName = AnnotationTools.getTableName(clazz, options); final String primaryKey = AnnotationTools.getPrimaryKeyField(clazz).getName(); boolean firstField = true; - + for (final Field elem : clazz.getFields()) { // static field is only for internal global declaration ==> remove it .. if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { @@ -1368,12 +654,12 @@ public class DataAccess { } } } - + public static List getsWhere(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); return getsWhere(clazz, options); } - + public static Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty) throws DataAccessException { if (options == null) { @@ -1399,7 +685,7 @@ public class DataAccess { } return condition; } - + @SuppressWarnings("unchecked") public static List getsWhere(final Class clazz, final QueryOptions options) throws DataAccessException, IOException { @@ -1416,7 +702,7 @@ public class DataAccess { query.append(" FROM `"); query.append(tableName); query.append("` "); - + generateSelectField(querySelect, query, clazz, options, count); querySelect.append(query.toString()); query = querySelect; @@ -1467,7 +753,7 @@ public class DataAccess { } return outs; } - + public static Object createObjectFromSQLRequest( final ResultSet rs, final Class clazz, @@ -1508,19 +794,19 @@ public class DataAccess { } return data; } - + public static long count(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); options.add(new Condition(getTableIdCondition(clazz, id))); return DataAccess.countWhere(clazz, options); } - + public static long countWhere(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); return countWhere(clazz, options); } - + public static long countWhere(final Class clazz, final QueryOptions options) throws Exception { final Condition condition = conditionFusionOrEmpty(options, false); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); @@ -1565,22 +851,22 @@ public class DataAccess { } return count; } - + public static T get(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); options.add(new Condition(getTableIdCondition(clazz, id))); return DataAccess.getWhere(clazz, options.getAllArray()); } - + public static List gets(final Class clazz) throws Exception { return getsWhere(clazz); } - + public static List gets(final Class clazz, final QueryOption... option) throws Exception { return getsWhere(clazz, option); } - + /** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). * @param Type of the reference @Id * @param clazz Data model that might remove element @@ -1596,14 +882,14 @@ public class DataAccess { return deleteHard(clazz, id, options); } } - + /** Delete items with the specific condition and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). * @param clazz Data model that might remove element. * @param condition Condition to remove elements. * @param options (Optional) Options of the request. * @return Number of element that is removed. */ public static int deleteWhere(final Class clazz, final QueryOption... option) throws Exception { - + final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); if (hasDeletedFieldName != null) { return deleteSoftWhere(clazz, option); @@ -1611,14 +897,14 @@ public class DataAccess { return deleteHardWhere(clazz, option); } } - + public static int deleteHard(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); options.add(new Condition(getTableIdCondition(clazz, id))); return deleteHardWhere(clazz, options.getAllArray()); } - + public static int deleteHardWhere(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); final Condition condition = conditionFusionOrEmpty(options, true); @@ -1641,14 +927,14 @@ public class DataAccess { entry.close(); } } - + private static int deleteSoft(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); options.add(new Condition(getTableIdCondition(clazz, id))); return deleteSoftWhere(clazz, options.getAllArray()); } - + public static int deleteSoftWhere(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); final Condition condition = conditionFusionOrEmpty(options, true); @@ -1676,18 +962,18 @@ public class DataAccess { entry.close(); } } - + public static int unsetDelete(final Class clazz, final ID_TYPE id) throws DataAccessException { return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id))); } - + public static int unsetDelete(final Class clazz, final ID_TYPE id, final QueryOption... option) throws DataAccessException { final QueryOptions options = new QueryOptions(option); options.add(new Condition(getTableIdCondition(clazz, id))); return unsetDeleteWhere(clazz, options.getAllArray()); } - + public static int unsetDeleteWhere(final Class clazz, final QueryOption... option) throws DataAccessException { final QueryOptions options = new QueryOptions(option); final Condition condition = conditionFusionOrEmpty(options, true); @@ -1721,7 +1007,7 @@ public class DataAccess { throw new DataAccessException("Fail to excute the SQL query:" + ex.getMessage()); } } - + public static void drop(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); final String tableName = AnnotationTools.getTableName(clazz, options); @@ -1753,7 +1039,7 @@ public class DataAccess { entry.close(); } } - + public static void cleanAll(final Class clazz, final QueryOption... option) throws Exception { final QueryOptions options = new QueryOptions(option); final String tableName = AnnotationTools.getTableName(clazz, options); @@ -1786,7 +1072,7 @@ public class DataAccess { entry = null; } } - + /** Execute a simple query with external property. * @param Type of the data generate. * @param clazz Class that might be analyze. @@ -1803,7 +1089,7 @@ public class DataAccess { final QueryOptions options = new QueryOptions(option); return query(clazz, query, parameters, options); } - + public static List query( final Class clazz, final String queryBase, @@ -1812,7 +1098,7 @@ public class DataAccess { final List lazyCall = new ArrayList<>(); // TODO ... final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); final DBEntry entry = DBInterfaceOption.getAutoEntry(options); - + final Condition condition = conditionFusionOrEmpty(options, false); final StringBuilder query = new StringBuilder(queryBase); final List outs = new ArrayList<>(); @@ -1820,7 +1106,7 @@ public class DataAccess { try { final CountInOut count = new CountInOut(); condition.whereAppendQuery(query, null, options, null); - + final List groups = options.get(GroupBy.class); for (final GroupBy group : groups) { group.generateQuery(query, null); @@ -1867,7 +1153,7 @@ public class DataAccess { final RetreiveFromDB element = createSetValueFromDbCallback(jjj + 1, field); actionToRetreive.add(element); } - + while (rs.next()) { count.value = 1; Object data = null; diff --git a/src/org/kar/archidata/dataAccess/DataAccessMorphia.java b/src/org/kar/archidata/dataAccess/DataAccessMorphia.java new file mode 100644 index 0000000..d681175 --- /dev/null +++ b/src/org/kar/archidata/dataAccess/DataAccessMorphia.java @@ -0,0 +1,1579 @@ +package org.kar.archidata.dataAccess; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import org.kar.archidata.annotation.AnnotationTools; +import org.kar.archidata.annotation.CreationTimestamp; +import org.kar.archidata.annotation.UpdateTimestamp; +import org.kar.archidata.dataAccess.options.CheckFunction; +import org.kar.archidata.dataAccess.options.Condition; +import org.kar.archidata.dataAccess.options.DBInterfaceOption; +import org.kar.archidata.dataAccess.options.DBInterfaceRoot; +import org.kar.archidata.dataAccess.options.FilterValue; +import org.kar.archidata.dataAccess.options.Limit; +import org.kar.archidata.dataAccess.options.OrderBy; +import org.kar.archidata.dataAccess.options.QueryOption; +import org.kar.archidata.dataAccess.options.TransmitKey; +import org.kar.archidata.db.DBEntry; +import org.kar.archidata.db.DbInterfaceMorphia; +import org.kar.archidata.exception.DataAccessException; +import org.kar.archidata.tools.ConfigBaseVariable; +import org.kar.archidata.tools.DateTools; +import org.kar.archidata.tools.UuidUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.InsertOneResult; +import com.mongodb.client.result.UpdateResult; + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.InternalServerErrorException; + +/* TODO list: + - Manage to group of SQL action to permit to commit only at the end. + */ + +/** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite + * back-end. */ +public class DataAccessMorphia { + static final Logger LOGGER = LoggerFactory.getLogger(DataAccessMorphia.class); + // by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...) + static final List addOn = new ArrayList<>(); + + /*static { + addOn.add(new AddOnManyToMany()); + addOn.add(new AddOnManyToOne()); + addOn.add(new AddOnOneToMany()); + addOn.add(new AddOnDataJson()); + } + */ + + /** Add a new add-on on the current management. + * @param addOn instantiate object on the Add-on + */ + public static void addAddOn(final DataAccessAddOn addOn) { + DataAccessMorphia.addOn.add(addOn); + } + + private final DbInterfaceMorphia db; + + public DataAccessMorphia(final DbInterfaceMorphia db) { + this.db = db; + } + + public boolean isDBExist(final String name, final QueryOption... option) throws InternalServerErrorException { + final QueryOptions options = new QueryOptions(option); + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + // no base manage in sqLite ... + // TODO: check if the file exist or not ... + return true; + } + DBEntry entry; + try { + entry = DBInterfaceOption.getAutoEntry(options); + } catch (final IOException ex) { + ex.printStackTrace(); + LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); + + // TODO: TO test + + return false; + } + try { + // TODO : Maybe connect with a temporary not specified connection interface to a db ... + final PreparedStatement ps = entry.connection.prepareStatement("show databases"); + final ResultSet rs = ps.executeQuery(); + // LOGGER.info("List all tables: equals? '{}'", name); + while (rs.next()) { + final String data = rs.getString(1); + // LOGGER.info(" - '{}'", data); + if (name.equals(data)) { + return true; + } + } + return false; + } catch (final SQLException ex) { + LOGGER.error("Can not check if the DB exist SQL-error !!! {}", ex.getMessage()); + } finally { + try { + entry.close(); + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + entry = null; + } + throw new InternalServerErrorException("Can Not manage the DB-access"); + } + + public boolean createDB(final String name) { + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + // no base manage in sqLite ... + // TODO: check if the file exist or not ... + return true; + } + try { + return 1 == executeSimpleQuery("CREATE DATABASE `" + name + "`;", new DBInterfaceRoot(true)); + } catch (final SQLException | IOException ex) { + ex.printStackTrace(); + LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); + return false; + } + } + + public boolean isTableExist(final String name, final QueryOption... option) throws InternalServerErrorException { + final QueryOptions options = new QueryOptions(option); + try { + String request = ""; + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + request = """ + SELECT COUNT(*) AS total + FROM sqlite_master + WHERE type = 'table' + AND name = ?; + """; + // PreparedStatement ps = entry.connection.prepareStatement("show tables"); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final PreparedStatement ps = entry.connection.prepareStatement(request); + ps.setString(1, name); + final ResultSet ret = ps.executeQuery(); + final int count = ret.getInt("total"); + return count == 1; + } else { + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + // TODO : Maybe connect with a temporary not specified connection interface to a db ... + final PreparedStatement ps = entry.connection.prepareStatement("show tables"); + final ResultSet rs = ps.executeQuery(); + // LOGGER.info("List all tables: equals? '{}'", name); + while (rs.next()) { + final String data = rs.getString(1); + // LOGGER.info(" - '{}'", data); + if (name.equals(data)) { + return true; + } + } + return false; + } + } catch (final SQLException ex) { + LOGGER.error("Can not check if the table exist SQL-error !!! {}", ex.getMessage()); + } catch (final IOException ex) { + LOGGER.error("Can not check if the table exist!!! {}", ex.getMessage()); + } + throw new InternalServerErrorException("Can Not manage the DB-access"); + } + + /** Extract a list of Long with "-" separated element from a SQL input data. + * @param rs Result Set of the BDD + * @param iii Id in the result set + * @return The list of Long value + * @throws SQLException if an error is generated in the SQL request. */ + public List getListOfIds(final ResultSet rs, final int iii, final String separator) throws SQLException { + final String trackString = rs.getString(iii); + if (rs.wasNull()) { + return null; + } + final List out = new ArrayList<>(); + final String[] elements = trackString.split(separator); + for (final String elem : elements) { + final Long tmp = Long.parseLong(elem); + out.add(tmp); + } + return out; + } + + /** Extract a list of UUID with "-" separated element from a SQL input data. + * @param rs Result Set of the BDD + * @param iii Id in the result set + * @return The list of Long value + * @throws SQLException if an error is generated in the SQL request. */ + public List getListOfUUIDs(final ResultSet rs, final int iii, final String separator) throws SQLException { + final String trackString = rs.getString(iii); + if (rs.wasNull()) { + return null; + } + final List out = new ArrayList<>(); + final String[] elements = trackString.split(separator); + for (final String elem : elements) { + final UUID tmp = UUID.fromString(elem); + out.add(tmp); + } + return out; + } + + public byte[][] splitIntoGroupsOf16Bytes(final byte[] input) { + final int inputLength = input.length; + final int numOfGroups = (inputLength + 15) / 16; // Calculate the number of groups needed + final byte[][] groups = new byte[numOfGroups][16]; + + for (int i = 0; i < numOfGroups; i++) { + final int startIndex = i * 16; + final int endIndex = Math.min(startIndex + 16, inputLength); + groups[i] = Arrays.copyOfRange(input, startIndex, endIndex); + } + + return groups; + } + + public List getListOfRawUUIDs(final ResultSet rs, final int iii) throws SQLException, DataAccessException { + final byte[] trackString = rs.getBytes(iii); + if (rs.wasNull()) { + return null; + } + final byte[][] elements = splitIntoGroupsOf16Bytes(trackString); + final List out = new ArrayList<>(); + for (final byte[] elem : elements) { + final UUID tmp = UuidUtils.asUuid(elem); + out.add(tmp); + } + return out; + } + + public UUID getListOfRawUUID(final ResultSet rs, final int iii) throws SQLException, DataAccessException { + final byte[] elem = rs.getBytes(iii); + if (rs.wasNull()) { + return null; + } + return UuidUtils.asUuid(elem); + } + + protected void setValuedb( + final Class type, + final T data, + final Field field, + final String fieldName, + final Document docSet, + final Document docUnSet) throws Exception { + if (type == long.class) { + + docSet.append(fieldName, field.getLong(data)); + return; + } + if (type == int.class) { + docSet.append(fieldName, field.getInt(data)); + return; + } + if (type == float.class) { + docSet.append(fieldName, field.getFloat(data)); + return; + } + if (type == Double.class) { + docSet.append(fieldName, field.getDouble(data)); + return; + } + if (type == boolean.class) { + docSet.append(fieldName, field.getBoolean(data)); + return; + } + final Object tmp = field.get(data); + if (tmp == null) { + docUnSet.append(fieldName, ""); + return; + } + if (type.isEnum()) { + docSet.append(fieldName, tmp.toString()); + return; + } + if (type == Long.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Integer.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Float.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Double.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Boolean.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == String.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Timestamp.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == UUID.class) { + docSet.append(fieldName, tmp); + return; + } + if (type == Date.class) { + // TODO ... + /* + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant()); + ps.setTimestamp(iii.value, sqlDate); + }*/ + } + if (type == Instant.class) { + /* + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final String sqlDate = ((Instant) tmp).toString(); + ps.setString(iii.value, sqlDate); + } + */ + } + if (type == LocalDate.class) { + /* + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final java.sql.Date sqlDate = java.sql.Date.valueOf((LocalDate) tmp); + ps.setDate(iii.value, sqlDate); + } + */ + } + if (type == LocalTime.class) { + /* + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp); + ps.setTime(iii.value, sqlDate); + } + */ + } + throw new DataAccessException("Unknown Field Type"); + + } + + protected void setValueFromDoc( + final Class type, + final Object data, + final CountInOut count, + final Field field, + final Document doc, + final CountInOut countNotNull) throws Exception { + final String fieldName = AnnotationTools.getFieldName(field); + if (!doc.containsKey(fieldName)) { + field.set(data, null); + return; + } + if (type == UUID.class) { + final UUID value = doc.get(fieldName, UUID.class); + field.set(data, value); + return; + } + if (type == Long.class || type == long.class) { + final Long value = doc.getLong(fieldName); + field.set(data, value); + return; + } + if (type == Integer.class || type == int.class) { + final Integer value = doc.getInteger(fieldName); + field.set(data, value); + return; + } + if (type == Float.class || type == float.class) { + final Double value = doc.getDouble(fieldName); + field.set(data, (float) ((double) value)); + return; + } + if (type == Double.class || type == double.class) { + final Double value = doc.getDouble(fieldName); + field.set(data, value); + return; + } + if (type == Boolean.class || type == boolean.class) { + final Boolean value = doc.getBoolean(fieldName); + field.set(data, value); + return; + } + if (type == Timestamp.class) { + LOGGER.error("TODO: TimeStamp ... "); + /* + final Timestamp tmp = rs.getTimestamp(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + */ + } + if (type == Date.class) { + LOGGER.error("TODO: Date ... "); + /* + try { + final Timestamp tmp = rs.getTimestamp(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, Date.from(tmp.toInstant())); + countNotNull.inc(); + } + } catch (final SQLException ex) { + final String tmp = rs.getString(count.value); + LOGGER.error("Fail to parse the SQL time !!! {}", tmp); + if (rs.wasNull()) { + field.set(data, null); + } else { + final Date date = DateTools.parseDate(tmp); + LOGGER.error("Fail to parse the SQL time !!! {}", date); + field.set(data, date); + countNotNull.inc(); + } + }*/ + } + if (type == Instant.class) { + LOGGER.error("TODO: Instant ... "); + /* + final String tmp = rs.getString(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, Instant.parse(tmp)); + countNotNull.inc(); + } + */ + } + if (type == LocalDate.class) { + LOGGER.error("TODO: LocalDate ... "); + /* + final java.sql.Date tmp = rs.getDate(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp.toLocalDate()); + countNotNull.inc(); + } + */ + } + if (type == LocalTime.class) { + LOGGER.error("TODO: LocalTime ... "); + /* + final java.sql.Time tmp = rs.getTime(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp.toLocalTime()); + countNotNull.inc(); + } + */ + } + if (type == String.class) { + final String value = doc.getString(fieldName); + field.set(data, value); + return; + + } + if (type.isEnum()) { + final String value = doc.getString(fieldName); + boolean find = false; + final Object[] arr = type.getEnumConstants(); + for (final Object elem : arr) { + if (elem.toString().equals(value)) { + field.set(data, elem); + countNotNull.inc(); + find = true; + break; + } + } + if (!find) { + throw new DataAccessException("Enum value does not exist in the Model: '" + value + "'"); + } + return; + } + final Object value = doc.get(fieldName, field.getType()); + field.set(data, value); + return; + //throw new ArchiveException("wrong type of field [" + fieldName + "]: " + doc.toJson()); + } + + // TODO: this function will replace the previous one !!! + protected RetreiveFromDB createSetValueFromDbCallback(final int count, final Field field) throws Exception { + final Class type = field.getType(); + if (type == UUID.class) { + return (final ResultSet rs, final Object obj) -> { + + final byte[] tmp = rs.getBytes(count); + // final UUID tmp = rs.getObject(count, UUID.class); + if (rs.wasNull()) { + field.set(obj, null); + } else { + // field.set(obj, tmp); + final UUID uuid = UuidUtils.asUuid(tmp); + field.set(obj, uuid); + } + }; + } + if (type == Long.class) { + return (final ResultSet rs, final Object obj) -> { + final Long tmp = rs.getLong(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == long.class) { + return (final ResultSet rs, final Object obj) -> { + final Long tmp = rs.getLong(count); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setLong(obj, tmp); + } + }; + } + if (type == Integer.class) { + return (final ResultSet rs, final Object obj) -> { + final Integer tmp = rs.getInt(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == int.class) { + return (final ResultSet rs, final Object obj) -> { + final Integer tmp = rs.getInt(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setInt(obj, tmp); + } + }; + } + if (type == Float.class) { + return (final ResultSet rs, final Object obj) -> { + final Float tmp = rs.getFloat(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == float.class) { + return (final ResultSet rs, final Object obj) -> { + final Float tmp = rs.getFloat(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setFloat(obj, tmp); + } + }; + } + if (type == Double.class) { + return (final ResultSet rs, final Object obj) -> { + final Double tmp = rs.getDouble(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == double.class) { + return (final ResultSet rs, final Object obj) -> { + final Double tmp = rs.getDouble(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setDouble(obj, tmp); + } + }; + } + if (type == Boolean.class) { + return (final ResultSet rs, final Object obj) -> { + final Boolean tmp = rs.getBoolean(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == boolean.class) { + return (final ResultSet rs, final Object obj) -> { + final Boolean tmp = rs.getBoolean(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setBoolean(obj, tmp); + } + }; + } + if (type == Timestamp.class) { + return (final ResultSet rs, final Object obj) -> { + final Timestamp tmp = rs.getTimestamp(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == Date.class) { + return (final ResultSet rs, final Object obj) -> { + try { + final Timestamp tmp = rs.getTimestamp(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, Date.from(tmp.toInstant())); + } + } catch (final SQLException ex) { + final String tmp = rs.getString(count); + LOGGER.error("Fail to parse the SQL time !!! {}", tmp); + if (rs.wasNull()) { + field.set(obj, null); + } else { + final Date date = DateTools.parseDate(tmp); + LOGGER.error("Fail to parse the SQL time !!! {}", date); + field.set(obj, date); + } + } + }; + } + if (type == Instant.class) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, Instant.parse(tmp)); + } + }; + } + if (type == LocalDate.class) { + return (final ResultSet rs, final Object obj) -> { + final java.sql.Date tmp = rs.getDate(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp.toLocalDate()); + } + }; + } + if (type == LocalTime.class) { + return (final ResultSet rs, final Object obj) -> { + final java.sql.Time tmp = rs.getTime(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp.toLocalTime()); + } + }; + } + if (type == String.class) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type.isEnum()) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + boolean find = false; + final Object[] arr = type.getEnumConstants(); + for (final Object elem : arr) { + if (elem.toString().equals(tmp)) { + field.set(obj, elem); + find = true; + break; + } + } + if (!find) { + throw new DataAccessException("Enum value does not exist in the Model: '" + tmp + "'"); + } + } + }; + } + throw new DataAccessException("Unknown Field Type"); + + } + + public boolean isAddOnField(final Field field) { + return findAddOnforField(field) != null; + } + + public DataAccessAddOn findAddOnforField(final Field field) { + for (final DataAccessAddOn elem : addOn) { + if (elem.isCompatibleField(field)) { + return elem; + } + } + return null; + } + + // TODO: manage insert batch... + public 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); + out.add(tmp); + } + return out; + } + + public long getNextSequenceLongValue(final String collectionName, String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + fieldName = "sequence_id"; + } + // Collection "counters" to store the sequences if Ids + final MongoCollection countersCollection = this.db.getDatastore().getDatabase() + .getCollection("counters"); + + // Filter to find the specific counter for the collections + final Document filter = new Document("_id", collectionName); + + // Update the field of 1 + final Document update = new Document("$inc", new Document(fieldName, 1L)); + + // get the value after updated it + final FindOneAndUpdateOptions options = new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER) + .upsert(true); // create field if not exist + + // Real creation of the unique counter. + final Document updatedCounter = countersCollection.findOneAndUpdate(filter, update, options); + + // Return the new sequence value... + return updatedCounter.getLong(fieldName); + } + + @SuppressWarnings("unchecked") + public T insert(final T data, final QueryOption... option) throws Exception { + final Class clazz = data.getClass(); + final QueryOptions options = new QueryOptions(option); + + // External checker of data: + final List checks = options.get(CheckFunction.class); + for (final CheckFunction check : checks) { + check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz), options); + } + + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final List asyncFieldUpdate = new ArrayList<>(); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + Field primaryKeyField = null; + Object uniqueId = null; + // real add in the BDD: + ObjectId insertedId = null; + try { + final MongoCollection collection = this.db.getDatastore().getDatabase() + .getCollection(collectionName); + final Document doc = new Document(); + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + final String tableFieldName = AnnotationTools.getFieldName(field); + final Object currentInsertValue = field.get(data); + if (AnnotationTools.isPrimaryKey(field)) { + primaryKeyField = field; + if (primaryKeyField.getType() == UUID.class) { + final UUID uuid = UuidUtils.nextUUID(); + uniqueId = uuid; + doc.append(tableFieldName, uuid); + continue; + } else if (primaryKeyField.getType() == Long.class || primaryKeyField.getType() == long.class) { + // By default the MongoDB does not manage the + final long id = getNextSequenceLongValue(collectionName, tableFieldName); + uniqueId = id; + doc.append(tableFieldName, id); + continue; + } + LOGGER.error("TODO: Manage the ID primary key for type: "); + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + if (addOn.isInsertAsync(field)) { + LOGGER.error("TODO: add async objects ..."); + //asyncFieldUpdate.add(field); + } + continue; + } + final boolean createTime = field.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0; + if (createTime) { + doc.append(tableFieldName, Date.from(Instant.now())); + continue; + } + final boolean updateTime = field.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0; + if (updateTime) { + doc.append(tableFieldName, Date.from(Instant.now())); + continue; + } + if (!field.getClass().isPrimitive()) { + if (currentInsertValue == null) { + final DefaultValue[] defaultValue = field.getDeclaredAnnotationsByType(DefaultValue.class); + LOGGER.error("TODO: convert default value in the correct value for the DB..."); + continue; + } + } + doc.append(tableFieldName, currentInsertValue); + } + final InsertOneResult result = collection.insertOne(doc); + // Get the Object of inserted object: + insertedId = result.getInsertedId().asObjectId().getValue(); + LOGGER.info("Document inserted with ID: " + insertedId); + + // Rechercher et récupérer le document inséré à partir de son ObjectId + final Document insertedDocument = collection.find(new Document("_id", insertedId)).first(); + + // Afficher le document récupéré + System.out.println("Inserted document: " + insertedDocument); + + } catch (final Exception ex) { + LOGGER.error("Fail SQL request: {}", ex.getMessage()); + ex.printStackTrace(); + throw new DataAccessException("Fail to Insert data in DB : " + ex.getMessage()); + } finally { + entry.close(); + } + final List asyncActions = new ArrayList<>(); + for (final Field field : asyncFieldUpdate) { + final DataAccessAddOn addOn = findAddOnforField(field); + if (uniqueId instanceof final Long id) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.asyncInsert(tableName, id, field, field.get(data), asyncActions); + } else if (uniqueId instanceof final UUID uuid) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.asyncInsert(tableName, uuid, field, field.get(data), asyncActions); + } + } + for (final LazyGetter action : asyncActions) { + action.doRequest(); + } + return (T) getWhere(clazz, new Condition(new QueryCondition("_id", "=", insertedId))); + } + + // seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated... + public T insertWithJson(final Class clazz, final String jsonData) throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + // parse the object to be sure the data are valid: + final T data = mapper.readValue(jsonData, clazz); + return insert(data); + } + + public QueryCondition getTableIdCondition(final Class clazz, final ID_TYPE idKey) + throws DataAccessException { + // Find the ID field type .... + final Field idField = AnnotationTools.getIdField(clazz); + if (idField == null) { + throw new DataAccessException( + "The class have no annotation @Id ==> can not determine the default type searching"); + } + // check the compatibility of the id and the declared ID + final Class typeClass = idField.getType(); + if (idKey == null) { + throw new DataAccessException("Try to identify the ID type and object wa null."); + } + if (idKey.getClass() != typeClass) { + if (idKey.getClass() == Condition.class) { + throw new DataAccessException( + "Try to identify the ID type on a condition 'close' internal API error use xxxWhere(...) instead."); + } + throw new DataAccessException("Request update with the wrong type ..."); + } + return new QueryCondition(AnnotationTools.getFieldName(idField), "=", idKey); + } + + /** Update an object with the inserted json data + * + * @param Type of the object to insert + * @param Master key on the object manage with @Id + * @param clazz Class reference of the insertion model + * @param id Key to insert data + * @param jsonData Json data (partial) values to update + * @return the number of object updated + * @throws Exception */ + public long updateWithJson( + final Class clazz, + final ID_TYPE id, + final String jsonData, + final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + options.add(new TransmitKey(id)); + return updateWhereWithJson(clazz, jsonData, options.getAllArray()); + } + + public long updateWhereWithJson(final Class clazz, final String jsonData, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + if (options.get(Condition.class).size() == 0) { + 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); + // Read the tree to filter injection of data: + final JsonNode root = mapper.readTree(jsonData); + final List keys = new ArrayList<>(); + final var iterator = root.fieldNames(); + iterator.forEachRemaining(e -> keys.add(e)); + options.add(new FilterValue(keys)); + return updateWhere(data, options.getAllArray()); + } + + public long update(final T data, final ID_TYPE id) throws Exception { + return update(data, id, AnnotationTools.getFieldsNames(data.getClass())); + } + + /** @param + * @param data + * @param id + * @param filterValue + * @return the affected rows. + * @throws Exception */ + public long update( + final T data, + final ID_TYPE id, + final List updateColomn, + final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(data.getClass(), id))); + options.add(new FilterValue(updateColomn)); + options.add(new TransmitKey(id)); + return updateWhere(data, options); + } + + public long updateWhere(final T data, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return updateWhere(data, options); + } + + public long updateWhere(final T data, QueryOptions options) throws Exception { + final Class clazz = data.getClass(); + if (options == null) { + options = new QueryOptions(); + } + final Condition condition = conditionFusionOrEmpty(options, true); + final List filterKeys = options != null ? options.get(FilterValue.class) : new ArrayList<>(); + if (filterKeys.size() != 1) { + throw new DataAccessException("request a gets without/or with more 1 filter of values"); + } + final FilterValue filterKey = filterKeys.get(0); + // External checker of data: + if (options != null) { + final List checks = options.get(CheckFunction.class); + for (final CheckFunction check : checks) { + check.getChecker().check("", data, filterKey.getValues(), options); + } + } + final List asyncActions = new ArrayList<>(); + + // real add in the BDD: + try { + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + final Document docSet = new Document(); + final Document docUnSet = new Document(); + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + final String name = AnnotationTools.getFieldName(field); + if (!filterKey.getValues().contains(name)) { + continue; + } else if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + if (addOn.isInsertAsync(field)) { + LOGGER.error("TODO: Add on not managed ... "); + /* + final List transmitKey = options.get(TransmitKey.class); + if (transmitKey.size() != 1) { + throw new DataAccessException( + "Fail to transmit Key to update the async update... (must have only 1)"); + } + addOn.asyncUpdate(tableName, transmitKey.get(0).getKey(), field, field.get(data), asyncActions); + */ + } + continue; + } + if (addOn != null) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.insertData(ps, field, data, iii); + } else { + final Class type = field.getType(); + if (!type.isPrimitive()) { + final Object tmp = field.get(data); + if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { + continue; + } + } + setValuedb(type, data, field, name, docSet, docUnSet); + /* + if (!field.getClass().isPrimitive()) { + final Object tmp = field.get(data); + if (tmp != null) { + docSet.append(name, tmp); + } else { + docUnSet.append(name, null); + } + } + */ + } + + } + // Do the query ... + + final MongoCollection collection = this.db.getDatastore().getDatabase() + .getCollection(collectionName); + final Document actions = new Document(); + if (!docSet.isEmpty()) { + actions.append("$set", docSet); + } + if (!docUnSet.isEmpty()) { + actions.append("$unset", docUnSet); + } + LOGGER.info("update some values: {}", actions.toJson()); + final UpdateResult ret = collection.updateMany(filters, actions); + return ret.getModifiedCount(); + } catch (final SQLException ex) { + ex.printStackTrace(); + } + for (final LazyGetter action : asyncActions) { + action.doRequest(); + } + return 0; + } + + public void addElement(final PreparedStatement ps, final Object value, final CountInOut iii) throws Exception { + if (value instanceof final UUID tmp) { + final byte[] dataByte = UuidUtils.asBytes(tmp); + ps.setBytes(iii.value, dataByte); + } else 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 ..."); + } + } + + public int executeSimpleQuery(final String query, final QueryOption... option) throws SQLException, IOException { + final QueryOptions options = new QueryOptions(option); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + LOGGER.info("Query : '{}'", query); + try (final Statement stmt = entry.connection.createStatement()) { + return stmt.executeUpdate(query); + } + } + + public boolean executeQuery(final String query, final QueryOption... option) throws SQLException, IOException { + final QueryOptions options = new QueryOptions(option); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + try (final Statement stmt = entry.connection.createStatement()) { + return stmt.execute(query); + } + } + + public T getWhere(final Class clazz, final QueryOptions options) throws Exception { + options.add(new Limit(1)); + final List values = getsWhere(clazz, options); + if (values.size() == 0) { + return null; + } + return values.get(0); + } + + public T getWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return getWhere(clazz, options); + } + + // This must be refactored to manage the .project of Morphia. + public void generateSelectField(// + final StringBuilder querySelect, // + final StringBuilder query, // + final Class clazz, // + final QueryOptions options, // + final CountInOut count// + ) throws Exception { + /* + final boolean readAllfields = QueryOptions.readAllColomn(options); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final String primaryKey = AnnotationTools.getPrimaryKeyField(clazz).getName(); + boolean firstField = true; + + for (final Field elem : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(elem); + if (addOn != null && !addOn.canRetrieve(elem)) { + continue; + } + final boolean notRead = AnnotationTools.isdefaultNotRead(elem); + if (!readAllfields && notRead) { + continue; + } + final String name = AnnotationTools.getFieldName(elem); + if (firstField) { + firstField = false; + } else { + querySelect.append(","); + } + querySelect.append(" "); + if (addOn != null) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.generateQuery(tableName, primaryKey, elem, querySelect, query, name, count, options); + } else { + querySelect.append(tableName); + querySelect.append("."); + querySelect.append(name); + count.inc(); + } + } + */ + } + + public List getsWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return getsWhere(clazz, options); + } + + public Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty) + throws DataAccessException { + if (options == null) { + return new Condition(); + } + final List conditions = options.get(Condition.class); + if (conditions.size() == 0) { + if (throwIfEmpty) { + throw new DataAccessException("request a gets without any condition"); + } else { + return new Condition(); + } + } + Condition condition = null; + if (conditions.size() == 1) { + condition = conditions.get(0); + } else { + final QueryAnd andCondition = new QueryAnd(); + for (final Condition cond : conditions) { + andCondition.add(cond.condition); + } + condition = new Condition(andCondition); + } + return condition; + } + + @SuppressWarnings("unchecked") + public List getsWhere(final Class clazz, final QueryOptions options) + throws DataAccessException, IOException { + + final Condition condition = conditionFusionOrEmpty(options, false); + final List lazyCall = new ArrayList<>(); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final List outs = new ArrayList<>(); + final MongoCollection collection = this.db.getDatastore().getDatabase().getCollection(collectionName); + try { + final CountInOut count = new CountInOut(); + // Select values to read + //generateSelectField(querySelect, query, clazz, options, count); + // Generate the filtering of the data: + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + FindIterable retFind = null; + if (filters != null) { + LOGGER.info("getsWhere Find filter: {}", filters.toBsonDocument().toJson()); + retFind = collection.find(filters); + } else { + retFind = collection.find(); + } + /* Not manage right now ... + final List groups = options.get(GroupBy.class); + for (final GroupBy group : groups) { + group.generateQuery(query, tableName); + } + */ + final List orders = options.get(OrderBy.class); + if (orders.size() != 0) { + final Document sorts = new Document(); + for (final OrderBy order : orders) { + order.generateSort(sorts); + } + retFind = retFind.sort(sorts); + } + + final List limits = options.get(Limit.class); + if (limits.size() == 1) { + retFind = retFind.limit((int) limits.get(0).getValue()); + } else if (limits.size() > 1) { + throw new DataAccessException("Request with multiple 'limit'..."); + } + + final MongoCursor cursor = retFind.iterator(); + try (cursor) { + while (cursor.hasNext()) { + final Document doc = cursor.next(); + count.value = 1; + final CountInOut countNotNull = new CountInOut(0); + System.out.println(doc.toJson()); // Affichage du document en format JSON + final Object data = createObjectFromDocument(doc, clazz, count, countNotNull, options, lazyCall); + final T out = (T) data; + outs.add(out); + } + LOGGER.info("Async calls: {}", lazyCall.size()); + for (final LazyGetter elem : lazyCall) { + elem.doRequest(); + } + } + } catch (final SQLException ex) { + ex.printStackTrace(); + throw new DataAccessException("Catch a SQL Exception: " + ex.getMessage()); + } catch (final Exception ex) { + ex.printStackTrace(); + throw new DataAccessException("Catch an Exception: " + ex.getMessage()); + } + return outs; + } + + public Object createObjectFromDocument( + final Document doc, + final Class clazz, + final CountInOut count, + final CountInOut countNotNull, + final QueryOptions options, + final List lazyCall) throws Exception { + final boolean readAllfields = QueryOptions.readAllColomn(options); + // TODO: manage class that is defined inside a class ==> Not manage for now... + Object data = null; + for (final Constructor contructor : clazz.getConstructors()) { + if (contructor.getParameterCount() == 0) { + data = contructor.newInstance(); + break; + } + } + if (data == null) { + throw new DataAccessException( + "Can not find the default constructor for the class: " + clazz.getCanonicalName()); + } + for (final Field elem : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(elem); + if (addOn != null && !addOn.canRetrieve(elem)) { + continue; + } + final boolean notRead = AnnotationTools.isdefaultNotRead(elem); + if (!readAllfields && notRead) { + continue; + } + if (addOn != null) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.fillFromDoc(doc, elem, data, count, options, lazyCall); + //addOn.fillFromQuery(rs, elem, data, count, options, lazyCall); + } else { + setValueFromDoc(elem.getType(), data, count, elem, doc, countNotNull); + //setValueFromDb(elem.getType(), data, count, elem, rs, countNotNull); + } + } + return data; + } + + public long count(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return this.countWhere(clazz, options); + } + + public long countWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return countWhere(clazz, options); + } + + public long countWhere(final Class clazz, final QueryOptions options) throws Exception { + final Condition condition = conditionFusionOrEmpty(options, false); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final MongoCollection collection = this.db.getDatastore().getDatabase().getCollection(collectionName); + try { + // Generate the filtering of the data: + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + if (filters != null) { + return collection.countDocuments(filters); + } + return collection.countDocuments(); + } catch (final Exception ex) { + ex.printStackTrace(); + throw new DataAccessException("Catch an Exception: " + ex.getMessage()); + } + } + + public T get(final Class clazz, final ID_TYPE id, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return this.getWhere(clazz, options.getAllArray()); + } + + public List gets(final Class clazz) throws Exception { + return getsWhere(clazz); + } + + public List gets(final Class clazz, final QueryOption... option) throws Exception { + return getsWhere(clazz, option); + } + + /** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). + * @param Type of the reference @Id + * @param clazz Data model that might remove element + * @param id Unique Id of the model + * @param options (Optional) Options of the request + * @return Number of element that is removed. */ + public long delete(final Class clazz, final ID_TYPE id, final QueryOption... options) + throws Exception { + final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (hasDeletedFieldName != null) { + return deleteSoft(clazz, id, options); + } else { + return deleteHard(clazz, id, options); + } + } + + /** Delete items with the specific condition and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). + * @param clazz Data model that might remove element. + * @param condition Condition to remove elements. + * @param options (Optional) Options of the request. + * @return Number of element that is removed. */ + public long deleteWhere(final Class clazz, final QueryOption... option) throws Exception { + final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (hasDeletedFieldName != null) { + return deleteSoftWhere(clazz, option); + } else { + return deleteHardWhere(clazz, option); + } + } + + public long deleteHard(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return deleteHardWhere(clazz, options.getAllArray()); + } + + public long deleteHardWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final MongoCollection collection = this.db.getDatastore().getDatabase().getCollection(collectionName); + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + DeleteResult retFind; + if (filters != null) { + retFind = collection.deleteMany(filters); + } else { + throw new DataAccessException("Too dangerout to delete element with no filter values !!!"); + } + return retFind.getDeletedCount(); + } + + private long deleteSoft(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return deleteSoftWhere(clazz, options.getAllArray()); + } + + public long deleteSoftWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final MongoCollection collection = this.db.getDatastore().getDatabase().getCollection(collectionName); + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + final Document actions = new Document("$set", new Document(deletedFieldName, true)); + LOGGER.info("update some values: {}", actions.toJson()); + final UpdateResult ret = collection.updateMany(filters, actions); + return ret.getModifiedCount(); + } + + public long unsetDelete(final Class clazz, final ID_TYPE id) throws DataAccessException { + return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id))); + } + + public long unsetDelete(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws DataAccessException { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return unsetDeleteWhere(clazz, options.getAllArray()); + } + + public long unsetDeleteWhere(final Class clazz, final QueryOption... option) throws DataAccessException { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (deletedFieldName == null) { + throw new DataAccessException("The class " + clazz.getCanonicalName() + " has no deleted field"); + } + final MongoCollection collection = this.db.getDatastore().getDatabase().getCollection(collectionName); + final Bson filters = condition.getFilter(collectionName, options, deletedFieldName); + final Document actions = new Document("$set", new Document(deletedFieldName, false)); + LOGGER.info("update some values: {}", actions.toJson()); + final UpdateResult ret = collection.updateMany(filters, actions); + return ret.getModifiedCount(); + } + + public void drop(final Class clazz, final QueryOption... option) throws Exception { + /* + final QueryOptions options = new QueryOptions(option); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("DROP TABLE IF EXISTS `"); + query.append(tableName); + query.append("`"); + try { + LOGGER.trace("Execute Query: {}", query.toString()); + // Remove main table + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + ps.executeUpdate(); + // search subTable: + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.drop(tableName, field); + } + } + } finally { + entry.close(); + } + */ + } + + public void cleanAll(final Class clazz, final QueryOption... option) throws Exception { + /* + final QueryOptions options = new QueryOptions(option); + final String collectionName = AnnotationTools.getCollectionName(clazz, options); + DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("DELETE FROM `"); + query.append(tableName); + query.append("`"); + try { + LOGGER.trace("Execute Query: {}", query.toString()); + // Remove main table + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + ps.executeUpdate(); + // search subTable: + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + LOGGER.error("TODO: Add on not managed ... "); + //addOn.cleanAll(tableName, field); + } + } + } finally { + entry.close(); + entry = null; + } + */ + } +} diff --git a/src/org/kar/archidata/dataAccess/DataAccessSQL.java b/src/org/kar/archidata/dataAccess/DataAccessSQL.java new file mode 100644 index 0000000..04ce234 --- /dev/null +++ b/src/org/kar/archidata/dataAccess/DataAccessSQL.java @@ -0,0 +1,1904 @@ +package org.kar.archidata.dataAccess; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.kar.archidata.annotation.AnnotationTools; +import org.kar.archidata.annotation.CreationTimestamp; +import org.kar.archidata.annotation.UpdateTimestamp; +import org.kar.archidata.dataAccess.addOn.AddOnDataJson; +import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; +import org.kar.archidata.dataAccess.addOn.AddOnManyToOne; +import org.kar.archidata.dataAccess.addOn.AddOnOneToMany; +import org.kar.archidata.dataAccess.options.CheckFunction; +import org.kar.archidata.dataAccess.options.Condition; +import org.kar.archidata.dataAccess.options.DBInterfaceOption; +import org.kar.archidata.dataAccess.options.DBInterfaceRoot; +import org.kar.archidata.dataAccess.options.FilterValue; +import org.kar.archidata.dataAccess.options.GroupBy; +import org.kar.archidata.dataAccess.options.Limit; +import org.kar.archidata.dataAccess.options.OrderBy; +import org.kar.archidata.dataAccess.options.QueryOption; +import org.kar.archidata.dataAccess.options.TransmitKey; +import org.kar.archidata.db.DBEntry; +import org.kar.archidata.exception.DataAccessException; +import org.kar.archidata.tools.ConfigBaseVariable; +import org.kar.archidata.tools.DateTools; +import org.kar.archidata.tools.UuidUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.InternalServerErrorException; + +/* TODO list: + - Manage to group of SQL action to permit to commit only at the end. + */ + +/** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite + * back-end. */ +public class DataAccessSQL extends DataAccess { + static final Logger LOGGER = LoggerFactory.getLogger(DataAccessSQL.class); + // by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...) + static final List addOn = new ArrayList<>(); + + static { + addOn.add(new AddOnManyToMany()); + addOn.add(new AddOnManyToOne()); + addOn.add(new AddOnOneToMany()); + addOn.add(new AddOnDataJson()); + } + + /** Add a new add-on on the current management. + * @param addOn instantiate object on the Add-on */ + public static void addAddOn(final DataAccessAddOn addOn) { + DataAccessSQL.addOn.add(addOn); + } + + public DataAccessSQL() { + + } + + public static boolean isDBExist(final String name, final QueryOption... option) + throws InternalServerErrorException { + final QueryOptions options = new QueryOptions(option); + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + // no base manage in sqLite ... + // TODO: check if the file exist or not ... + return true; + } + DBEntry entry; + try { + entry = DBInterfaceOption.getAutoEntry(options); + } catch (final IOException ex) { + ex.printStackTrace(); + LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); + + // TODO: TO test + + return false; + } + try { + // TODO : Maybe connect with a temporary not specified connection interface to a db ... + final PreparedStatement ps = entry.connection.prepareStatement("show databases"); + final ResultSet rs = ps.executeQuery(); + // LOGGER.info("List all tables: equals? '{}'", name); + while (rs.next()) { + final String data = rs.getString(1); + // LOGGER.info(" - '{}'", data); + if (name.equals(data)) { + return true; + } + } + return false; + } catch (final SQLException ex) { + LOGGER.error("Can not check if the DB exist SQL-error !!! {}", ex.getMessage()); + } finally { + try { + entry.close(); + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + entry = null; + } + throw new InternalServerErrorException("Can Not manage the DB-access"); + } + + public static boolean createDB(final String name) { + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + // no base manage in sqLite ... + // TODO: check if the file exist or not ... + return true; + } + try { + return 1 == DataAccessSQL.executeSimpleQuery("CREATE DATABASE `" + name + "`;", new DBInterfaceRoot(true)); + } catch (final SQLException | IOException ex) { + ex.printStackTrace(); + LOGGER.error("Can not check if the DB exist!!! {}", ex.getMessage()); + return false; + } + } + + public static boolean isTableExist(final String name, final QueryOption... option) + throws InternalServerErrorException { + final QueryOptions options = new QueryOptions(option); + try { + String request = ""; + if ("sqlite".equals(ConfigBaseVariable.getDBType())) { + request = """ + SELECT COUNT(*) AS total + FROM sqlite_master + WHERE type = 'table' + AND name = ?; + """; + // PreparedStatement ps = entry.connection.prepareStatement("show tables"); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final PreparedStatement ps = entry.connection.prepareStatement(request); + ps.setString(1, name); + final ResultSet ret = ps.executeQuery(); + final int count = ret.getInt("total"); + return count == 1; + } else { + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + // TODO : Maybe connect with a temporary not specified connection interface to a db ... + final PreparedStatement ps = entry.connection.prepareStatement("show tables"); + final ResultSet rs = ps.executeQuery(); + // LOGGER.info("List all tables: equals? '{}'", name); + while (rs.next()) { + final String data = rs.getString(1); + // LOGGER.info(" - '{}'", data); + if (name.equals(data)) { + return true; + } + } + return false; + } + } catch (final SQLException ex) { + LOGGER.error("Can not check if the table exist SQL-error !!! {}", ex.getMessage()); + } catch (final IOException ex) { + LOGGER.error("Can not check if the table exist!!! {}", ex.getMessage()); + } + throw new InternalServerErrorException("Can Not manage the DB-access"); + } + + /** Extract a list of Long with "-" separated element from a SQL input data. + * @param rs Result Set of the BDD + * @param iii Id in the result set + * @return The list of Long value + * @throws SQLException if an error is generated in the SQL request. */ + public static List getListOfIds(final ResultSet rs, final int iii, final String separator) + throws SQLException { + final String trackString = rs.getString(iii); + if (rs.wasNull()) { + return null; + } + final List out = new ArrayList<>(); + final String[] elements = trackString.split(separator); + for (final String elem : elements) { + final Long tmp = Long.parseLong(elem); + out.add(tmp); + } + return out; + } + + /** Extract a list of UUID with "-" separated element from a SQL input data. + * @param rs Result Set of the BDD + * @param iii Id in the result set + * @return The list of Long value + * @throws SQLException if an error is generated in the SQL request. */ + public static List getListOfUUIDs(final ResultSet rs, final int iii, final String separator) + throws SQLException { + final String trackString = rs.getString(iii); + if (rs.wasNull()) { + return null; + } + final List out = new ArrayList<>(); + final String[] elements = trackString.split(separator); + for (final String elem : elements) { + final UUID tmp = UUID.fromString(elem); + out.add(tmp); + } + return out; + } + + public static byte[][] splitIntoGroupsOf16Bytes(final byte[] input) { + final int inputLength = input.length; + final int numOfGroups = (inputLength + 15) / 16; // Calculate the number of groups needed + final byte[][] groups = new byte[numOfGroups][16]; + + for (int i = 0; i < numOfGroups; i++) { + final int startIndex = i * 16; + final int endIndex = Math.min(startIndex + 16, inputLength); + groups[i] = Arrays.copyOfRange(input, startIndex, endIndex); + } + + return groups; + } + + public static List getListOfRawUUIDs(final ResultSet rs, final int iii) + throws SQLException, DataAccessException { + final byte[] trackString = rs.getBytes(iii); + if (rs.wasNull()) { + return null; + } + final byte[][] elements = splitIntoGroupsOf16Bytes(trackString); + final List out = new ArrayList<>(); + for (final byte[] elem : elements) { + final UUID tmp = UuidUtils.asUuid(elem); + out.add(tmp); + } + return out; + } + + public static UUID getListOfRawUUID(final ResultSet rs, final int iii) throws SQLException, DataAccessException { + final byte[] elem = rs.getBytes(iii); + if (rs.wasNull()) { + return null; + } + return UuidUtils.asUuid(elem); + } + + protected static void setValuedb( + final Class type, + final T data, + final CountInOut iii, + final Field field, + final PreparedStatement ps) throws Exception { + if (type == UUID.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.BINARY); + } else { + final byte[] dataByte = UuidUtils.asBytes((UUID) tmp); + ps.setBytes(iii.value, dataByte); + } + } else if (type == Long.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.BIGINT); + } else { + ps.setLong(iii.value, (Long) tmp); + } + } else if (type == long.class) { + ps.setLong(iii.value, field.getLong(data)); + } else if (type == Integer.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + ps.setInt(iii.value, (Integer) tmp); + } + } else if (type == int.class) { + ps.setInt(iii.value, field.getInt(data)); + } else if (type == Float.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.FLOAT); + } else { + ps.setFloat(iii.value, (Float) tmp); + } + } else if (type == float.class) { + ps.setFloat(iii.value, field.getFloat(data)); + } else if (type == Double.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.DOUBLE); + } else { + ps.setDouble(iii.value, (Double) tmp); + } + } else if (type == Double.class) { + ps.setDouble(iii.value, field.getDouble(data)); + } else if (type == Boolean.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + ps.setBoolean(iii.value, (Boolean) tmp); + } + } else if (type == boolean.class) { + ps.setBoolean(iii.value, field.getBoolean(data)); + } else if (type == Timestamp.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + ps.setTimestamp(iii.value, (Timestamp) tmp); + } + } else if (type == Date.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant()); + ps.setTimestamp(iii.value, sqlDate); + } + } else if (type == Instant.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final String sqlDate = ((Instant) tmp).toString(); + ps.setString(iii.value, sqlDate); + } + } else if (type == LocalDate.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final java.sql.Date sqlDate = java.sql.Date.valueOf((LocalDate) tmp); + ps.setDate(iii.value, sqlDate); + } + } else if (type == LocalTime.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.INTEGER); + } else { + final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp); + ps.setTime(iii.value, sqlDate); + } + } else if (type == String.class) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.VARCHAR); + } else { + ps.setString(iii.value, (String) tmp); + } + } else if (type.isEnum()) { + final Object tmp = field.get(data); + if (tmp == null) { + ps.setNull(iii.value, Types.VARCHAR); + } else { + ps.setString(iii.value, tmp.toString()); + } + } else { + throw new DataAccessException("Unknown Field Type"); + } + iii.inc(); + } + + protected static void setValueFromDb( + final Class type, + final Object data, + final CountInOut count, + final Field field, + final ResultSet rs, + final CountInOut countNotNull) throws Exception { + if (type == UUID.class) { + final byte[] tmp = rs.getBytes(count.value); + // final UUID tmp = rs.getObject(count.value, UUID.class); + if (rs.wasNull()) { + field.set(data, null); + } else { + // field.set(data, tmp); + final UUID uuid = UuidUtils.asUuid(tmp); + field.set(data, uuid); + countNotNull.inc(); + } + } else if (type == Long.class) { + final Long tmp = rs.getLong(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == long.class) { + final Long tmp = rs.getLong(count.value); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setLong(data, tmp); + countNotNull.inc(); + } + } else if (type == Integer.class) { + final Integer tmp = rs.getInt(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == int.class) { + final Integer tmp = rs.getInt(count.value); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setInt(data, tmp); + countNotNull.inc(); + } + } else if (type == Float.class) { + final Float tmp = rs.getFloat(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == float.class) { + final Float tmp = rs.getFloat(count.value); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setFloat(data, tmp); + countNotNull.inc(); + } + } else if (type == Double.class) { + final Double tmp = rs.getDouble(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == double.class) { + final Double tmp = rs.getDouble(count.value); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setDouble(data, tmp); + countNotNull.inc(); + } + } else if (type == Boolean.class) { + final Boolean tmp = rs.getBoolean(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == boolean.class) { + final Boolean tmp = rs.getBoolean(count.value); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setBoolean(data, tmp); + countNotNull.inc(); + } + } else if (type == Timestamp.class) { + final Timestamp tmp = rs.getTimestamp(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type == Date.class) { + try { + final Timestamp tmp = rs.getTimestamp(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, Date.from(tmp.toInstant())); + countNotNull.inc(); + } + } catch (final SQLException ex) { + final String tmp = rs.getString(count.value); + LOGGER.error("Fail to parse the SQL time !!! {}", tmp); + if (rs.wasNull()) { + field.set(data, null); + } else { + final Date date = DateTools.parseDate(tmp); + LOGGER.error("Fail to parse the SQL time !!! {}", date); + field.set(data, date); + countNotNull.inc(); + } + } + } else if (type == Instant.class) { + final String tmp = rs.getString(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, Instant.parse(tmp)); + countNotNull.inc(); + } + } else if (type == LocalDate.class) { + final java.sql.Date tmp = rs.getDate(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp.toLocalDate()); + countNotNull.inc(); + } + } else if (type == LocalTime.class) { + final java.sql.Time tmp = rs.getTime(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp.toLocalTime()); + countNotNull.inc(); + } + } else if (type == String.class) { + final String tmp = rs.getString(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + field.set(data, tmp); + countNotNull.inc(); + } + } else if (type.isEnum()) { + final String tmp = rs.getString(count.value); + if (rs.wasNull()) { + field.set(data, null); + } else { + boolean find = false; + final Object[] arr = type.getEnumConstants(); + for (final Object elem : arr) { + if (elem.toString().equals(tmp)) { + field.set(data, elem); + countNotNull.inc(); + find = true; + break; + } + } + if (!find) { + throw new DataAccessException("Enum value does not exist in the Model: '" + tmp + "'"); + } + } + } else { + throw new DataAccessException("Unknown Field Type"); + } + count.inc(); + } + + // TODO: this function will replace the previous one !!! + protected static RetreiveFromDB createSetValueFromDbCallback(final int count, final Field field) throws Exception { + final Class type = field.getType(); + if (type == UUID.class) { + return (final ResultSet rs, final Object obj) -> { + + final byte[] tmp = rs.getBytes(count); + // final UUID tmp = rs.getObject(count, UUID.class); + if (rs.wasNull()) { + field.set(obj, null); + } else { + // field.set(obj, tmp); + final UUID uuid = UuidUtils.asUuid(tmp); + field.set(obj, uuid); + } + }; + } + if (type == Long.class) { + return (final ResultSet rs, final Object obj) -> { + final Long tmp = rs.getLong(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == long.class) { + return (final ResultSet rs, final Object obj) -> { + final Long tmp = rs.getLong(count); + if (rs.wasNull()) { + // field.set(data, null); + } else { + field.setLong(obj, tmp); + } + }; + } + if (type == Integer.class) { + return (final ResultSet rs, final Object obj) -> { + final Integer tmp = rs.getInt(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == int.class) { + return (final ResultSet rs, final Object obj) -> { + final Integer tmp = rs.getInt(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setInt(obj, tmp); + } + }; + } + if (type == Float.class) { + return (final ResultSet rs, final Object obj) -> { + final Float tmp = rs.getFloat(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == float.class) { + return (final ResultSet rs, final Object obj) -> { + final Float tmp = rs.getFloat(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setFloat(obj, tmp); + } + }; + } + if (type == Double.class) { + return (final ResultSet rs, final Object obj) -> { + final Double tmp = rs.getDouble(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == double.class) { + return (final ResultSet rs, final Object obj) -> { + final Double tmp = rs.getDouble(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setDouble(obj, tmp); + } + }; + } + if (type == Boolean.class) { + return (final ResultSet rs, final Object obj) -> { + final Boolean tmp = rs.getBoolean(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == boolean.class) { + return (final ResultSet rs, final Object obj) -> { + final Boolean tmp = rs.getBoolean(count); + if (rs.wasNull()) { + // field.set(obj, null); + } else { + field.setBoolean(obj, tmp); + } + }; + } + if (type == Timestamp.class) { + return (final ResultSet rs, final Object obj) -> { + final Timestamp tmp = rs.getTimestamp(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type == Date.class) { + return (final ResultSet rs, final Object obj) -> { + try { + final Timestamp tmp = rs.getTimestamp(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, Date.from(tmp.toInstant())); + } + } catch (final SQLException ex) { + final String tmp = rs.getString(count); + LOGGER.error("Fail to parse the SQL time !!! {}", tmp); + if (rs.wasNull()) { + field.set(obj, null); + } else { + final Date date = DateTools.parseDate(tmp); + LOGGER.error("Fail to parse the SQL time !!! {}", date); + field.set(obj, date); + } + } + }; + } + if (type == Instant.class) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, Instant.parse(tmp)); + } + }; + } + if (type == LocalDate.class) { + return (final ResultSet rs, final Object obj) -> { + final java.sql.Date tmp = rs.getDate(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp.toLocalDate()); + } + }; + } + if (type == LocalTime.class) { + return (final ResultSet rs, final Object obj) -> { + final java.sql.Time tmp = rs.getTime(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp.toLocalTime()); + } + }; + } + if (type == String.class) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + field.set(obj, tmp); + } + }; + } + if (type.isEnum()) { + return (final ResultSet rs, final Object obj) -> { + final String tmp = rs.getString(count); + if (rs.wasNull()) { + field.set(obj, null); + } else { + boolean find = false; + final Object[] arr = type.getEnumConstants(); + for (final Object elem : arr) { + if (elem.toString().equals(tmp)) { + field.set(obj, elem); + find = true; + break; + } + } + if (!find) { + throw new DataAccessException("Enum value does not exist in the Model: '" + tmp + "'"); + } + } + }; + } + throw new DataAccessException("Unknown Field Type"); + + } + + public static boolean isAddOnField(final Field field) { + return findAddOnforField(field) != null; + } + + public static DataAccessAddOn findAddOnforField(final Field field) { + for (final DataAccessAddOn elem : addOn) { + if (elem.isCompatibleField(field)) { + return elem; + } + } + return null; + } + + // TODO: manage insert batch... + 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); + out.add(tmp); + } + return out; + } + + @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") + public static T insert(final T data, final QueryOption... option) throws Exception { + final Class clazz = data.getClass(); + final QueryOptions options = new QueryOptions(option); + + // External checker of data: + final List checks = options.get(CheckFunction.class); + for (final CheckFunction check : checks) { + check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz), options); + } + + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final List asyncFieldUpdate = new ArrayList<>(); + Long uniqueSQLID = null; + UUID uniqueSQLUUID = null; + final String tableName = AnnotationTools.getTableName(clazz, options); + Field primaryKeyField = null; + boolean generateUUID = false; + // real add in the BDD: + try { + // boolean createIfNotExist = clazz.getDeclaredAnnotationsByType(SQLIfNotExists.class).length != 0; + final StringBuilder query = new StringBuilder(); + query.append("INSERT INTO `"); + query.append(tableName); + query.append("` ("); + + boolean firstField = true; + int count = 0; + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isPrimaryKey(field)) { + primaryKeyField = field; + if (primaryKeyField.getType() != UUID.class) { + break; + } + generateUUID = true; + count++; + final String name = AnnotationTools.getFieldName(field); + if (firstField) { + firstField = false; + } else { + query.append(","); + } + query.append(" `"); + query.append(name); + query.append("`"); + break; + } + } + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isPrimaryKey(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + if (addOn.isInsertAsync(field)) { + asyncFieldUpdate.add(field); + } + continue; + } + final boolean createTime = field.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0; + if (createTime) { + continue; + } + final boolean updateTime = field.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0; + if (updateTime) { + continue; + } + if (!field.getClass().isPrimitive()) { + final Object tmp = field.get(data); + if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { + continue; + } + } + count++; + final String name = AnnotationTools.getFieldName(field); + if (firstField) { + firstField = false; + } else { + query.append(","); + } + query.append(" `"); + query.append(name); + query.append("`"); + } + firstField = true; + query.append(") VALUES ("); + for (int iii = 0; iii < count; iii++) { + if (firstField) { + firstField = false; + } else { + query.append(","); + } + query.append("?"); + } + query.append(")"); + final List orders = options.get(OrderBy.class); + for (final OrderBy order : orders) { + order.generateQuery(query, tableName); + } + LOGGER.debug("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); + UUID uuid = null; + if (generateUUID) { + firstField = false; + // uuid = UUID.randomUUID(); + uuid = UuidUtils.nextUUID(); + addElement(ps, uuid, iii); + iii.inc(); + } + for (final Field elem : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { + continue; + } + if (AnnotationTools.isPrimaryKey(elem)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(elem); + if (addOn != null && !addOn.canInsert(elem)) { + continue; + } + final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0; + if (createTime) { + continue; + } + final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0; + if (updateTime) { + continue; + } + if (addOn != null) { + // Add-on specific insertion. + addOn.insertData(ps, elem, data, iii); + } else { + // Generic class insertion... + final Class type = elem.getType(); + if (!type.isPrimitive()) { + final Object tmp = elem.get(data); + if (tmp == null && elem.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { + continue; + } + } + setValuedb(type, data, iii, elem, ps); + } + count++; + } + // execute the request + final int affectedRows = ps.executeUpdate(); + if (affectedRows == 0) { + throw new SQLException("Creating node failed, no rows affected."); + } + // Retrieve uid inserted + if (generateUUID) { + // we generate the UUID, otherwise we can not retrieve it + uniqueSQLUUID = uuid; + } else { + try (ResultSet generatedKeys = ps.getGeneratedKeys()) { + if (generatedKeys.next()) { + if (primaryKeyField.getType() == UUID.class) { + // uniqueSQLUUID = generatedKeys.getObject(1, UUID.class); + /* final Object obj = generatedKeys.getObject(1); final BigInteger bigint = (BigInteger) generatedKeys.getObject(1); uniqueSQLUUID = UuidUtils.asUuid(bigint); final UUID + * generatedUUID = (UUID) generatedKeys.getObject(1); System.out.println("UUID généré: " + generatedUUID); */ + //final Object obj = generatedKeys.getObject(1); + final byte[] tmpid = generatedKeys.getBytes(1); + uniqueSQLUUID = UuidUtils.asUuid(tmpid); + } else { + uniqueSQLID = generatedKeys.getLong(1); + } + } else { + throw new SQLException("Creating node failed, no ID obtained (1)."); + } + } catch (final Exception ex) { + LOGGER.error("Can not get the UID key inserted ... "); + ex.printStackTrace(); + throw new SQLException("Creating node failed, no ID obtained (2)."); + } + } + ps.close(); + if (primaryKeyField != null) { + if (primaryKeyField.getType() == Long.class) { + primaryKeyField.set(data, uniqueSQLID); + } else if (primaryKeyField.getType() == long.class) { + primaryKeyField.setLong(data, uniqueSQLID); + } else if (primaryKeyField.getType() == UUID.class) { + primaryKeyField.set(data, uniqueSQLUUID); + } else { + LOGGER.error("Can not manage the primary filed !!!"); + } + } + // ps.execute(); + } catch (final SQLException ex) { + LOGGER.error("Fail SQL request: {}", ex.getMessage()); + ex.printStackTrace(); + throw new DataAccessException("Fail to Insert data in DB : " + ex.getMessage()); + } finally { + entry.close(); + } + final List asyncActions = new ArrayList<>(); + for (final Field field : asyncFieldUpdate) { + final DataAccessAddOn addOn = findAddOnforField(field); + if (uniqueSQLID != null) { + addOn.asyncInsert(tableName, uniqueSQLID, field, field.get(data), asyncActions); + } else if (uniqueSQLUUID != null) { + addOn.asyncInsert(tableName, uniqueSQLUUID, field, field.get(data), asyncActions); + } + } + for (final LazyGetter action : asyncActions) { + action.doRequest(); + } + return data; + } + + // seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated... + public static T insertWithJson(final Class clazz, final String jsonData) throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + // parse the object to be sure the data are valid: + final T data = mapper.readValue(jsonData, clazz); + return insert(data); + } + + public static QueryCondition getTableIdCondition(final Class clazz, final ID_TYPE idKey) + throws DataAccessException { + // Find the ID field type .... + final Field idField = AnnotationTools.getIdField(clazz); + if (idField == null) { + throw new DataAccessException( + "The class have no annotation @Id ==> can not determine the default type searching"); + } + // check the compatibility of the id and the declared ID + final Class typeClass = idField.getType(); + if (idKey == null) { + throw new DataAccessException("Try to identify the ID type and object wa null."); + } + if (idKey.getClass() != typeClass) { + if (idKey.getClass() == Condition.class) { + throw new DataAccessException( + "Try to identify the ID type on a condition 'close' internal API error use xxxWhere(...) instead."); + } + throw new DataAccessException("Request update with the wrong type ..."); + } + return new QueryCondition(AnnotationTools.getFieldName(idField), "=", idKey); + } + + /** Update an object with the inserted json data + * + * @param Type of the object to insert + * @param Master key on the object manage with @Id + * @param clazz Class reference of the insertion model + * @param id Key to insert data + * @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 QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + options.add(new TransmitKey(id)); + return updateWhereWithJson(clazz, jsonData, options.getAllArray()); + } + + public static int updateWhereWithJson(final Class clazz, final String jsonData, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + if (options.get(Condition.class).size() == 0) { + 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); + // Read the tree to filter injection of data: + final JsonNode root = mapper.readTree(jsonData); + final List keys = new ArrayList<>(); + final var iterator = root.fieldNames(); + iterator.forEachRemaining(e -> keys.add(e)); + 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, AnnotationTools.getFieldsNames(data.getClass())); + } + + /** @param + * @param data + * @param id + * @param filterValue + * @return the affected rows. + * @throws Exception */ + public static int update( + final T data, + final ID_TYPE id, + final List updateColomn, + final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(data.getClass(), id))); + options.add(new FilterValue(updateColomn)); + options.add(new TransmitKey(id)); + return updateWhere(data, options); + } + + public static int updateWhere(final T data, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return updateWhere(data, options); + } + + @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") + public static int updateWhere(final T data, QueryOptions options) throws Exception { + final Class clazz = data.getClass(); + if (options == null) { + options = new QueryOptions(); + } + final Condition condition = conditionFusionOrEmpty(options, true); + final List filters = options != null ? options.get(FilterValue.class) : new ArrayList<>(); + if (filters.size() != 1) { + throw new DataAccessException("request a gets without/or with more 1 filter of values"); + } + final FilterValue filter = filters.get(0); + // External checker of data: + if (options != null) { + final List checks = options.get(CheckFunction.class); + for (final CheckFunction check : checks) { + check.getChecker().check("", data, filter.getValues(), options); + } + } + final List asyncActions = new ArrayList<>(); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + // real add in the BDD: + try (entry) { + final String tableName = AnnotationTools.getTableName(clazz, options); + // boolean createIfNotExist = clazz.getDeclaredAnnotationsByType(SQLIfNotExists.class).length != 0; + final StringBuilder query = new StringBuilder(); + query.append("UPDATE `"); + query.append(tableName); + query.append("` SET "); + + boolean firstField = true; + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + final String name = AnnotationTools.getFieldName(field); + if (!filter.getValues().contains(name)) { + continue; + } else if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + if (addOn.isInsertAsync(field)) { + final List transmitKey = options.get(TransmitKey.class); + if (transmitKey.size() != 1) { + throw new DataAccessException( + "Fail to transmit Key to update the async update... (must have only 1)"); + } + addOn.asyncUpdate(tableName, transmitKey.get(0).getKey(), field, field.get(data), asyncActions); + } + continue; + } + if (!field.getClass().isPrimitive()) { + final Object tmp = field.get(data); + if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { + continue; + } + } + if (firstField) { + firstField = false; + } else { + query.append(","); + } + query.append(" `"); + query.append(name); + query.append("` = ? "); + } + query.append(" "); + final List orders = options.get(OrderBy.class); + for (final OrderBy order : orders) { + order.generateQuery(query, tableName); + } + query.append(" "); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + condition.whereAppendQuery(query, tableName, null, deletedFieldName); + + // If the first field is not set, then nothing to update n the main base: + if (!firstField) { + LOGGER.debug("generate update query: '{}'", query.toString()); + // prepare the request: + try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), + Statement.RETURN_GENERATED_KEYS)) { + final CountInOut iii = new CountInOut(1); + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + final String name = AnnotationTools.getFieldName(field); + if (!filter.getValues().contains(name)) { + continue; + } else if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + continue; + } + if (addOn == null) { + final Class type = field.getType(); + if (!type.isPrimitive()) { + final Object tmp = field.get(data); + if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { + continue; + } + } + setValuedb(type, data, iii, field, ps); + } else { + addOn.insertData(ps, field, data, iii); + } + } + condition.injectQuery(ps, iii); + final int out = ps.executeUpdate(); + return out; + } + } + } catch (final SQLException ex) { + ex.printStackTrace(); + } + for (final LazyGetter action : asyncActions) { + action.doRequest(); + } + return 0; + } + + public static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii) + throws Exception { + if (value instanceof final UUID tmp) { + final byte[] dataByte = UuidUtils.asBytes(tmp); + ps.setBytes(iii.value, dataByte); + } else 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 ..."); + } + } + + public static int executeSimpleQuery(final String query, final QueryOption... option) + throws SQLException, IOException { + final QueryOptions options = new QueryOptions(option); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + LOGGER.info("Query : '{}'", query); + try (final Statement stmt = entry.connection.createStatement()) { + return stmt.executeUpdate(query); + } + } + + public static boolean executeQuery(final String query, final QueryOption... option) + throws SQLException, IOException { + final QueryOptions options = new QueryOptions(option); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + try (final Statement stmt = entry.connection.createStatement()) { + return stmt.execute(query); + } + } + + public static T getWhere(final Class clazz, final QueryOptions options) throws Exception { + options.add(new Limit(1)); + final List values = getsWhere(clazz, options); + if (values.size() == 0) { + return null; + } + return values.get(0); + } + + public static T getWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return getWhere(clazz, options); + } + + public static void generateSelectField(// + final StringBuilder querySelect, // + final StringBuilder query, // + final Class clazz, // + final QueryOptions options, // + final CountInOut count// + ) throws Exception { + final boolean readAllfields = QueryOptions.readAllColomn(options); + final String tableName = AnnotationTools.getTableName(clazz, options); + final String primaryKey = AnnotationTools.getPrimaryKeyField(clazz).getName(); + boolean firstField = true; + + for (final Field elem : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(elem); + if (addOn != null && !addOn.canRetrieve(elem)) { + continue; + } + final boolean notRead = AnnotationTools.isdefaultNotRead(elem); + if (!readAllfields && notRead) { + continue; + } + final String name = AnnotationTools.getFieldName(elem); + if (firstField) { + firstField = false; + } else { + querySelect.append(","); + } + querySelect.append(" "); + if (addOn != null) { + addOn.generateQuery(tableName, primaryKey, elem, querySelect, query, name, count, options); + } else { + querySelect.append(tableName); + querySelect.append("."); + querySelect.append(name); + count.inc(); + } + } + } + + public static List getsWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return getsWhere(clazz, options); + } + + public static Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty) + throws DataAccessException { + if (options == null) { + return new Condition(); + } + final List conditions = options.get(Condition.class); + if (conditions.size() == 0) { + if (throwIfEmpty) { + throw new DataAccessException("request a gets without any condition"); + } else { + return new Condition(); + } + } + Condition condition = null; + if (conditions.size() == 1) { + condition = conditions.get(0); + } else { + final QueryAnd andCondition = new QueryAnd(); + for (final Condition cond : conditions) { + andCondition.add(cond.condition); + } + condition = new Condition(andCondition); + } + return condition; + } + + @SuppressWarnings("unchecked") + public static List getsWhere(final Class clazz, final QueryOptions options) + throws DataAccessException, IOException { + final Condition condition = conditionFusionOrEmpty(options, false); + final List lazyCall = new ArrayList<>(); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final List outs = new ArrayList<>(); + try (final DBEntry entry = DBInterfaceOption.getAutoEntry(options)) { + final CountInOut count = new CountInOut(); + final StringBuilder querySelect = new StringBuilder(); + StringBuilder query = new StringBuilder(); + final String tableName = AnnotationTools.getTableName(clazz, options); + querySelect.append("SELECT "); + query.append(" FROM `"); + query.append(tableName); + query.append("` "); + + generateSelectField(querySelect, query, clazz, options, count); + querySelect.append(query.toString()); + query = querySelect; + condition.whereAppendQuery(query, tableName, options, deletedFieldName); + final List groups = options.get(GroupBy.class); + for (final GroupBy group : groups) { + group.generateQuery(query, tableName); + } + final List orders = options.get(OrderBy.class); + for (final OrderBy order : orders) { + order.generateQuery(query, tableName); + } + final List limits = options.get(Limit.class); + if (limits.size() == 1) { + limits.get(0).generateQuery(query, tableName); + } else if (limits.size() > 1) { + throw new DataAccessException("Request with multiple 'limit'..."); + } + LOGGER.debug("generate the query: '{}'", query.toString()); + // prepare the request: + try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), + Statement.RETURN_GENERATED_KEYS)) { + final CountInOut iii = new CountInOut(1); + condition.injectQuery(ps, iii); + if (limits.size() == 1) { + limits.get(0).injectQuery(ps, iii); + } + // execute the request + final ResultSet rs = ps.executeQuery(); + while (rs.next()) { + count.value = 1; + final CountInOut countNotNull = new CountInOut(0); + final Object data = createObjectFromSQLRequest(rs, clazz, count, countNotNull, options, lazyCall); + final T out = (T) data; + outs.add(out); + } + LOGGER.info("Async calls: {}", lazyCall.size()); + for (final LazyGetter elem : lazyCall) { + elem.doRequest(); + } + } + } catch (final SQLException ex) { + ex.printStackTrace(); + throw new DataAccessException("Catch a SQL Exception: " + ex.getMessage()); + } catch (final Exception ex) { + ex.printStackTrace(); + throw new DataAccessException("Catch an Exception: " + ex.getMessage()); + } + return outs; + } + + public static Object createObjectFromSQLRequest( + final ResultSet rs, + final Class clazz, + final CountInOut count, + final CountInOut countNotNull, + final QueryOptions options, + final List lazyCall) throws Exception { + final boolean readAllfields = QueryOptions.readAllColomn(options); + // TODO: manage class that is defined inside a class ==> Not manage for now... + Object data = null; + for (final Constructor contructor : clazz.getConstructors()) { + if (contructor.getParameterCount() == 0) { + data = contructor.newInstance(); + } + } + if (data == null) { + throw new DataAccessException( + "Can not find the default constructor for the class: " + clazz.getCanonicalName()); + } + for (final Field elem : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(elem); + if (addOn != null && !addOn.canRetrieve(elem)) { + continue; + } + final boolean notRead = AnnotationTools.isdefaultNotRead(elem); + if (!readAllfields && notRead) { + continue; + } + if (addOn != null) { + addOn.fillFromQuery(rs, elem, data, count, options, lazyCall); + } else { + setValueFromDb(elem.getType(), data, count, elem, rs, countNotNull); + } + } + return data; + } + + public static long count(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return DataAccessSQL.countWhere(clazz, options); + } + + public static long countWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return countWhere(clazz, options); + } + + public static long countWhere(final Class clazz, final QueryOptions options) throws Exception { + final Condition condition = conditionFusionOrEmpty(options, false); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + DBEntry entry = DBInterfaceOption.getAutoEntry(options); + long count = 0; + // real add in the BDD: + try { + final StringBuilder query = new StringBuilder(); + final String tableName = AnnotationTools.getTableName(clazz, options); + query.append("SELECT COUNT(*) AS count FROM `"); + query.append(tableName); + query.append("` "); + condition.whereAppendQuery(query, tableName, options, deletedFieldName); + final List limits = options.get(Limit.class); + if (limits.size() == 1) { + limits.get(0).generateQuery(query, tableName); + } else if (limits.size() > 1) { + throw new DataAccessException("Request with multiple 'limit'..."); + } + LOGGER.debug("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); + condition.injectQuery(ps, iii); + if (limits.size() == 1) { + limits.get(0).injectQuery(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 { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return DataAccessSQL.getWhere(clazz, options.getAllArray()); + } + + public static List gets(final Class clazz) throws Exception { + return getsWhere(clazz); + } + + public static List gets(final Class clazz, final QueryOption... option) throws Exception { + return getsWhere(clazz, option); + } + + /** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). + * @param Type of the reference @Id + * @param clazz Data model that might remove element + * @param id Unique Id of the model + * @param options (Optional) Options of the request + * @return Number of element that is removed. */ + public static int delete(final Class clazz, final ID_TYPE id, final QueryOption... options) + throws Exception { + final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (hasDeletedFieldName != null) { + return deleteSoft(clazz, id, options); + } else { + return deleteHard(clazz, id, options); + } + } + + /** Delete items with the specific condition and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). + * @param clazz Data model that might remove element. + * @param condition Condition to remove elements. + * @param options (Optional) Options of the request. + * @return Number of element that is removed. */ + public static int deleteWhere(final Class clazz, final QueryOption... option) throws Exception { + + final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (hasDeletedFieldName != null) { + return deleteSoftWhere(clazz, option); + } else { + return deleteHardWhere(clazz, option); + } + } + + public static int deleteHard(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return deleteHardWhere(clazz, options.getAllArray()); + } + + public static int deleteHardWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String tableName = AnnotationTools.getTableName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + // find the deleted field + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("DELETE FROM `"); + query.append(tableName); + query.append("` "); + condition.whereAppendQuery(query, tableName, null, deletedFieldName); + try { + LOGGER.debug("APPLY: {}", query.toString()); + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + final CountInOut iii = new CountInOut(1); + condition.injectQuery(ps, iii); + return ps.executeUpdate(); + } finally { + entry.close(); + } + } + + private static int deleteSoft(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws Exception { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return deleteSoftWhere(clazz, options.getAllArray()); + } + + public static int deleteSoftWhere(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String tableName = AnnotationTools.getTableName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + /* String updateFieldName = null; if ("sqlite".equalsIgnoreCase(ConfigBaseVariable.getDBType())) { updateFieldName = AnnotationTools.getUpdatedFieldName(clazz); } */ + // find the deleted field + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("UPDATE `"); + query.append(tableName); + query.append("` SET `"); + query.append(deletedFieldName); + query.append("`=true "); + /* The trigger work well, but the timestamp is store @ seconds... if (updateFieldName != null) { // done only in SQLite (the trigger does not work... query.append(", `"); + * query.append(updateFieldName); query.append("`=DATE()"); } */ + condition.whereAppendQuery(query, tableName, null, deletedFieldName); + try { + LOGGER.debug("APPLY UPDATE: {}", query.toString()); + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + final CountInOut iii = new CountInOut(1); + condition.injectQuery(ps, iii); + return ps.executeUpdate(); + } finally { + entry.close(); + } + } + + public static int unsetDelete(final Class clazz, final ID_TYPE id) throws DataAccessException { + return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id))); + } + + public static int unsetDelete(final Class clazz, final ID_TYPE id, final QueryOption... option) + throws DataAccessException { + final QueryOptions options = new QueryOptions(option); + options.add(new Condition(getTableIdCondition(clazz, id))); + return unsetDeleteWhere(clazz, options.getAllArray()); + } + + public static int unsetDeleteWhere(final Class clazz, final QueryOption... option) throws DataAccessException { + final QueryOptions options = new QueryOptions(option); + final Condition condition = conditionFusionOrEmpty(options, true); + final String tableName = AnnotationTools.getTableName(clazz, options); + final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + if (deletedFieldName == null) { + throw new DataAccessException("The class " + clazz.getCanonicalName() + " has no deleted field"); + } + DBEntry entry; + try { + entry = DBInterfaceOption.getAutoEntry(options); + } catch (final IOException ex) { + throw new DataAccessException("Fail to connect the DB: " + ex.getMessage()); + } + final StringBuilder query = new StringBuilder(); + query.append("UPDATE `"); + query.append(tableName); + query.append("` SET `"); + query.append(deletedFieldName); + query.append("`=false "); + // need to disable the deleted false because the model must be unselected to be updated. + options.add(QueryOptions.ACCESS_DELETED_ITEMS); + condition.whereAppendQuery(query, tableName, options, deletedFieldName); + try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString())) { + final CountInOut iii = new CountInOut(1); + condition.injectQuery(ps, iii); + return ps.executeUpdate(); + } catch (final SQLException ex) { + throw new DataAccessException("Catch SQL error:" + ex.getMessage()); + } catch (final Exception ex) { + throw new DataAccessException("Fail to excute the SQL query:" + ex.getMessage()); + } + } + + public static void drop(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final String tableName = AnnotationTools.getTableName(clazz, options); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("DROP TABLE IF EXISTS `"); + query.append(tableName); + query.append("`"); + try { + LOGGER.trace("Execute Query: {}", query.toString()); + // Remove main table + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + ps.executeUpdate(); + // search subTable: + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + addOn.drop(tableName, field); + } + } + } finally { + entry.close(); + } + } + + public static void cleanAll(final Class clazz, final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + final String tableName = AnnotationTools.getTableName(clazz, options); + DBEntry entry = DBInterfaceOption.getAutoEntry(options); + final StringBuilder query = new StringBuilder(); + query.append("DELETE FROM `"); + query.append(tableName); + query.append("`"); + try { + LOGGER.trace("Execute Query: {}", query.toString()); + // Remove main table + final PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + ps.executeUpdate(); + // search subTable: + for (final Field field : clazz.getFields()) { + // static field is only for internal global declaration ==> remove it .. + if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { + continue; + } + if (AnnotationTools.isGenericField(field)) { + continue; + } + final DataAccessAddOn addOn = findAddOnforField(field); + if (addOn != null && !addOn.canInsert(field)) { + addOn.cleanAll(tableName, field); + } + } + } finally { + entry.close(); + entry = null; + } + } + + /** Execute a simple query with external property. + * @param Type of the data generate. + * @param clazz Class that might be analyze. + * @param query Base of the query. + * @param parameters "?" parameter of the query. + * @param option Optional parameters + * @return The list of element requested + * @throws Exception */ + public static List query( + final Class clazz, + final String query, + final List parameters, + final QueryOption... option) throws Exception { + final QueryOptions options = new QueryOptions(option); + return query(clazz, query, parameters, options); + } + + public static List query( + final Class clazz, + final String queryBase, + final List parameters, + final QueryOptions options) throws Exception { + final List lazyCall = new ArrayList<>(); + // TODO ... final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); + final DBEntry entry = DBInterfaceOption.getAutoEntry(options); + + final Condition condition = conditionFusionOrEmpty(options, false); + final StringBuilder query = new StringBuilder(queryBase); + final List outs = new ArrayList<>(); + // real add in the BDD: + try { + final CountInOut count = new CountInOut(); + condition.whereAppendQuery(query, null, options, null); + + final List groups = options.get(GroupBy.class); + for (final GroupBy group : groups) { + group.generateQuery(query, null); + } + final List orders = options.get(OrderBy.class); + for (final OrderBy order : orders) { + order.generateQuery(query, null); + } + final List limits = options.get(Limit.class); + if (limits.size() == 1) { + limits.get(0).generateQuery(query, null); + } else if (limits.size() > 1) { + throw new DataAccessException("Request with multiple 'limit'..."); + } + LOGGER.debug("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 (parameters != null) { + for (final Object elem : parameters) { + DataAccessSQL.addElement(ps, elem, iii); + } + iii.inc(); + } + condition.injectQuery(ps, iii); + if (limits.size() == 1) { + limits.get(0).injectQuery(ps, iii); + } + // execute the request + final ResultSet rs = ps.executeQuery(); + final ResultSetMetaData rsmd = rs.getMetaData(); + final List actionToRetreive = new ArrayList<>(); + LOGGER.info("Field:"); + for (int jjj = 0; jjj < rsmd.getColumnCount(); jjj++) { + final String label = rsmd.getColumnLabel(jjj + 1); + LOGGER.info(" - {}:{}", jjj, label); + // find field name ... + final Field field = AnnotationTools.getFieldNamed(clazz, label); + if (field == null) { + throw new DataAccessException("Query with unknown field: '" + label + "'"); + } + // create the callback... + final RetreiveFromDB element = createSetValueFromDbCallback(jjj + 1, field); + actionToRetreive.add(element); + } + + while (rs.next()) { + count.value = 1; + Object data = null; + for (final Constructor contructor : clazz.getConstructors()) { + if (contructor.getParameterCount() == 0) { + data = contructor.newInstance(); + } + } + if (data == null) { + // TODO... + } else { + for (final RetreiveFromDB action : actionToRetreive) { + action.doRequest(rs, data); + } + } + @SuppressWarnings("unchecked") + final TYPE out = (TYPE) data; + outs.add(out); + } + LOGGER.info("Async calls: {}", lazyCall.size()); + for (final LazyGetter elem : lazyCall) { + elem.doRequest(); + } + } catch (final SQLException ex) { + ex.printStackTrace(); + throw ex; + } catch (final Exception ex) { + ex.printStackTrace(); + } finally { + entry.close(); + } + return outs; + } +} diff --git a/src/org/kar/archidata/dataAccess/QueryAnd.java b/src/org/kar/archidata/dataAccess/QueryAnd.java index 55f7f4a..2bc1879 100644 --- a/src/org/kar/archidata/dataAccess/QueryAnd.java +++ b/src/org/kar/archidata/dataAccess/QueryAnd.java @@ -5,22 +5,26 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Filters; + public class QueryAnd implements QueryItem { protected final List childs; - + public QueryAnd(final List child) { this.childs = child; } - + public QueryAnd(final QueryItem... child) { this.childs = new ArrayList<>(); Collections.addAll(this.childs, child); } - + public void add(final QueryItem... child) { Collections.addAll(this.childs, child); } - + @Override public void generateQuery(final StringBuilder query, final String tableName) { if (this.childs.size() >= 1) { @@ -39,16 +43,25 @@ public class QueryAnd implements QueryItem { query.append(")"); } } - + @Override public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { - + for (final QueryItem elem : this.childs) { elem.injectQuery(ps, iii); } } - + public int size() { return this.childs.size(); } + + @Override + public void generateFilter(final List filters) { + final List filtersLocal = new ArrayList<>(); + for (final QueryItem elem : this.childs) { + elem.generateFilter(filtersLocal); + } + filters.add(Filters.and(filtersLocal.toArray(new Bson[0]))); + } } diff --git a/src/org/kar/archidata/dataAccess/QueryCondition.java b/src/org/kar/archidata/dataAccess/QueryCondition.java index c399d3f..4b90162 100644 --- a/src/org/kar/archidata/dataAccess/QueryCondition.java +++ b/src/org/kar/archidata/dataAccess/QueryCondition.java @@ -1,12 +1,20 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; +import java.util.List; + +import org.bson.conversions.Bson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.client.model.Filters; public class QueryCondition implements QueryItem { + static final Logger LOGGER = LoggerFactory.getLogger(DataAccess.class); private final String key; private final String comparator; private final Object value; - + /** * Simple DB comparison element. Note the injected object is injected in the statement and not in the query directly. * @param key Field to check (the Model property name) @@ -18,7 +26,7 @@ public class QueryCondition implements QueryItem { this.comparator = comparator; this.value = value; } - + @Override public void generateQuery(final StringBuilder query, final String tableName) { if (tableName != null) { @@ -30,10 +38,30 @@ public class QueryCondition implements QueryItem { query.append(this.comparator); query.append(" ?"); } - + @Override public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { DataAccess.addElement(ps, this.value, iii); iii.inc(); } + + @Override + public void generateFilter(final List filters) { + if ("=".equals(this.comparator)) { + filters.add(Filters.eq(this.key, this.value)); + } else if ("!=".equals(this.comparator)) { + filters.add(Filters.ne(this.key, this.value)); + } else if (">".equals(this.comparator)) { + filters.add(Filters.gt(this.key, this.value)); + } else if (">=".equals(this.comparator)) { + filters.add(Filters.gte(this.key, this.value)); + } else if ("<".equals(this.comparator)) { + filters.add(Filters.lt(this.key, this.value)); + } else if ("<=".equals(this.comparator)) { + filters.add(Filters.lte(this.key, this.value)); + } else { + LOGGER.error("Not manage comparison: '{}'", this.key); + } + + } } diff --git a/src/org/kar/archidata/dataAccess/QueryInList.java b/src/org/kar/archidata/dataAccess/QueryInList.java index bfacf9f..9d03bc6 100644 --- a/src/org/kar/archidata/dataAccess/QueryInList.java +++ b/src/org/kar/archidata/dataAccess/QueryInList.java @@ -3,6 +3,10 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; import java.util.List; +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Filters; + public class QueryInList implements QueryItem { protected final String key; protected final String comparator; @@ -50,4 +54,9 @@ public class QueryInList implements QueryItem { iii.inc(); } } + + @Override + public void generateFilter(final List filters) { + filters.add(Filters.in(this.key, this.value)); + } } diff --git a/src/org/kar/archidata/dataAccess/QueryItem.java b/src/org/kar/archidata/dataAccess/QueryItem.java index 20498b7..1478df6 100644 --- a/src/org/kar/archidata/dataAccess/QueryItem.java +++ b/src/org/kar/archidata/dataAccess/QueryItem.java @@ -1,9 +1,17 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; +import java.util.List; + +import org.bson.conversions.Bson; public interface QueryItem { + // For SQL mode query construction void generateQuery(StringBuilder query, String tableName); + // For SQL mode query injection void injectQuery(PreparedStatement ps, CountInOut iii) throws Exception; + + // For No-SQL mode filter creation + void generateFilter(List filters); } diff --git a/src/org/kar/archidata/dataAccess/QueryNotNull.java b/src/org/kar/archidata/dataAccess/QueryNotNull.java index 3bcef4e..712c504 100644 --- a/src/org/kar/archidata/dataAccess/QueryNotNull.java +++ b/src/org/kar/archidata/dataAccess/QueryNotNull.java @@ -1,6 +1,11 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; +import java.util.List; + +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Filters; public class QueryNotNull implements QueryItem { private final String key; @@ -21,4 +26,9 @@ public class QueryNotNull implements QueryItem { @Override public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {} + + @Override + public void generateFilter(final List filters) { + filters.add(Filters.exists(this.key)); + } } diff --git a/src/org/kar/archidata/dataAccess/QueryNull.java b/src/org/kar/archidata/dataAccess/QueryNull.java index dfc70d4..0b1ad75 100644 --- a/src/org/kar/archidata/dataAccess/QueryNull.java +++ b/src/org/kar/archidata/dataAccess/QueryNull.java @@ -1,14 +1,19 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; +import java.util.List; + +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Filters; public class QueryNull implements QueryItem { private final String key; - + public QueryNull(final String key) { this.key = key; } - + @Override public void generateQuery(final StringBuilder query, final String tableName) { if (tableName != null) { @@ -18,7 +23,13 @@ public class QueryNull implements QueryItem { query.append(this.key); query.append(" IS NULL"); } - + @Override public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {} + + @Override + public void generateFilter(final List filters) { + // Not sure of the result ... maybe check it ... + filters.add(Filters.eq(this.key, null)); + } } diff --git a/src/org/kar/archidata/dataAccess/QueryOr.java b/src/org/kar/archidata/dataAccess/QueryOr.java index 18e28aa..30cd25a 100644 --- a/src/org/kar/archidata/dataAccess/QueryOr.java +++ b/src/org/kar/archidata/dataAccess/QueryOr.java @@ -1,19 +1,24 @@ package org.kar.archidata.dataAccess; import java.sql.PreparedStatement; +import java.util.ArrayList; import java.util.List; +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Filters; + public class QueryOr implements QueryItem { protected final List childs; - + public QueryOr(final List childs) { this.childs = childs; } - + public QueryOr(final QueryItem... childs) { this.childs = List.of(childs); } - + @Override public void generateQuery(final StringBuilder query, final String tableName) { if (this.childs.size() >= 1) { @@ -32,11 +37,20 @@ public class QueryOr implements QueryItem { query.append(")"); } } - + @Override public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { for (final QueryItem elem : this.childs) { elem.injectQuery(ps, iii); } } + + @Override + public void generateFilter(final List filters) { + final List filtersLocal = new ArrayList<>(); + for (final QueryItem elem : this.childs) { + elem.generateFilter(filtersLocal); + } + filters.add(Filters.or(filtersLocal.toArray(new Bson[0]))); + } } diff --git a/src/org/kar/archidata/dataAccess/options/CheckJPA.java b/src/org/kar/archidata/dataAccess/options/CheckJPA.java index 06ce78d..bfcfd1c 100644 --- a/src/org/kar/archidata/dataAccess/options/CheckJPA.java +++ b/src/org/kar/archidata/dataAccess/options/CheckJPA.java @@ -28,10 +28,10 @@ import jakarta.persistence.ManyToOne; import jakarta.validation.constraints.Size; 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 @@ -40,9 +40,9 @@ public class CheckJPA implements CheckFunctionInterface { * @throws Exception Exception is generate if the data are incorrect. */ void check(final String baseName, final K data, final QueryOptions options) throws Exception; } - + protected Map>> checking = null; - + protected void add(final String field, final CheckInterface checkFunction) { List> actions = this.checking.get(field); if (actions == null) { @@ -51,11 +51,11 @@ public class CheckJPA implements CheckFunctionInterface { } actions.add(checkFunction); } - + public CheckJPA(final Class clazz) { this.clazz = clazz; } - + public void initialize() throws Exception { if (this.checking != null) { return; @@ -84,7 +84,7 @@ public class CheckJPA implements CheckFunctionInterface { 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); @@ -131,7 +131,7 @@ public class CheckJPA implements CheckFunctionInterface { } }); } - + } else if (type == Integer.class || type == int.class) { final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); if (maxValueRoot != null) { @@ -191,7 +191,7 @@ public class CheckJPA implements CheckFunctionInterface { }); } } 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) { @@ -251,11 +251,11 @@ public class CheckJPA implements CheckFunctionInterface { }); } } else if (type == Date.class || type == Timestamp.class) { - + } else if (type == LocalDate.class) { - + } else if (type == LocalTime.class) { - + } else if (type == String.class) { final int maxSizeString = AnnotationTools.getLimitSize(field); if (maxSizeString > 0) { @@ -337,14 +337,22 @@ public class CheckJPA implements CheckFunctionInterface { } }); } - + } } catch (final Exception ex) { this.checking = null; throw ex; } } - + + public void check(final String baseName, final Object data) throws Exception { + check(baseName, data, null, null); + } + + public void check(final String baseName, final Object data, final List filterValue) throws Exception { + check(baseName, data, filterValue, null); + } + @Override public void check( final String baseName, @@ -370,7 +378,7 @@ public class CheckJPA implements CheckFunctionInterface { } checkTyped(dataCasted, filterValue, options); } - + public void checkTyped(final T data, final List filterValue, final QueryOptions options) throws Exception { // nothing to do ... } diff --git a/src/org/kar/archidata/dataAccess/options/Condition.java b/src/org/kar/archidata/dataAccess/options/Condition.java index 7831a80..64f9a94 100644 --- a/src/org/kar/archidata/dataAccess/options/Condition.java +++ b/src/org/kar/archidata/dataAccess/options/Condition.java @@ -1,35 +1,40 @@ package org.kar.archidata.dataAccess.options; import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import org.bson.conversions.Bson; import org.kar.archidata.dataAccess.CountInOut; import org.kar.archidata.dataAccess.QueryItem; import org.kar.archidata.dataAccess.QueryOptions; +import com.mongodb.client.model.Filters; + /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ public class Condition extends QueryOption { public final QueryItem condition; - + public Condition(final QueryItem items) { this.condition = items; } - + public Condition() { this.condition = null; } - + public void generateQuery(final StringBuilder query, final String tableName) { if (this.condition != null) { this.condition.generateQuery(query, tableName); } } - + public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { if (this.condition != null) { this.condition.injectQuery(ps, iii); } } - + public void whereAppendQuery( final StringBuilder query, final String tableName, @@ -62,4 +67,26 @@ public class Condition extends QueryOption { } query.append("\n"); } + + public Bson getFilter(final String collectionName, final QueryOptions options, final String deletedFieldName) { + boolean exclude_deleted = true; + if (options != null) { + exclude_deleted = !options.exist(AccessDeletedItems.class); + } + final List filter = new ArrayList<>(); + if (exclude_deleted && deletedFieldName != null) { + filter.add(Filters.ne(deletedFieldName, false)); + } + // Check if we have a condition to generate + if (this.condition != null) { + this.condition.generateFilter(filter); + } + if (filter.size() == 0) { + return null; + } + if (filter.size() == 1) { + return filter.get(0); + } + return Filters.and(filter.toArray(new Bson[0])); + } } diff --git a/src/org/kar/archidata/dataAccess/options/Limit.java b/src/org/kar/archidata/dataAccess/options/Limit.java index fbabb21..88f2159 100644 --- a/src/org/kar/archidata/dataAccess/options/Limit.java +++ b/src/org/kar/archidata/dataAccess/options/Limit.java @@ -7,17 +7,21 @@ import org.kar.archidata.dataAccess.DataAccess; public class Limit extends QueryOption { protected final long limit; - + public Limit(final long limit) { this.limit = limit; } - + public void generateQuery(final StringBuilder query, final String tableName) { query.append(" LIMIT ? \n"); } - + public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { DataAccess.addElement(ps, this.limit, iii); iii.inc(); } + + public long getValue() { + return this.limit; + } } diff --git a/src/org/kar/archidata/dataAccess/options/OrderBy.java b/src/org/kar/archidata/dataAccess/options/OrderBy.java index be0a10e..0d6b92a 100644 --- a/src/org/kar/archidata/dataAccess/options/OrderBy.java +++ b/src/org/kar/archidata/dataAccess/options/OrderBy.java @@ -3,19 +3,21 @@ package org.kar.archidata.dataAccess.options; import java.sql.PreparedStatement; import java.util.List; +import org.bson.Document; import org.kar.archidata.dataAccess.CountInOut; +import org.kar.archidata.dataAccess.options.OrderItem.Order; public class OrderBy extends QueryOption { protected final List childs; - + public OrderBy(final List childs) { this.childs = childs; } - + public OrderBy(final OrderItem... childs) { this.childs = List.of(childs); } - + public void generateQuery(final StringBuilder query, final String tableName) { if (this.childs.size() == 0) { return; @@ -36,8 +38,14 @@ public class OrderBy extends QueryOption { } query.append("\n"); } - + public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception { // nothing to add. } + + public void generateSort(final Document data) { + for (final OrderItem elem : this.childs) { + data.append(elem.value, elem.order == Order.ASC ? 1 : -1); + } + } } diff --git a/src/org/kar/archidata/db/DBConfig.java b/src/org/kar/archidata/db/DBConfig.java index 46c7a52..2bda5c6 100644 --- a/src/org/kar/archidata/db/DBConfig.java +++ b/src/org/kar/archidata/db/DBConfig.java @@ -1,6 +1,7 @@ package org.kar.archidata.db; import org.kar.archidata.dataAccess.DataAccess; +import org.kar.archidata.exception.DataAccessException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,10 +16,13 @@ public class DBConfig { private final boolean keepConnected; public DBConfig(final String type, final String hostname, final Integer port, final String login, - final String password, final String dbName, final boolean keepConnected) { + final String password, final String dbName, final boolean keepConnected) throws DataAccessException { if (type == null) { this.type = "mysql"; } else { + if (!"mysql".equals(type) && !"sqlite".equals(type) && !"mongo".equals(type)) { + throw new DataAccessException("unexpected DB type: '" + type + "'"); + } this.type = type; } if (hostname == null) { @@ -27,7 +31,11 @@ public class DBConfig { this.hostname = hostname; } if (port == null) { - this.port = 3306; + if ("mysql".equals(this.type)) { + this.port = 3306; + } else { + this.port = 27017; + } } else { this.port = port; } @@ -35,6 +43,7 @@ public class DBConfig { this.password = password; this.dbName = dbName; this.keepConnected = keepConnected; + } @Override @@ -82,11 +91,17 @@ public class DBConfig { } return "jdbc:sqlite:" + this.hostname + ".db"; } - if (isRoot) { - return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port - + "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"; + if ("mongo".equals(this.type)) { + return "mongodb:" + getLogin() + ":" + getPassword() + "//" + this.hostname + ":" + this.port; } - return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName - + "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"; + if ("mysql".equals(this.type)) { + if (isRoot) { + return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + + "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"; + } + return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName + + "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"; + } + return "dead_code"; } } diff --git a/src/org/kar/archidata/db/DBEntry.java b/src/org/kar/archidata/db/DBEntry.java deleted file mode 100644 index c24f620..0000000 --- a/src/org/kar/archidata/db/DBEntry.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.kar.archidata.db; - -import java.io.Closeable; -import java.io.IOException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DBEntry implements Closeable { - final static Logger LOGGER = LoggerFactory.getLogger(DBEntry.class); - public DBConfig config; - public Connection connection; - private static List stored = new ArrayList<>(); - - private DBEntry(final DBConfig config, final boolean root) throws IOException { - this.config = config; - if (root) { - connectRoot(); - } else { - connect(); - } - } - - public static DBEntry createInterface(final DBConfig config) throws IOException { - return createInterface(config, false); - } - - public static DBEntry createInterface(final DBConfig config, final boolean root) throws IOException { - if (config.getKeepConnected()) { - for (final DBEntry elem : stored) { - if (elem == null) { - continue; - } - if (elem.config.getUrl().equals(config.getUrl())) { - return elem; - } - } - final DBEntry tmp = new DBEntry(config, root); - stored.add(tmp); - return tmp; - } else { - return new DBEntry(config, root); - } - } - - public void connectRoot() throws IOException { - try { - this.connection = DriverManager.getConnection(this.config.getUrl(true), this.config.getLogin(), - this.config.getPassword()); - } catch (final SQLException ex) { - throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true)); - } - - } - - public void connect() throws IOException { - try { - this.connection = DriverManager.getConnection(this.config.getUrl(), this.config.getLogin(), - this.config.getPassword()); - } catch (final SQLException ex) { - LOGGER.error("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true)); - throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true)); - } - - } - - @Override - public void close() throws IOException { - if (this.config.getKeepConnected()) { - return; - } - closeForce(); - } - - public void closeForce() throws IOException { - try { - // connection.commit(); - this.connection.close(); - } catch (final SQLException ex) { - throw new IOException("Dis-connection db fail: " + ex.getMessage()); - } - } - - public static void closeAllForceMode() throws IOException { - for (final DBEntry entry : stored) { - entry.closeForce(); - } - stored = new ArrayList<>(); - } -} diff --git a/src/org/kar/archidata/db/DbInterface.java b/src/org/kar/archidata/db/DbInterface.java new file mode 100644 index 0000000..7367217 --- /dev/null +++ b/src/org/kar/archidata/db/DbInterface.java @@ -0,0 +1,3 @@ +package org.kar.archidata.db; + +public class DbInterface {} diff --git a/src/org/kar/archidata/db/DbInterfaceMorphia.java b/src/org/kar/archidata/db/DbInterfaceMorphia.java new file mode 100644 index 0000000..ab2a8a6 --- /dev/null +++ b/src/org/kar/archidata/db/DbInterfaceMorphia.java @@ -0,0 +1,68 @@ +package org.kar.archidata.db; + +import java.io.Closeable; +import java.io.IOException; + +import org.bson.UuidRepresentation; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +import dev.morphia.Datastore; +import dev.morphia.Morphia; + +public class DbInterfaceMorphia extends DbInterface implements Closeable { + final static Logger LOGGER = LoggerFactory.getLogger(DbInterfaceMorphia.class); + private final MongoClient mongoClient; + private final Datastore datastore; + + public DbInterfaceMorphia(final String dbUrl, final String dbName, final Class... classes) { + // Connect to MongoDB (simple form): + // final MongoClient mongoClient = MongoClients.create(dbUrl); + + // Connect to MongoDB (complex form): + final ConnectionString connectionString = new ConnectionString(dbUrl); + // Créer un CodecRegistry pour UUID + //final CodecRegistry uuidCodecRegistry = CodecRegistries.fromCodecs(new UUIDCodec()); + // Créer un CodecRegistry pour POJOs + final CodecRegistry pojoCodecRegistry = CodecRegistries + .fromProviders(PojoCodecProvider.builder().automatic(true).build()); + // Ajouter le CodecRegistry par défaut, le codec UUID et celui pour POJOs + //final CodecRegistry codecRegistry = CodecRegistries.fromRegistries( + // MongoClientSettings.getDefaultCodecRegistry(), /*uuidCodecRegistry, */ pojoCodecRegistry); + + final CodecRegistry codecRegistry = CodecRegistries.fromRegistries( + MongoClientSettings.getDefaultCodecRegistry(), + CodecRegistries.fromCodecs(new org.bson.codecs.UuidCodec(UuidRepresentation.STANDARD)), + pojoCodecRegistry); + // Configurer MongoClientSettings + final MongoClientSettings clientSettings = MongoClientSettings.builder() // + .applyConnectionString(connectionString)// + .codecRegistry(codecRegistry) // + .uuidRepresentation(UuidRepresentation.STANDARD)// + .build(); + this.mongoClient = MongoClients.create(clientSettings); + this.datastore = Morphia.createDatastore(this.mongoClient, "karusic"); + // Map entities + this.datastore.getMapper().map(classes); + // Ensure indexes + this.datastore.ensureIndexes(); + } + + public Datastore getDatastore() { + return this.datastore; + } + + @Override + public void close() throws IOException { + this.mongoClient.close(); + + } +} diff --git a/src/org/kar/archidata/db/DbInterfaceSQL.java b/src/org/kar/archidata/db/DbInterfaceSQL.java new file mode 100644 index 0000000..18fca6e --- /dev/null +++ b/src/org/kar/archidata/db/DbInterfaceSQL.java @@ -0,0 +1,42 @@ +package org.kar.archidata.db; + +import java.io.Closeable; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DbInterfaceSQL extends DbInterface implements Closeable { + final static Logger LOGGER = LoggerFactory.getLogger(DbInterfaceSQL.class); + + private final Connection connection; + + public DbInterfaceSQL(final DBConfig config, final String dbName, final Class... classes) throws IOException { + this(config.getUrl(), config.getLogin(), config.getPassword()); + } + + public DbInterfaceSQL(final String dbUrl, final String login, final String password) throws IOException { + try { + this.connection = DriverManager.getConnection(dbUrl, login, password); + } catch (final SQLException ex) { + LOGGER.error("Connection db fail: " + ex.getMessage() + " On URL: " + dbUrl); + throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + dbUrl); + } + } + + public Connection getConnection() { + return this.connection; + } + + @Override + public void close() throws IOException { + try { + this.connection.close(); + } catch (final SQLException ex) { + throw new IOException("Dis-connection db fail: " + ex.getMessage()); + } + } +} diff --git a/src/org/kar/archidata/model/UUIDGenericData.java b/src/org/kar/archidata/model/UUIDGenericData.java index 57388d8..a1fca2e 100644 --- a/src/org/kar/archidata/model/UUIDGenericData.java +++ b/src/org/kar/archidata/model/UUIDGenericData.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.DefaultValue; public class UUIDGenericData extends GenericTiming { @Id + @DefaultValue("(UUID_TO_BIN(UUID(), TRUE))") @Column(nullable = false, unique = true) @Schema(description = "Unique UUID of the object", required = false, readOnly = true, example = "e6b33c1c-d24d-11ee-b616-02420a030102")