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; }