diff --git a/.classpath b/.classpath
index dd85e60..f6aec1b 100644
--- a/.classpath
+++ b/.classpath
@@ -25,21 +25,24 @@
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java
index 6178648..dea7c1b 100644
--- a/src/org/kar/archidata/annotation/AnnotationTools.java
+++ b/src/org/kar/archidata/annotation/AnnotationTools.java
@@ -53,7 +53,40 @@ public class AnnotationTools {
return tmp;
}
- public static String getSchemedescription(final Field element) throws Exception {
+ public static boolean getSchemaReadOnly(final Field element) throws Exception {
+ final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
+ if (annotation.length == 0) {
+ return false;
+ }
+ if (annotation.length > 1) {
+ throw new Exception("Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
+ }
+ return ((Schema) annotation[0]).readOnly();
+ }
+
+ public static String getSchemaExample(final Class> element) throws Exception {
+ final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
+ if (annotation.length == 0) {
+ return null;
+ }
+ if (annotation.length > 1) {
+ throw new Exception("Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
+ }
+ return ((Schema) annotation[0]).example();
+ }
+
+ public static String getSchemaDescription(final Class> element) throws Exception {
+ final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
+ if (annotation.length == 0) {
+ return null;
+ }
+ if (annotation.length > 1) {
+ throw new Exception("Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
+ }
+ return ((Schema) annotation[0]).description();
+ }
+
+ public static String getSchemaDescription(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return null;
@@ -67,7 +100,7 @@ public class AnnotationTools {
public static String getComment(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataComment.class);
if (annotation.length == 0) {
- return getSchemedescription(element);
+ return getSchemaDescription(element);
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @DataComment on " + element.getClass().getCanonicalName());
diff --git a/src/org/kar/archidata/dataAccess/DataAccess.java b/src/org/kar/archidata/dataAccess/DataAccess.java
index e0f1429..2f4f3aa 100644
--- a/src/org/kar/archidata/dataAccess/DataAccess.java
+++ b/src/org/kar/archidata/dataAccess/DataAccess.java
@@ -309,11 +309,11 @@ public class DataAccess {
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);
+ // final UUID tmp = rs.getObject(count.value, UUID.class);
if (rs.wasNull()) {
field.set(data, null);
} else {
- //field.set(data, tmp);
+ // field.set(data, tmp);
final UUID uuid = UuidUtils.asUuid(tmp);
field.set(data, uuid);
countNotNull.inc();
@@ -491,11 +491,11 @@ public class DataAccess {
return (final ResultSet rs, final Object obj) -> {
final byte[] tmp = rs.getBytes(count);
- //final UUID tmp = rs.getObject(count, UUID.class);
+ // final UUID tmp = rs.getObject(count, UUID.class);
if (rs.wasNull()) {
field.set(obj, null);
} else {
- //field.set(obj, tmp);
+ // field.set(obj, tmp);
final UUID uuid = UuidUtils.asUuid(tmp);
field.set(obj, uuid);
}
@@ -852,7 +852,7 @@ public class DataAccess {
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
if (primaryKeyField.getType() == UUID.class) {
- //uniqueSQLUUID = generatedKeys.getObject(1, UUID.class);
+ // uniqueSQLUUID = generatedKeys.getObject(1, UUID.class);
final byte[] tmpid = generatedKeys.getBytes(1);
uniqueSQLUUID = UuidUtils.asUuid(tmpid);
} else {
diff --git a/src/org/kar/archidata/dataAccess/DataFactoryZod.java b/src/org/kar/archidata/dataAccess/DataFactoryZod.java
new file mode 100644
index 0000000..9a2a0d2
--- /dev/null
+++ b/src/org/kar/archidata/dataAccess/DataFactoryZod.java
@@ -0,0 +1,247 @@
+package org.kar.archidata.dataAccess;
+
+import java.io.FileWriter;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.kar.archidata.annotation.AnnotationTools;
+import org.kar.archidata.exception.DataAccessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DataFactoryZod {
+ static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryZod.class);
+
+ public static String convertTypeZod(final Class> type) throws Exception {
+ if (type == UUID.class) {
+ return "string()";
+ }
+ if (type == Long.class) {
+ return "bigint()";
+ }
+ if (type == long.class) {
+ return "bigint()";
+ }
+ if (type == Integer.class || type == int.class) {
+ return "number().safe()";
+ }
+ if (type == Boolean.class || type == boolean.class) {
+ return "boolean()";
+ }
+ if (type == double.class || type == float.class || type == Double.class || type == Float.class) {
+ return "number()";
+ }
+ if (type == Instant.class) {
+ return "string().utc()";
+ }
+ if (type == Date.class || type == Timestamp.class) {
+ return "date()";
+ }
+ if (type == LocalDate.class) {
+ return "date()";
+ }
+ if (type == LocalTime.class) {
+ return "date()";
+ }
+ if (type == String.class) {
+ return "string()";
+ }
+ if (type.isEnum()) {
+ final Object[] arr = type.getEnumConstants();
+ final StringBuilder out = new StringBuilder();
+ boolean first = true;
+ out.append("enum([");
+ for (final Object elem : arr) {
+ if (!first) {
+ out.append(",");
+ }
+ first = false;
+ out.append("\"");
+ out.append(elem.toString());
+ out.append("\"");
+ }
+ out.append("])");
+ return out.toString();
+ }
+ throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName());
+ }
+
+ public static String optionalTypeZod(final Class> type) throws Exception {
+ if (type.isEnum() || type == UUID.class || type == Long.class || type == Integer.class || type == Boolean.class | type == Double.class || type == Float.class || type == Instant.class
+ || type == Date.class || type == Timestamp.class || type == LocalDate.class || type == LocalTime.class || type == String.class) {
+ return ".optional()";
+ }
+ return "";
+ }
+
+ public static void createTablesSpecificType(final Field elem, final int fieldId, final StringBuilder builder) throws Exception {
+ final String name = elem.getName();
+ final Class> classModel = elem.getType();
+ final int limitSize = AnnotationTools.getLimitSize(elem);
+
+ final String comment = AnnotationTools.getComment(elem);
+
+ if (fieldId != 0) {
+ builder.append(",");
+ }
+ if (comment != null) {
+ builder.append("\n\t// ");
+ builder.append(comment);
+ }
+ builder.append("\n\t");
+ builder.append(name);
+ builder.append(": zod.");
+ builder.append(convertTypeZod(classModel));
+ if (limitSize > 0 && classModel == String.class) {
+ builder.append(".max(");
+ builder.append(limitSize);
+ builder.append(")");
+ }
+ if (AnnotationTools.getSchemaReadOnly(elem)) {
+ builder.append(".readonly()");
+ }
+ builder.append(optionalTypeZod(classModel));
+ }
+
+ private static boolean isFieldFromSuperClass(final Class> model, final String filedName) {
+ final Class> superClass = model.getSuperclass();
+ if (superClass == null) {
+ return false;
+ }
+ for (final Field field : superClass.getFields()) {
+ String name;
+ try {
+ name = AnnotationTools.getFieldName(field);
+ if (filedName.equals(name)) {
+ return true;
+ }
+ } catch (final Exception e) {
+ // TODO Auto-generated catch block
+ LOGGER.trace("Catch error field name in parent create data table: {}", e.getMessage());
+ }
+ }
+ return false;
+ }
+
+ /** Request the generation of the TypeScript file for the "Zod" export model
+ * @param classs List of class used in the model
+ * @return A string representing the Server models
+ * @throws Exception */
+ public static String createTables(final List> classs) throws Exception {
+ final Map previousClassesGenerated = new LinkedHashMap<>();
+ final List order = new ArrayList<>();
+ for (final Class> clazz : classs) {
+ createTable(clazz, previousClassesGenerated, order);
+ }
+ final StringBuilder generatedData = new StringBuilder();
+ generatedData.append("""
+ /**
+ * Interface of the server (auto-generated code)
+ */
+ import { z as zod } from \"zod\";
+
+ """);
+ for (final String elem : order) {
+ final String data = previousClassesGenerated.get(elem);
+ generatedData.append(data);
+ generatedData.append("\n\n");
+ }
+ LOGGER.info("generated: {}", generatedData.toString());
+ final FileWriter myWriter = new FileWriter("api.ts");
+ myWriter.write(generatedData.toString());
+ myWriter.close();
+ return generatedData.toString();
+ }
+
+ public static void createTable(final Class> clazz, final Map previousClassesGenerated, final List order) throws Exception {
+ if (previousClassesGenerated.get(clazz.getCanonicalName()) != null) {
+ return;
+ }
+ // add the current class to prevent multiple creation
+ previousClassesGenerated.put(clazz.getCanonicalName(), "In Generation");
+ // Local generation of class:
+ final StringBuilder internalBuilder = new StringBuilder();
+ final List alreadyAdded = new ArrayList<>();
+ LOGGER.trace("parse class: '{}'", clazz.getCanonicalName());
+ int fieldId = 0;
+ 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 String dataName = elem.getName();
+ if (isFieldFromSuperClass(clazz, dataName)) {
+ LOGGER.trace(" SKIP: '{}'", elem.getName());
+ continue;
+ }
+ if (alreadyAdded.contains(dataName)) {
+ LOGGER.trace(" SKIP2: '{}'", elem.getName());
+ continue;
+ }
+ alreadyAdded.add(dataName);
+ LOGGER.trace(" + '{}'", elem.getName());
+ if (DataAccess.isAddOnField(elem)) {
+ final DataAccessAddOn addOn = DataAccess.findAddOnforField(elem);
+ LOGGER.error("Create type for: {} ==> {} (ADD-ON) ==> Not managed now ....", AnnotationTools.getFieldName(elem), elem.getType());
+ /* LOGGER.trace("Create type for: {} ==> {} (ADD-ON)", AnnotationTools.getFieldName(elem), elem.getType()); if (addOn != null) { addOn.createTables(tableName, elem, tmpOut,
+ * preActionList, postActionList, createIfNotExist, createDrop, fieldId); } else { throw new DataAccessException( "Element matked as add-on but add-on does not loaded: table:" +
+ * tableName + " field name=" + AnnotationTools.getFieldName(elem) + " type=" + elem.getType()); } fieldId++; */
+ } else {
+ LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType());
+ DataFactoryZod.createTablesSpecificType(elem, fieldId, internalBuilder);
+ fieldId++;
+ }
+
+ }
+ final String description = AnnotationTools.getSchemaDescription(clazz);
+ final String example = AnnotationTools.getSchemaExample(clazz);
+ final StringBuilder generatedData = new StringBuilder();
+ if (description != null || example != null) {
+ generatedData.append("/**\n");
+ if (description != null) {
+ for (final String elem : description.split("\n")) {
+ generatedData.append(" * ");
+ generatedData.append(elem);
+ generatedData.append("\n");
+ }
+ }
+ if (example != null) {
+ generatedData.append(" * Example:\n");
+ generatedData.append(" * ```\n");
+ for (final String elem : example.split("\n")) {
+ generatedData.append(" * ");
+ generatedData.append(elem);
+ generatedData.append("\n");
+ }
+ generatedData.append(" * ```\n");
+ }
+ generatedData.append(" */\n");
+ }
+ generatedData.append("export const ");
+ generatedData.append(clazz.getSimpleName());
+ generatedData.append(" = ");
+ final Class> parentClass = clazz.getSuperclass();
+ if (parentClass != Object.class) {
+ createTable(parentClass, previousClassesGenerated, order);
+ generatedData.append(parentClass.getSimpleName());
+ generatedData.append(".extend({");
+ } else {
+ generatedData.append("zod.object({");
+ }
+ generatedData.append(internalBuilder.toString());
+ generatedData.append("\n});");
+ // Remove the previous to reorder the map ==> parent must be inserted before us.
+ previousClassesGenerated.put(clazz.getCanonicalName(), generatedData.toString());
+ order.add(clazz.getCanonicalName());
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/kar/archidata/migration/MigrationSqlStep.java b/src/org/kar/archidata/migration/MigrationSqlStep.java
index 4a9f6b3..8f890c6 100644
--- a/src/org/kar/archidata/migration/MigrationSqlStep.java
+++ b/src/org/kar/archidata/migration/MigrationSqlStep.java
@@ -21,6 +21,7 @@ record Action(String action, AsyncCall async, List filterDB) {
public Action(final String action, final String filterDB) {
this(action, null, List.of(filterDB));
}
+
public Action(final AsyncCall async) {
this(null, async, List.of());
}
@@ -142,6 +143,7 @@ public class MigrationSqlStep implements MigrationInterface {
public void addAction(final String action) {
this.actions.add(new Action(action));
}
+
public void addAction(final AsyncCall async) {
this.actions.add(new Action(async));
}
@@ -149,6 +151,7 @@ public class MigrationSqlStep implements MigrationInterface {
public void addAction(final String action, final String filterdBType) {
this.actions.add(new Action(action, filterdBType));
}
+
public void addAction(final AsyncCall async, final String filterdBType) {
this.actions.add(new Action(async, filterdBType));
}
diff --git a/src/org/kar/archidata/model/GenericTiming.java b/src/org/kar/archidata/model/GenericTiming.java
index 30ec625..0bbcd60 100644
--- a/src/org/kar/archidata/model/GenericTiming.java
+++ b/src/org/kar/archidata/model/GenericTiming.java
@@ -10,15 +10,12 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
-import jakarta.persistence.Temporal;
-import jakarta.persistence.TemporalType;
import jakarta.validation.constraints.NotNull;
public class GenericTiming {
@DataNotRead
@CreationTimestamp
@Column(nullable = false)
- @Temporal(TemporalType.TIMESTAMP)
@NotNull
@Schema(description = "Create time of the object", required = false, example = "2000-01-23T01:23:45.678+01:00", readOnly = true)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@@ -26,10 +23,9 @@ public class GenericTiming {
@DataNotRead
@UpdateTimestamp
@Column(nullable = false)
- @Temporal(TemporalType.TIMESTAMP)
@NotNull
@Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
- //public Instant updatedAt = null;
+ // public Instant updatedAt = null;
public Date updatedAt = null;
}