diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index 027c097..de63433 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -36,7 +36,7 @@ public class AnnotationTools { return element.getSimpleName(); } if (annotation.length > 1) { - throw new Exception("Must not have more than 1 element @SQLTableName on " + element.getClass().getCanonicalName()); + throw new Exception("Must not have more than 1 element @Table on " + element.getClass().getCanonicalName()); } final String tmp = ((Table) annotation[0]).name(); if (tmp == null) { @@ -51,7 +51,7 @@ public class AnnotationTools { return null; } if (annotation.length > 1) { - throw new Exception("Must not have more than 1 element @SQLComment on " + element.getClass().getCanonicalName()); + throw new Exception("Must not have more than 1 element @DataComment on " + element.getClass().getCanonicalName()); } return ((DataComment) annotation[0]).value(); } @@ -62,7 +62,7 @@ public class AnnotationTools { return null; } if (annotation.length > 1) { - throw new Exception("Must not have more than 1 element @SQLDefault on " + element.getClass().getCanonicalName()); + throw new Exception("Must not have more than 1 element @DataDefault on " + element.getClass().getCanonicalName()); } return ((DataDefault) annotation[0]).value(); } @@ -70,12 +70,13 @@ public class AnnotationTools { public static Integer getLimitSize(final Field element) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); if (annotation.length == 0) { - return null; + return 255; } if (annotation.length > 1) { - throw new Exception("Must not have more than 1 element @SQLLimitSize on " + element.getClass().getCanonicalName()); + throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName()); } - return ((Column) annotation[0]).length(); + final int length = ((Column) annotation[0]).length(); + return length <= 0 ? null : length; } public static boolean isAnnotationGroup(final Field field, final Class annotationType) { diff --git a/src/org/kar/archidata/dataAccess/DataAccess.java b/src/org/kar/archidata/dataAccess/DataAccess.java index 3335140..12b304f 100644 --- a/src/org/kar/archidata/dataAccess/DataAccess.java +++ b/src/org/kar/archidata/dataAccess/DataAccess.java @@ -40,8 +40,20 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.InternalServerErrorException; +/* TODO list: + - useful code to manage external query: List query(class clazz, String query, List parameters); + ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM TABLE2"); + ResultSetMetaData rsmd = rs.getMetaData(); + String name = rsmd.getColumnName(1); + - 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 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 { @@ -51,20 +63,12 @@ public class DataAccess { 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) { DataAccess.addOn.add(addOn); } - public static class ExceptionDBInterface extends Exception { - private static final long serialVersionUID = 1L; - public int errorID; - - public ExceptionDBInterface(final int errorId, final String message) { - super(message); - this.errorID = errorId; - } - } - public DataAccess() { } @@ -777,7 +781,6 @@ public class DataAccess { } } whereInjectValue(ps, condition, iii); - return ps.executeUpdate(); } catch (final SQLException ex) { ex.printStackTrace(); @@ -805,8 +808,6 @@ public class DataAccess { ps.setDouble(iii.value, tmp); } else if (value instanceof final Boolean tmp) { ps.setBoolean(iii.value, tmp); - } else if (value instanceof final Boolean tmp) { - ps.setBoolean(iii.value, tmp); } else if (value instanceof final Timestamp tmp) { ps.setTimestamp(iii.value, tmp); } else if (value instanceof final Date tmp) { @@ -822,8 +823,7 @@ public class DataAccess { } } - public static void whereAppendQuery(final StringBuilder query, final String tableName, final QueryItem condition, final QueryOptions options, final String deletedFieldName) - throws ExceptionDBInterface { + public static void whereAppendQuery(final StringBuilder query, final String tableName, final QueryItem condition, final QueryOptions options, final String deletedFieldName) { boolean exclude_deleted = true; if (options != null) { exclude_deleted = !options.exist(AccessDeletedItems.class); @@ -1058,6 +1058,12 @@ public class DataAccess { return delete(clazz, id, null); } + /** 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 QueryOptions options) throws Exception { final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); if (hasDeletedFieldName != null) { @@ -1067,6 +1073,11 @@ public class DataAccess { } } + /** 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 QueryItem condition, final QueryOptions options) throws Exception { final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz); if (hasDeletedFieldName != null) { @@ -1107,13 +1118,6 @@ public class DataAccess { return deleteSoftWhere(clazz, getTableIdCondition(clazz, id), options); } - public static String getDBNow() { - if (!"sqlite".equals(ConfigBaseVariable.getDBType())) { - return "now(3)"; - } - return "DATE()"; - } - public static int deleteSoftWhere(final Class clazz, final QueryItem condition, final QueryOptions options) throws Exception { final String tableName = AnnotationTools.getTableName(clazz, options); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); @@ -1163,7 +1167,6 @@ public class DataAccess { query.append("` SET `"); query.append(deletedFieldName); query.append("`=false "); - /* is is needed only for SQLite ??? query.append("`modify_date`="); query.append(getDBNow()); query.append(", "); */ // need to disable the deleted false because the model must be unselected to be updated. options.add(QueryOptions.ACCESS_DELETED_ITEMS); whereAppendQuery(query, tableName, condition, options, deletedFieldName); diff --git a/src/org/kar/archidata/migration/MigrationEngine.java b/src/org/kar/archidata/migration/MigrationEngine.java index caa0565..8d4d5bf 100644 --- a/src/org/kar/archidata/migration/MigrationEngine.java +++ b/src/org/kar/archidata/migration/MigrationEngine.java @@ -48,13 +48,20 @@ public class MigrationEngine { } /** Get the current version/migration name - * @return Model represent the last migration. If null then no migration has been done. */ - public Migration getCurrentVersion() { + * @return Model represent the last migration. If null then no migration has been done. + * @throws MigrationException */ + public Migration getCurrentVersion() throws MigrationException { if (!DataAccess.isTableExist("KAR_migration")) { return null; } try { - final List data = DataAccess.gets(Migration.class, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + List data = null; + try { + data = DataAccess.gets(Migration.class, new QueryOptions(QueryOptions.READ_ALL_COLOMN)); + } catch (final Exception e) { + // Previous version does not have the same timeCode... + data = DataAccess.gets(Migration.class); + } if (data == null) { LOGGER.error("Can not collect the migration table in the DB:{}"); return null; @@ -63,16 +70,16 @@ public class MigrationEngine { LOGGER.error("Fail to Request migration table in the DB: empty size"); return null; } - LOGGER.debug("List of migrations:"); + LOGGER.info("List of migrations:"); for (final Migration elem : data) { - LOGGER.debug(" - date={} name={} end={}", elem.updatedAt, elem.name, elem.terminated); + LOGGER.info(" - date={} name={} end={}", elem.updatedAt, elem.name, elem.terminated); } return data.get(data.size() - 1); } catch (final Exception ex) { LOGGER.error("Fail to Request migration table in the DB:{}", ex.getMessage()); ex.printStackTrace(); } - return null; + throw new MigrationException("Can not retreive Migration model"); } /** Process the automatic migration of the system The function wait the Administrator intervention to correct the bug. @@ -99,10 +106,13 @@ public class MigrationEngine { public void migrateErrorThrow(final DBConfig config) throws MigrationException { LOGGER.info("Execute migration ... [BEGIN]"); // check the integrity of the migrations: + LOGGER.info("List of availlable Migration: "); for (final MigrationInterface elem : this.datas) { if (elem == null) { + LOGGER.info(" - null"); throw new MigrationException("Add a null migration"); } + LOGGER.info(" - {}", elem.getName()); if (elem == this.init) { throw new MigrationException("Add a migration that is the initialization migration"); } @@ -142,6 +152,7 @@ public class MigrationEngine { LOGGER.info("DB '{}' exist.", config.getDbName()); // STEP 2: Check migration table exist: LOGGER.info("Verify existance of migration table '{}'", "KAR_migration"); + // TODO: set the class in parameters instead of string... exist = DataAccess.isTableExist("KAR_migration"); if (!exist) { LOGGER.info("'{}' Does not exist create a new one...", "KAR_migration"); @@ -180,18 +191,22 @@ public class MigrationEngine { throw new MigrationException("An error occured in the last migration: '" + currentVersion.name + "' defect @" + currentVersion.stepId + "/" + currentVersion.count); } LOGGER.info("Upgrade the system Current version: {}", currentVersion.name); - boolean find = this.init != null && this.init.getName() == currentVersion.name; - if (currentVersion.name.equals(this.init.getName())) { + boolean find = this.init != null && this.init.getName().equals(currentVersion.name); + if (find) { toApply = this.datas; } else { - for (int iii = 0; iii < this.datas.size(); iii++) { + LOGGER.info(" ===> Check what must be apply:"); + for (final MigrationInterface elem : this.datas) { + LOGGER.info(" - {}", elem.getName()); if (!find) { - if (this.datas.get(iii).getName() == currentVersion.name) { + if (currentVersion.name.equals(elem.getName())) { + LOGGER.info(" == current version"); find = true; } continue; } - toApply.add(this.datas.get(iii)); + LOGGER.info(" ++ add "); + toApply.add(elem); } } } @@ -241,13 +256,18 @@ public class MigrationEngine { migrationResult.name = elem.getName(); migrationResult.stepId = 0; migrationResult.terminated = false; - migrationResult.count = elem.getNumberOfStep(); + try { + migrationResult.count = elem.getNumberOfStep(); + } catch (final Exception e) { + e.printStackTrace(); + throw new MigrationException("Fail to get number of migration step (maybe generation fail): " + e.getLocalizedMessage()); + } migrationResult.log = log.toString(); try { migrationResult = DataAccess.insert(migrationResult); } catch (final Exception e) { - // TODO Auto-generated catch block e.printStackTrace(); + throw new MigrationException("Fail to insert migration Log in the migration table: " + e.getLocalizedMessage()); } boolean ret = true; try { @@ -256,7 +276,7 @@ public class MigrationEngine { log.append("\nFail in the migration apply "); log.append(e.getLocalizedMessage()); e.printStackTrace(); - throw new MigrationException("Migration fial: '" + migrationResult.name + "' defect @" + migrationResult.stepId + "/" + migrationResult.count); + throw new MigrationException("Migration fail: '" + migrationResult.name + "' defect @" + migrationResult.stepId + "/" + migrationResult.count); } if (ret) { migrationResult.terminated = true; @@ -264,6 +284,7 @@ public class MigrationEngine { DataAccess.update(migrationResult, migrationResult.id, List.of("terminated")); } catch (final Exception e) { e.printStackTrace(); + throw new MigrationException("Fail to update migration Log in the migration table: " + e.getLocalizedMessage()); } } else { try { @@ -272,13 +293,15 @@ public class MigrationEngine { DataAccess.update(migrationResult, migrationResult.id, List.of("log")); } catch (final Exception e) { e.printStackTrace(); + throw new MigrationException("Fail to update migration Log in the migration table: " + e.getLocalizedMessage() + " WITH: An error occured in the migration (OUTSIDE detection): '" + + migrationResult.name + "' defect @" + migrationResult.stepId + "/" + migrationResult.count); } throw new MigrationException("An error occured in the migration (OUTSIDE detection): '" + migrationResult.name + "' defect @" + migrationResult.stepId + "/" + migrationResult.count); } LOGGER.info("Migrate: [{}/{}] {} [ END ]", id, count, elem.getName()); } - public void revertTo(final DBEntry entry, final String migrationName) { + public void revertTo(final DBEntry entry, final String migrationName) throws MigrationException { final Migration currentVersion = getCurrentVersion(); final List toApply = new ArrayList<>(); boolean find = false; diff --git a/src/org/kar/archidata/migration/MigrationInterface.java b/src/org/kar/archidata/migration/MigrationInterface.java index 7002fde..f98cd35 100644 --- a/src/org/kar/archidata/migration/MigrationInterface.java +++ b/src/org/kar/archidata/migration/MigrationInterface.java @@ -23,5 +23,5 @@ public interface MigrationInterface { /** Get the number of step in the migration process. * @return count of SQL access. */ - int getNumberOfStep(); + int getNumberOfStep() throws Exception; } diff --git a/src/org/kar/archidata/migration/MigrationSqlStep.java b/src/org/kar/archidata/migration/MigrationSqlStep.java index 1a3e3f8..e8be90f 100644 --- a/src/org/kar/archidata/migration/MigrationSqlStep.java +++ b/src/org/kar/archidata/migration/MigrationSqlStep.java @@ -26,13 +26,18 @@ record Action(String action, List filterDB) { public class MigrationSqlStep implements MigrationInterface { final static Logger LOGGER = LoggerFactory.getLogger(MigrationSqlStep.class); private final List actions = new ArrayList<>(); + private boolean isGenerated = false; @Override public String getName() { return getClass().getCanonicalName(); } - public void display() { + public void display() throws Exception { + if (!this.isGenerated) { + this.isGenerated = true; + generateStep(); + } for (int iii = 0; iii < this.actions.size(); iii++) { final Action action = this.actions.get(iii); LOGGER.info(" >>>> SQL ACTION : {}/{} ==> filter='{}'\n{}", iii, this.actions.size(), action.filterDB(), action.action()); @@ -49,7 +54,10 @@ public class MigrationSqlStep implements MigrationInterface { @Override public boolean applyMigration(final DBEntry entry, final StringBuilder log, final Migration model) throws Exception { - generateStep(); + if (!this.isGenerated) { + this.isGenerated = true; + generateStep(); + } for (int iii = 0; iii < this.actions.size(); iii++) { log.append("action [" + (iii + 1) + "/" + this.actions.size() + "]\n"); LOGGER.info(" >>>> SQL ACTION : {}/{}", iii + 1, this.actions.size()); @@ -128,7 +136,11 @@ public class MigrationSqlStep implements MigrationInterface { } @Override - public int getNumberOfStep() { + public int getNumberOfStep() throws Exception { + if (!this.isGenerated) { + this.isGenerated = true; + generateStep(); + } return this.actions.size(); } diff --git a/src/org/kar/archidata/migration/model/Migration.java b/src/org/kar/archidata/migration/model/Migration.java index b1fe808..7c34802 100644 --- a/src/org/kar/archidata/migration/model/Migration.java +++ b/src/org/kar/archidata/migration/model/Migration.java @@ -29,5 +29,6 @@ public class Migration extends GenericDataSoftDelete { @DataComment("number of element in the migration") public Integer count; @DataComment("Log generate by the migration") + @Column(length = 0) public String log = ""; } diff --git a/src/org/kar/archidata/model/GenericToken.java b/src/org/kar/archidata/model/GenericToken.java index 312ed8d..fb976ef 100644 --- a/src/org/kar/archidata/model/GenericToken.java +++ b/src/org/kar/archidata/model/GenericToken.java @@ -15,10 +15,10 @@ import jakarta.persistence.Table; public class GenericToken extends GenericDataSoftDelete { @Column(nullable = false) public Long parentId; - @Column(nullable = false) + @Column(nullable = false, length = 0) public String name; @Column(nullable = false) public Timestamp endValidityTime = null; - @Column(nullable = false) + @Column(nullable = false, length = 0) public String token; }