diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index 60b0c98..9823313 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -2,6 +2,7 @@ package org.kar.archidata.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; @@ -41,6 +42,24 @@ import jakarta.ws.rs.DefaultValue; public class AnnotationTools { static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class); + public static TYPE get(final Parameter param, final Class clazz) { + final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz); + + if (annotations.length == 0) { + return null; + } + return annotations[0]; + } + + public static TYPE[] gets(final Parameter param, final Class clazz) { + final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz); + + if (annotations.length == 0) { + return null; + } + return annotations; + } + public static TYPE get(final Field element, final Class clazz) { final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz); @@ -59,6 +78,24 @@ public class AnnotationTools { return annotations; } + public static TYPE get(final Class classObject, final Class clazz) { + final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz); + + if (annotations.length == 0) { + return null; + } + return annotations[0]; + } + + public static TYPE[] gets(final Class classObject, final Class clazz) { + final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz); + + if (annotations.length == 0) { + return null; + } + return annotations; + } + // For SQL declaration table Name public static String getTableName(final Class clazz, final QueryOptions options) throws DataAccessException { if (options != null) { @@ -121,14 +158,6 @@ public class AnnotationTools { return get(element, CollectionNotEmpty.class); } - public static boolean getSchemaReadOnly(final Field element) { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); - if (annotation.length == 0) { - return false; - } - return ((Schema) annotation[0]).readOnly(); - } - public static String getSchemaExample(final Class element) { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); if (annotation.length == 0) { @@ -137,14 +166,6 @@ public class AnnotationTools { return ((Schema) annotation[0]).example(); } - public static boolean getNoWriteSpecificMode(final Class element) { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(NoWriteSpecificMode.class); - if (annotation.length == 0) { - return false; - } - return true; - } - public static String getSchemaDescription(final Class element) { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); if (annotation.length == 0) { diff --git a/src/org/kar/archidata/annotation/NoWriteSpecificMode.java b/src/org/kar/archidata/annotation/NoWriteSpecificMode.java deleted file mode 100644 index fcad39d..0000000 --- a/src/org/kar/archidata/annotation/NoWriteSpecificMode.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.kar.archidata.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * The NoWriteSpecificMode annotation is used to indicate that there is no - * specific API for write mode when generating code for other languages. This - * annotation is particularly useful in code generators for client libraries - * where a separate, reduced data structure for write operations is not needed. - * - *

Usage: - * - Target: This annotation can be applied to class types. - * - Retention: The annotation is retained at runtime, allowing it to be - * processed by frameworks or libraries that handle code generation logic. - * - *

Behavior: - * - When applied to a class, the NoWriteSpecificMode annotation specifies - * that the class does not require a separate API or data structure for - * write operations. This can simplify the generated code by avoiding the - * creation of redundant structures. - * - *

Example: - *

{@code
- * @NoWriteSpecificMode
- * public class User {
- *     public String username;
- *     public String email;
- * }
- * }
- * - * In this example, the User class will not generate a separate API or data - * structure for write operations in the client code. - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface NoWriteSpecificMode { - -} diff --git a/src/org/kar/archidata/annotation/apiGenerator/ApiAccessLimitation.java b/src/org/kar/archidata/annotation/apiGenerator/ApiAccessLimitation.java new file mode 100644 index 0000000..e2a7cf0 --- /dev/null +++ b/src/org/kar/archidata/annotation/apiGenerator/ApiAccessLimitation.java @@ -0,0 +1,21 @@ +package org.kar.archidata.annotation.apiGenerator; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(FIELD) +public @interface ApiAccessLimitation { + /** + * (Optional) The field is accessible in creation (POST) + */ + boolean creatable() default true; + + /** + * (Optional) The field is accessible in update mode (PUT, PATCH) + */ + boolean updatable() default true; +} diff --git a/src/org/kar/archidata/annotation/AsyncType.java b/src/org/kar/archidata/annotation/apiGenerator/ApiAsyncType.java similarity index 85% rename from src/org/kar/archidata/annotation/AsyncType.java rename to src/org/kar/archidata/annotation/apiGenerator/ApiAsyncType.java index 199ce7a..aecc816 100644 --- a/src/org/kar/archidata/annotation/AsyncType.java +++ b/src/org/kar/archidata/annotation/apiGenerator/ApiAsyncType.java @@ -1,4 +1,4 @@ -package org.kar.archidata.annotation; +package org.kar.archidata.annotation.apiGenerator; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -8,7 +8,7 @@ import java.lang.annotation.Target; /** In case of the update parameter with String input to detect null element. */ @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface AsyncType { +public @interface ApiAsyncType { // Possible class values. Class[] value(); diff --git a/src/org/kar/archidata/annotation/apiGenerator/ApiGenerationMode.java b/src/org/kar/archidata/annotation/apiGenerator/ApiGenerationMode.java new file mode 100644 index 0000000..7af2878 --- /dev/null +++ b/src/org/kar/archidata/annotation/apiGenerator/ApiGenerationMode.java @@ -0,0 +1,57 @@ +package org.kar.archidata.annotation.apiGenerator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The ApiGenerationMode annotation is used to indicate the generation mode for + * API operations when producing code for other languages. This annotation is + * particularly useful in code generators for client libraries where specific + * data structures for read, create, and update operations may or may not be needed. + * + *

Usage: + * - Target: This annotation can be applied to class types. + * - Retention: The annotation is retained at runtime, allowing it to be + * processed by frameworks or libraries that handle code generation logic. + * + *

Behavior: + * - When applied to a class, the ApiGenerationMode annotation specifies + * which API operations (read, create, update) should generate specific + * data structures. This can simplify the generated code by avoiding the + * creation of unnecessary structures. + * + *

Example: + *

{@code
+ * @ApiGenerationMode(creatable=false, updatable=false)
+ * public class User {
+ *     public String username;
+ *     public String email;
+ * }
+ * }
+ * + * In this example, the User class will not generate separate data structures + * for create and update operations in the client code, only for read operations. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiGenerationMode { + /** + * (Optional) Enable the generation of specific code for read access + * (generate object: MyClass). + */ + boolean read() default true; + + /** + * (Optional) Enable the generation of specific code for create access + * (generate object: MyClassCreate). + */ + boolean create() default false; + + /** + * (Optional) Enable the generation of specific code for update access + * (generate object: MyClassUpdate). + */ + boolean update() default false; +} diff --git a/src/org/kar/archidata/annotation/FormDataOptional.java b/src/org/kar/archidata/annotation/apiGenerator/ApiInputOptional.java similarity index 89% rename from src/org/kar/archidata/annotation/FormDataOptional.java rename to src/org/kar/archidata/annotation/apiGenerator/ApiInputOptional.java index a724c7e..c280b80 100644 --- a/src/org/kar/archidata/annotation/FormDataOptional.java +++ b/src/org/kar/archidata/annotation/apiGenerator/ApiInputOptional.java @@ -1,4 +1,4 @@ -package org.kar.archidata.annotation; +package org.kar.archidata.annotation.apiGenerator; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,9 +32,9 @@ import java.lang.annotation.Target; * @Operation(description = "Add a cover on a specific album") * @TypeScriptProgress * public Album uploadCover(@PathParam("id") final Long id, - * @FormDataOptional @FormDataParam("uri") final String uri, - * @FormDataOptional @FormDataParam("file") final InputStream fileInputStream, - * @FormDataOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData) + * @ApiInputOptional @FormDataParam("uri") final String uri, + * @ApiInputOptional @FormDataParam("file") final InputStream fileInputStream, + * @ApiInputOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData) * throws Exception { * // some code * } @@ -71,6 +71,6 @@ import java.lang.annotation.Target; */ @Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) -public @interface FormDataOptional { +public @interface ApiInputOptional { } diff --git a/src/org/kar/archidata/annotation/TypeScriptProgress.java b/src/org/kar/archidata/annotation/apiGenerator/ApiTypeScriptProgress.java similarity index 96% rename from src/org/kar/archidata/annotation/TypeScriptProgress.java rename to src/org/kar/archidata/annotation/apiGenerator/ApiTypeScriptProgress.java index d766f44..b9f57b8 100644 --- a/src/org/kar/archidata/annotation/TypeScriptProgress.java +++ b/src/org/kar/archidata/annotation/apiGenerator/ApiTypeScriptProgress.java @@ -1,4 +1,4 @@ -package org.kar.archidata.annotation; +package org.kar.archidata.annotation.apiGenerator; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -68,4 +68,4 @@ import java.lang.annotation.Target; */ @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface TypeScriptProgress {} +public @interface ApiTypeScriptProgress {} diff --git a/src/org/kar/archidata/api/DataResource.java b/src/org/kar/archidata/api/DataResource.java index 13dc192..20cfc7f 100644 --- a/src/org/kar/archidata/api/DataResource.java +++ b/src/org/kar/archidata/api/DataResource.java @@ -24,6 +24,7 @@ import javax.imageio.ImageIO; import org.bson.types.ObjectId; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; +import org.kar.archidata.annotation.apiGenerator.ApiInputOptional; import org.kar.archidata.annotation.security.PermitTokenInURI; import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.QueryCondition; @@ -426,7 +427,7 @@ public class DataResource { public Response retrieveDataFull( @Context final SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) final String token, - @HeaderParam("Range") final String range, + @ApiInputOptional @HeaderParam("Range") final String range, @PathParam("oid") final ObjectId oid, @PathParam("name") final String name) throws Exception { final GenericContext gc = (GenericContext) sc.getUserPrincipal(); diff --git a/src/org/kar/archidata/catcher/RestErrorResponse.java b/src/org/kar/archidata/catcher/RestErrorResponse.java index 1405e93..5dd7043 100644 --- a/src/org/kar/archidata/catcher/RestErrorResponse.java +++ b/src/org/kar/archidata/catcher/RestErrorResponse.java @@ -3,13 +3,13 @@ package org.kar.archidata.catcher; import java.time.Instant; import org.bson.types.ObjectId; -import org.kar.archidata.annotation.NoWriteSpecificMode; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import jakarta.persistence.Column; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.core.Response; -@NoWriteSpecificMode +@ApiGenerationMode public class RestErrorResponse { public ObjectId oid = new ObjectId(); @NotNull diff --git a/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java b/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java index e1700a6..fc2b459 100644 --- a/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java +++ b/src/org/kar/archidata/externalRestApi/dot/DotClassElement.java @@ -151,13 +151,6 @@ public class DotClassElement { return ".optional()"; } - public String readOnlyZod(final FieldProperty field) { - if (field.readOnly()) { - return ".readonly()"; - } - return ""; - } - public String generateBaseObject() { final StringBuilder out = new StringBuilder(); return out.toString(); diff --git a/src/org/kar/archidata/externalRestApi/model/ApiModel.java b/src/org/kar/archidata/externalRestApi/model/ApiModel.java index 180e000..6bb35d6 100644 --- a/src/org/kar/archidata/externalRestApi/model/ApiModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ApiModel.java @@ -9,9 +9,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.kar.archidata.annotation.AnnotationTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.core.Context; + public class ApiModel { static final Logger LOGGER = LoggerFactory.getLogger(ApiModel.class); @@ -37,6 +41,8 @@ public class ApiModel { public String name; // list of all parameters (/{key}/... public final Map> parameters = new HashMap<>(); + // list of all headers of the request (/{key}/... + public final Map headers = new HashMap<>(); // list of all query (?key...) public final Map> queries = new HashMap<>(); // when request multi-part, need to separate it. @@ -153,11 +159,12 @@ public class ApiModel { } else { parameterModel.add(previousModel.add(parameterType)); } - + final Context contextAnnotation = AnnotationTools.get(parameter, Context.class); + final HeaderParam headerParam = AnnotationTools.get(parameter, HeaderParam.class); final String pathParam = ApiTool.apiAnnotationGetPathParam(parameter); final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter); final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter); - final boolean formDataParamOptional = ApiTool.apiAnnotationGetFormDataOptional(parameter); + final boolean apiInputOptional = ApiTool.apiAnnotationGetApiInputOptional(parameter); if (queryParam != null) { if (!this.queries.containsKey(queryParam)) { this.queries.put(queryParam, parameterModel); @@ -169,7 +176,13 @@ public class ApiModel { } else if (formDataParam != null) { if (!this.multiPartParameters.containsKey(formDataParam)) { this.multiPartParameters.put(formDataParam, - new OptionalClassModel(parameterModel, formDataParamOptional)); + new OptionalClassModel(parameterModel, apiInputOptional)); + } + } else if (contextAnnotation != null) { + // out of scope parameters + } else if (headerParam != null) { + if (!this.headers.containsKey(headerParam.value())) { + this.headers.put(headerParam.value(), new OptionalClassModel(parameterModel, apiInputOptional)); } } else { this.unnamedElement.addAll(parameterModel); diff --git a/src/org/kar/archidata/externalRestApi/model/ApiTool.java b/src/org/kar/archidata/externalRestApi/model/ApiTool.java index de45571..b24a84c 100644 --- a/src/org/kar/archidata/externalRestApi/model/ApiTool.java +++ b/src/org/kar/archidata/externalRestApi/model/ApiTool.java @@ -7,9 +7,9 @@ import java.util.Arrays; import java.util.List; import org.glassfish.jersey.media.multipart.FormDataParam; -import org.kar.archidata.annotation.AsyncType; -import org.kar.archidata.annotation.FormDataOptional; -import org.kar.archidata.annotation.TypeScriptProgress; +import org.kar.archidata.annotation.apiGenerator.ApiInputOptional; +import org.kar.archidata.annotation.apiGenerator.ApiAsyncType; +import org.kar.archidata.annotation.apiGenerator.ApiTypeScriptProgress; import org.kar.archidata.annotation.method.ARCHIVE; import org.kar.archidata.annotation.method.RESTORE; @@ -53,7 +53,7 @@ public class ApiTool { } public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class); + final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiTypeScriptProgress.class); if (annotation.length == 0) { return false; } @@ -159,8 +159,8 @@ public class ApiTool { return ((QueryParam) annotation[0]).value(); } - public static boolean apiAnnotationGetFormDataOptional(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataOptional.class); + public static boolean apiAnnotationGetApiInputOptional(final Parameter element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiInputOptional.class); if (annotation.length == 0) { return false; } @@ -176,19 +176,19 @@ public class ApiTool { } public static Class[] apiAnnotationGetAsyncType(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); + final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiAsyncType.class); if (annotation.length == 0) { return null; } - return ((AsyncType) annotation[0]).value(); + return ((ApiAsyncType) annotation[0]).value(); } public static Class[] apiAnnotationGetAsyncType(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); + final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiAsyncType.class); if (annotation.length == 0) { return null; } - return ((AsyncType) annotation[0]).value(); + return ((ApiAsyncType) annotation[0]).value(); } public static List apiAnnotationGetConsumes(final Method element) throws Exception { diff --git a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java index 665a35f..8483a12 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java @@ -6,11 +6,14 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -public class ClassEnumModel extends ClassModel { +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; +import org.kar.archidata.tools.AnnotationCreator; +public class ClassEnumModel extends ClassModel { protected ClassEnumModel(final Class clazz) { this.originClasses = clazz; - this.noWriteSpecificMode = true; + this.apiGenerationMode = AnnotationCreator.createAnnotation(ApiGenerationMode.class, "readable", true, + "creatable", false, "updatable", false); } @Override diff --git a/src/org/kar/archidata/externalRestApi/model/ClassModel.java b/src/org/kar/archidata/externalRestApi/model/ClassModel.java index 27ab71d..994be09 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassModel.java @@ -8,18 +8,24 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; +import org.kar.archidata.tools.AnnotationCreator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public abstract class ClassModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ClassModel.class); protected boolean analyzeDone = false; protected Class originClasses = null; - protected boolean noWriteSpecificMode = false; + protected ApiGenerationMode apiGenerationMode = AnnotationCreator.createAnnotation(ApiGenerationMode.class); protected List dependencyModels = new ArrayList<>(); public Class getOriginClasses() { return this.originClasses; } - public boolean isNoWriteSpecificMode() { - return this.noWriteSpecificMode; + public ApiGenerationMode getApiGenerationMode() { + return this.apiGenerationMode; } protected boolean isCompatible(final Class clazz) { @@ -76,7 +82,15 @@ public abstract class ClassModel { public abstract Set getAlls(); - public List getReadOnlyField() { + public List getReadOnlyFields() { + return List.of(); + } + + public List getCreateFields() { + return List.of(); + } + + public List getUpdateFields() { return List.of(); } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java index 9ee1634..79dd2a5 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java @@ -11,7 +11,10 @@ import java.util.List; import java.util.Set; import org.kar.archidata.annotation.AnnotationTools; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import org.kar.archidata.exception.DataAccessException; +import org.kar.archidata.tools.AnnotationCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +34,10 @@ public class ClassObjectModel extends ClassModel { public ClassObjectModel(final Class clazz) { this.originClasses = clazz; + final ApiGenerationMode tmp = AnnotationTools.get(clazz, ApiGenerationMode.class); + if (tmp != null) { + this.apiGenerationMode = tmp; + } } @Override @@ -74,15 +81,16 @@ public class ClassObjectModel extends ClassModel { DecimalMax decimalMax, Pattern pattern, Email email, - Boolean readOnly, + ApiAccessLimitation accessLimitation, Boolean notNull, Boolean columnNotNull, Boolean nullable) { public FieldProperty(final String name, final ClassModel model, final ClassModel linkClass, final String comment, final Size stringSize, final Min min, final Max max, final DecimalMin decimalMin, - final DecimalMax decimalMax, final Pattern pattern, final Email email, final Boolean readOnly, - final Boolean notNull, final Boolean columnNotNull, final Boolean nullable) { + final DecimalMax decimalMax, final Pattern pattern, final Email email, + final ApiAccessLimitation accessLimitation, final Boolean notNull, final Boolean columnNotNull, + final Boolean nullable) { this.name = name; this.model = model; this.linkClass = linkClass; @@ -94,7 +102,11 @@ public class ClassObjectModel extends ClassModel { this.email = email; this.min = min; this.max = max; - this.readOnly = readOnly; + if (accessLimitation == null) { + this.accessLimitation = AnnotationCreator.createAnnotation(ApiAccessLimitation.class); + } else { + this.accessLimitation = accessLimitation; + } this.notNull = notNull; this.columnNotNull = columnNotNull; this.nullable = nullable; @@ -146,7 +158,7 @@ public class ClassObjectModel extends ClassModel { AnnotationTools.getConstraintsDecimalMax(field), // AnnotationTools.getConstraintsPattern(field), // AnnotationTools.getConstraintsEmail(field), // - AnnotationTools.getSchemaReadOnly(field), // + AnnotationTools.get(field, ApiAccessLimitation.class), // AnnotationTools.getConstraintsNotNull(field), // AnnotationTools.getColumnNotNull(field), // AnnotationTools.getNullable(field)); @@ -192,7 +204,6 @@ public class ClassObjectModel extends ClassModel { } this.analyzeDone = true; final Class clazz = this.originClasses; - this.noWriteSpecificMode = AnnotationTools.getNoWriteSpecificMode(clazz); this.isPrimitive = clazz.isPrimitive(); if (this.isPrimitive) { return; @@ -259,15 +270,43 @@ public class ClassObjectModel extends ClassModel { } @Override - public List getReadOnlyField() { + public List getReadOnlyFields() { final List out = new ArrayList<>(); for (final FieldProperty field : this.fields) { - if (field.readOnly()) { + if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) { out.add(field.name); } } if (this.extendsClass != null) { - out.addAll(this.extendsClass.getReadOnlyField()); + out.addAll(this.extendsClass.getReadOnlyFields()); + } + return out; + } + + @Override + public List getCreateFields() { + final List out = new ArrayList<>(); + for (final FieldProperty field : this.fields) { + if (field.accessLimitation().creatable()) { + out.add(field.name); + } + } + if (this.extendsClass != null) { + out.addAll(this.extendsClass.getReadOnlyFields()); + } + return out; + } + + @Override + public List getUpdateFields() { + final List out = new ArrayList<>(); + for (final FieldProperty field : this.fields) { + if (field.accessLimitation().updatable()) { + out.add(field.name); + } + } + if (this.extendsClass != null) { + out.addAll(this.extendsClass.getReadOnlyFields()); } return out; } diff --git a/src/org/kar/archidata/externalRestApi/typescript/TsApiGeneration.java b/src/org/kar/archidata/externalRestApi/typescript/TsApiGeneration.java index 5207907..a4aca3a 100644 --- a/src/org/kar/archidata/externalRestApi/typescript/TsApiGeneration.java +++ b/src/org/kar/archidata/externalRestApi/typescript/TsApiGeneration.java @@ -45,7 +45,9 @@ public class TsApiGeneration { final ClassEnumModel model, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { imports.add(model); final TsClassElement tsModel = tsGroup.find(model); return tsModel.tsTypeName; @@ -55,34 +57,47 @@ public class TsApiGeneration { final ClassObjectModel model, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { final TsClassElement tsModel = tsGroup.find(model); if (tsModel.nativeType != DefinedPosition.NATIVE) { - if (importWrite == null || tsModel.models.get(0).isNoWriteSpecificMode()) { - imports.add(model); + if (importCreate != null && tsModel.models.get(0).getApiGenerationMode().create()) { + importCreate.add(model); + } else if (importUpdate != null && tsModel.models.get(0).getApiGenerationMode().update()) { + importUpdate.add(model); } else { - importWrite.add(model); + imports.add(model); } } + String out = tsModel.tsTypeName; if (tsModel.nativeType != DefinedPosition.NORMAL) { - return tsModel.tsTypeName; + out = tsModel.tsTypeName; + } else if (importCreate != null && tsModel.models.get(0).getApiGenerationMode().create()) { + out = tsModel.tsTypeName + TsClassElement.MODEL_TYPE_CREATE; + } else if (importUpdate != null && tsModel.models.get(0).getApiGenerationMode().update()) { + out = tsModel.tsTypeName + TsClassElement.MODEL_TYPE_UPDATE; } - if (importWrite != null && !tsModel.models.get(0).isNoWriteSpecificMode()) { - return tsModel.tsTypeName + "Write"; + if (partialObject) { + return "Partial<" + out + ">"; } - return tsModel.tsTypeName; + return out; } public static String generateClassMapModelTypescript( final ClassMapModel model, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { final StringBuilder out = new StringBuilder(); out.append("{[key: "); - out.append(generateClassModelTypescript(model.keyModel, tsGroup, imports, importWrite)); + out.append(generateClassModelTypescript(model.keyModel, tsGroup, imports, importUpdate, importCreate, + partialObject)); out.append("]: "); - out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importWrite)); + out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importUpdate, importCreate, + partialObject)); out.append(";}"); return out.toString(); } @@ -91,9 +106,12 @@ public class TsApiGeneration { final ClassListModel model, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { final StringBuilder out = new StringBuilder(); - out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importWrite)); + out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importUpdate, importCreate, + partialObject)); out.append("[]"); return out.toString(); } @@ -102,18 +120,24 @@ public class TsApiGeneration { final ClassModel model, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { if (model instanceof final ClassObjectModel objectModel) { - return generateClassObjectModelTypescript(objectModel, tsGroup, imports, importWrite); + return generateClassObjectModelTypescript(objectModel, tsGroup, imports, importUpdate, importCreate, + partialObject); } if (model instanceof final ClassListModel listModel) { - return generateClassListModelTypescript(listModel, tsGroup, imports, importWrite); + return generateClassListModelTypescript(listModel, tsGroup, imports, importUpdate, importCreate, + partialObject); } if (model instanceof final ClassMapModel mapModel) { - return generateClassMapModelTypescript(mapModel, tsGroup, imports, importWrite); + return generateClassMapModelTypescript(mapModel, tsGroup, imports, importUpdate, importCreate, + partialObject); } if (model instanceof final ClassEnumModel enumModel) { - return generateClassEnumModelTypescript(enumModel, tsGroup, imports, importWrite); + return generateClassEnumModelTypescript(enumModel, tsGroup, imports, importUpdate, importCreate, + partialObject); } throw new IOException("Impossible model:" + model); } @@ -122,7 +146,9 @@ public class TsApiGeneration { final List models, final TsClassElementGroup tsGroup, final Set imports, - final Set importWrite) throws IOException { + final Set importUpdate, + final Set importCreate, + final boolean partialObject) throws IOException { if (models.size() == 0) { return "void"; } @@ -134,7 +160,8 @@ public class TsApiGeneration { } else { out.append(" | "); } - final String data = generateClassModelTypescript(model, tsGroup, imports, importWrite); + final String data = generateClassModelTypescript(model, tsGroup, imports, importUpdate, importCreate, + partialObject); out.append(data); } return out.toString(); @@ -159,7 +186,8 @@ public class TsApiGeneration { final Set imports = new HashSet<>(); final Set zodImports = new HashSet<>(); final Set isImports = new HashSet<>(); - final Set writeImports = new HashSet<>(); + final Set updateImports = new HashSet<>(); + final Set createImports = new HashSet<>(); final Set toolImports = new HashSet<>(); for (final ApiModel interfaceElement : element.interfaces) { final List consumes = interfaceElement.consumes; @@ -172,6 +200,7 @@ public class TsApiGeneration { data.append("\n\n"); data.append(returnComplexModel.replaceAll("(?m)^", "\t")); for (final ClassModel elem : interfaceElement.returnTypes) { + // TODO maybe need to update this with the type of zod requested (like update, create ... zodImports.addAll(elem.getDependencyGroupModels()); } } @@ -195,6 +224,9 @@ public class TsApiGeneration { if (interfaceElement.unnamedElement.size() == 1 || interfaceElement.multiPartParameters.size() != 0) { data.append("\n\t\t\tdata,"); } + if (!interfaceElement.headers.isEmpty()) { + data.append("\n\t\t\theaders,"); + } if (needGenerateProgress) { data.append("\n\t\t\tcallbacks,"); } @@ -207,7 +239,8 @@ public class TsApiGeneration { data.append("\n\t\t\t"); data.append(queryEntry.getKey()); data.append("?: "); - data.append(generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, null)); + data.append( + generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, null, null, false)); data.append(","); } data.append("\n\t\t},"); @@ -218,15 +251,27 @@ public class TsApiGeneration { data.append("\n\t\t\t"); data.append(paramEntry.getKey()); data.append(": "); - data.append(generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, null)); + data.append( + generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, null, null, false)); data.append(","); } data.append("\n\t\t},"); } if (interfaceElement.unnamedElement.size() == 1) { data.append("\n\t\tdata: "); - data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, - writeImports)); + if (interfaceElement.restTypeRequest == RestTypeRequest.POST) { + data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, + null, createImports, false)); + } else if (interfaceElement.restTypeRequest == RestTypeRequest.PUT) { + data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, + updateImports, null, false)); + } else if (interfaceElement.restTypeRequest == RestTypeRequest.PATCH) { + data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, + updateImports, null, true)); + } else { + data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, + null, null, true)); + } data.append(","); } else if (interfaceElement.multiPartParameters.size() != 0) { data.append("\n\t\tdata: {"); @@ -238,8 +283,34 @@ public class TsApiGeneration { data.append("?"); } data.append(": "); - data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, - writeImports)); + if (interfaceElement.restTypeRequest == RestTypeRequest.POST) { + data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, null, + createImports, false)); + } else if (interfaceElement.restTypeRequest == RestTypeRequest.PUT) { + data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, + updateImports, null, false)); + } else if (interfaceElement.restTypeRequest == RestTypeRequest.PATCH) { + data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, + updateImports, null, true)); + } else { + data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, null, + null, true)); + } + data.append(","); + } + data.append("\n\t\t},"); + } + if (!interfaceElement.headers.isEmpty()) { + data.append("\n\t\theaders?: {"); + for (final Entry headerEntry : interfaceElement.headers.entrySet()) { + data.append("\n\t\t\t"); + data.append(headerEntry.getKey()); + if (headerEntry.getValue().optional()) { + data.append("?"); + } + data.append(": "); + data.append(generateClassModelsTypescript(headerEntry.getValue().model(), tsGroup, imports, null, + null, false)); data.append(","); } data.append("\n\t\t},"); @@ -287,7 +358,7 @@ public class TsApiGeneration { toolImports.add("RESTRequestJson"); } else { final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports, - null); + null, null, false); data.append(returnType); data.append("> {"); if ("void".equals(returnType)) { @@ -332,7 +403,7 @@ public class TsApiGeneration { data.append("\n\t\t\t\taccept: produce,"); } else { final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, - imports, null); + imports, null, null, false); if (!"void".equals(returnType)) { for (final String elem : produces) { if (MediaType.APPLICATION_JSON.equals(elem)) { @@ -360,6 +431,9 @@ public class TsApiGeneration { if (needGenerateProgress) { data.append("\n\t\t\tcallbacks,"); } + if (!interfaceElement.headers.isEmpty()) { + data.append("\n\t\t\theaders,"); + } data.append("\n\t\t}"); if (returnComplexModel != null) { data.append(", is"); @@ -419,17 +493,30 @@ public class TsApiGeneration { if (tsModel.nativeType == DefinedPosition.NATIVE) { continue; } + if (!tsModel.models.get(0).getApiGenerationMode().read()) { + continue; + } finalImportSet.add("Zod" + tsModel.tsTypeName); } - for (final ClassModel model : writeImports) { + for (final ClassModel model : updateImports) { final TsClassElement tsModel = tsGroup.find(model); if (tsModel.nativeType != DefinedPosition.NORMAL) { continue; } - if (tsModel.models.get(0).isNoWriteSpecificMode()) { + if (!tsModel.models.get(0).getApiGenerationMode().update()) { continue; } - finalImportSet.add(tsModel.tsTypeName + "Write"); + finalImportSet.add(tsModel.tsTypeName + TsClassElement.MODEL_TYPE_UPDATE); + } + for (final ClassModel model : createImports) { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType != DefinedPosition.NORMAL) { + continue; + } + if (!tsModel.models.get(0).getApiGenerationMode().create()) { + continue; + } + finalImportSet.add(tsModel.tsTypeName + TsClassElement.MODEL_TYPE_CREATE); } if (finalImportSet.size() != 0) { diff --git a/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java b/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java index 1ed885d..949960e 100644 --- a/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java +++ b/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java @@ -27,6 +27,9 @@ public class TsClassElement { NORMAL // Normal Object to interpret. } + public static final String MODEL_TYPE_UPDATE = "Update"; + public static final String MODEL_TYPE_CREATE = "Create"; + public List models; public String zodName; public String tsTypeName; @@ -138,7 +141,7 @@ public class TsClassElement { out.append(this.tsTypeName); out.append(");\n"); } - out.append(generateExportCheckFunctionWrite("")); + out.append(generateExportCheckFunctionAppended("")); return out.toString(); } @@ -168,9 +171,9 @@ public class TsClassElement { return out.toString(); } - private String generateExportCheckFunctionWrite(final String writeString) { - return generateExportCheckFunction(this.tsCheckType + writeString, this.tsTypeName + writeString, - this.zodName + writeString); + private String generateExportCheckFunctionAppended(final String appendString) { + return generateExportCheckFunction(this.tsCheckType + appendString, this.tsTypeName + appendString, + this.zodName + appendString); } public String generateImports(final List depModels, final TsClassElementGroup tsGroup) @@ -180,11 +183,23 @@ public class TsClassElement { final TsClassElement tsModel = tsGroup.find(depModel); if (tsModel.nativeType != DefinedPosition.NATIVE) { out.append("import {"); - out.append(tsModel.zodName); - if (tsModel.nativeType == DefinedPosition.NORMAL && !(tsModel.models.get(0).isNoWriteSpecificMode())) { + if (tsModel.nativeType != DefinedPosition.NORMAL + || tsModel.models.get(0).getApiGenerationMode().read()) { + out.append(tsModel.zodName); + } + if (tsModel.nativeType == DefinedPosition.NORMAL + && tsModel.models.get(0).getApiGenerationMode().update()) { out.append(", "); out.append(tsModel.zodName); - out.append("Write "); + out.append(MODEL_TYPE_UPDATE); + out.append(" "); + } + if (tsModel.nativeType == DefinedPosition.NORMAL + && tsModel.models.get(0).getApiGenerationMode().create()) { + out.append(", "); + out.append(tsModel.zodName); + out.append(MODEL_TYPE_CREATE); + out.append(" "); } out.append("} from \"./"); out.append(tsModel.fileName); @@ -321,7 +336,7 @@ public class TsClassElement { } public String readOnlyZod(final FieldProperty field) { - if (field.readOnly()) { + if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) { return ".readonly()"; } return ""; @@ -343,18 +358,32 @@ public class TsClassElement { public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder out = new StringBuilder(); + out.append(getBaseHeader()); out.append(generateImports(model.getDependencyModels(), tsGroup)); out.append("\n"); - // ------------------------------------------------------------------------ - // -- Generate read mode - // ------------------------------------------------------------------------ + + if (model.getApiGenerationMode().read()) { + out.append(generateObjectRead(model, tsGroup)); + } + if (model.getApiGenerationMode().update()) { + out.append(generateObjectUpdate(model, tsGroup)); + } + if (model.getApiGenerationMode().create()) { + out.append(generateObjectCreate(model, tsGroup)); + } + return out.toString(); + } + + public String generateObjectRead(final ClassObjectModel model, final TsClassElementGroup tsGroup) + throws IOException { + final StringBuilder out = new StringBuilder(); out.append(generateComment(model)); out.append("export const "); out.append(this.zodName); out.append(" = "); // Check if the object is empty: - boolean isEmpty = model.getFields().size() == 0; + final boolean isEmpty = model.getFields().size() == 0; if (model.getExtendsClass() != null) { final ClassModel parentClass = model.getExtendsClass(); @@ -392,98 +421,138 @@ public class TsClassElement { out.append(optionalTypeZod(field)); out.append(",\n"); } - final List omitField = model.getReadOnlyField(); if (model.getExtendsClass() != null && isEmpty) { out.append(";\n"); } else { out.append("\n});\n"); } out.append(generateZodInfer(this.tsTypeName, this.zodName)); - out.append(generateExportCheckFunctionWrite("")); - // check if we need to generate write mode : - if (!model.isNoWriteSpecificMode()) { - // ------------------------------------------------------------------------ - // -- Generate write mode - // ------------------------------------------------------------------------ - //out.append(generateComment(model)); - out.append("export const "); - out.append(this.zodName); - out.append("Write = "); - isEmpty = model.getFields().stream().filter(field -> !field.readOnly()).count() == 0; - if (model.getExtendsClass() != null) { - final ClassModel parentClass = model.getExtendsClass(); - final TsClassElement tsParentModel = tsGroup.find(parentClass); - out.append(tsParentModel.zodName); - out.append("Write"); - if (!isEmpty) { - out.append(".extend({\n"); - } - } else { - out.append("zod.object({\n"); + out.append(generateExportCheckFunctionAppended("")); + return out.toString(); + } + + public String generateObjectUpdate(final ClassObjectModel model, final TsClassElementGroup tsGroup) + throws IOException { + final StringBuilder out = new StringBuilder(); + final String modeleType = MODEL_TYPE_UPDATE; + out.append("export const "); + out.append(this.zodName); + out.append(modeleType); + out.append(" = "); + // Check if at minimum One fiend is updatable to generate the local object + final boolean isEmpty = model.getFields().stream().filter(field -> field.accessLimitation().updatable()) + .count() == 0; + if (model.getExtendsClass() != null) { + final ClassModel parentClass = model.getExtendsClass(); + final TsClassElement tsParentModel = tsGroup.find(parentClass); + out.append(tsParentModel.zodName); + out.append(modeleType); + if (!isEmpty) { + out.append(".extend({\n"); } - for (final FieldProperty field : model.getFields()) { - // remove all readOnly field - if (field.readOnly()) { - continue; - } - final ClassModel fieldModel = field.model(); - if (field.comment() != null) { - out.append("\t/**\n"); - out.append("\t * "); - out.append(field.comment()); - out.append("\n\t */\n"); - } - out.append("\t"); - out.append(field.name()); - out.append(": "); - if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) { - final TsClassElement tsFieldModel = tsGroup.find(fieldModel); - out.append(tsFieldModel.zodName); - } else if (fieldModel instanceof final ClassListModel fieldListModel) { - final String data = generateTsList(fieldListModel, tsGroup); - out.append(data); - } else if (fieldModel instanceof final ClassMapModel fieldMapModel) { - final String data = generateTsMap(fieldMapModel, tsGroup); - out.append(data); - } - out.append(maxSizeZod(field)); - out.append(optionalWriteTypeZod(field)); - // all write field are optional - if (field.model() instanceof final ClassObjectModel plop) { - if (!plop.isPrimitive()) { - out.append(".optional()"); - } - } else { - out.append(".optional()"); - } - out.append(",\n"); - } - if (model.getExtendsClass() != null && isEmpty) { - out.append(";\n"); - } else { - out.append("\n});\n"); - } - out.append(generateZodInfer(this.tsTypeName + "Write", this.zodName + "Write")); - // Check only the input value ==> no need of the output - out.append(generateExportCheckFunctionWrite("Write")); - // Generate the Write Type associated. - /* - out.append("\nexport const "); - out.append(this.zodName); - out.append("Write = "); - out.append(this.zodName); - if (omitField.size() != 0) { - out.append(".omit({\n"); - for (final String elem : omitField) { - out.append("\t"); - out.append(elem); - out.append(": true,\n"); - } - out.append("\n})"); - } - out.append(".partial();\n"); - */ + } else { + out.append("zod.object({\n"); } + for (final FieldProperty field : model.getFields()) { + // remove all readOnly field + if (!field.accessLimitation().updatable()) { + continue; + } + final ClassModel fieldModel = field.model(); + if (field.comment() != null) { + out.append("\t/**\n"); + out.append("\t * "); + out.append(field.comment()); + out.append("\n\t */\n"); + } + out.append("\t"); + out.append(field.name()); + out.append(": "); + if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) { + final TsClassElement tsFieldModel = tsGroup.find(fieldModel); + out.append(tsFieldModel.zodName); + } else if (fieldModel instanceof final ClassListModel fieldListModel) { + final String data = generateTsList(fieldListModel, tsGroup); + out.append(data); + } else if (fieldModel instanceof final ClassMapModel fieldMapModel) { + final String data = generateTsMap(fieldMapModel, tsGroup); + out.append(data); + } + out.append(maxSizeZod(field)); + out.append(optionalWriteTypeZod(field)); + out.append(optionalTypeZod(field)); + out.append(",\n"); + } + if (model.getExtendsClass() != null && isEmpty) { + out.append(";\n"); + } else { + out.append("\n});\n"); + } + out.append(generateZodInfer(this.tsTypeName + modeleType, this.zodName + modeleType)); + // Check only the input value ==> no need of the output + out.append(generateExportCheckFunctionAppended(modeleType)); + return out.toString(); + } + + public String generateObjectCreate(final ClassObjectModel model, final TsClassElementGroup tsGroup) + throws IOException { + final StringBuilder out = new StringBuilder(); + final String modeleType = MODEL_TYPE_CREATE; + out.append("export const "); + out.append(this.zodName); + out.append(modeleType); + out.append(" = "); + final boolean isEmpty = model.getFields().stream().filter(field -> field.accessLimitation().creatable()) + .count() == 0; + if (model.getExtendsClass() != null) { + final ClassModel parentClass = model.getExtendsClass(); + final TsClassElement tsParentModel = tsGroup.find(parentClass); + out.append(tsParentModel.zodName); + out.append(modeleType); + if (!isEmpty) { + out.append(".extend({\n"); + } + } else { + out.append("zod.object({\n"); + } + for (final FieldProperty field : model.getFields()) { + // remove all readOnly field + if (!field.accessLimitation().creatable()) { + continue; + } + final ClassModel fieldModel = field.model(); + if (field.comment() != null) { + out.append("\t/**\n"); + out.append("\t * "); + out.append(field.comment()); + out.append("\n\t */\n"); + } + out.append("\t"); + out.append(field.name()); + out.append(": "); + if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) { + final TsClassElement tsFieldModel = tsGroup.find(fieldModel); + out.append(tsFieldModel.zodName); + } else if (fieldModel instanceof final ClassListModel fieldListModel) { + final String data = generateTsList(fieldListModel, tsGroup); + out.append(data); + } else if (fieldModel instanceof final ClassMapModel fieldMapModel) { + final String data = generateTsMap(fieldMapModel, tsGroup); + out.append(data); + } + out.append(maxSizeZod(field)); + out.append(optionalWriteTypeZod(field)); + out.append(optionalTypeZod(field)); + out.append(",\n"); + } + if (model.getExtendsClass() != null && isEmpty) { + out.append(";\n"); + } else { + out.append("\n});\n"); + } + out.append(generateZodInfer(this.tsTypeName + modeleType, this.zodName + modeleType)); + // Check only the input value ==> no need of the output + out.append(generateExportCheckFunctionAppended(modeleType)); return out.toString(); } diff --git a/src/org/kar/archidata/model/Data.java b/src/org/kar/archidata/model/Data.java index 845b4c8..e06cc26 100644 --- a/src/org/kar/archidata/model/Data.java +++ b/src/org/kar/archidata/model/Data.java @@ -1,6 +1,7 @@ package org.kar.archidata.model; import org.kar.archidata.annotation.DataIfNotExists; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; import com.fasterxml.jackson.annotation.JsonInclude; @@ -16,12 +17,15 @@ public class Data extends OIDGenericDataSoftDelete { @Column(length = 128, nullable = false) @Schema(description = "Sha512 of the data") @Size(max = 512) + @ApiAccessLimitation(creatable = false, updatable = false) public String sha512; @Column(length = 128, nullable = false) @Schema(description = "Mime -type of the media") @Size(max = 512) + @ApiAccessLimitation(creatable = false, updatable = false) public String mimeType; @Column(nullable = false) @Schema(description = "Size in Byte of the data") + @ApiAccessLimitation(creatable = false, updatable = false) public Long size; } diff --git a/src/org/kar/archidata/model/GenericData.java b/src/org/kar/archidata/model/GenericData.java index dacedb0..b4cf5fa 100644 --- a/src/org/kar/archidata/model/GenericData.java +++ b/src/org/kar/archidata/model/GenericData.java @@ -1,15 +1,20 @@ package org.kar.archidata.model; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +@ApiGenerationMode(create = true, update = true) public class GenericData extends GenericTiming { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, unique = true) - @Schema(description = "Unique Id of the object", required = false, readOnly = true, example = "123456") + @Schema(description = "Unique Id of the object", example = "123456") + @ApiAccessLimitation(creatable = false, updatable = false) public Long id = null; } diff --git a/src/org/kar/archidata/model/GenericDataSoftDelete.java b/src/org/kar/archidata/model/GenericDataSoftDelete.java index e4f691d..429c970 100644 --- a/src/org/kar/archidata/model/GenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/GenericDataSoftDelete.java @@ -2,18 +2,22 @@ package org.kar.archidata.model; import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataNotRead; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.persistence.Column; import jakarta.ws.rs.DefaultValue; +@ApiGenerationMode(create = true, update = true) public class GenericDataSoftDelete extends GenericData { @DataNotRead @Column(nullable = false) @DefaultValue("'0'") @DataDeleted - @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) + @Schema(description = "Deleted state", hidden = true) @Nullable + @ApiAccessLimitation(creatable = false, updatable = false) public Boolean deleted = null; } diff --git a/src/org/kar/archidata/model/GenericTiming.java b/src/org/kar/archidata/model/GenericTiming.java index 3e0bfec..7d8d91c 100644 --- a/src/org/kar/archidata/model/GenericTiming.java +++ b/src/org/kar/archidata/model/GenericTiming.java @@ -5,6 +5,8 @@ import java.util.Date; import org.kar.archidata.annotation.CreationTimestamp; import org.kar.archidata.annotation.DataNotRead; import org.kar.archidata.annotation.UpdateTimestamp; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import com.fasterxml.jackson.annotation.JsonFormat; @@ -12,20 +14,22 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.persistence.Column; +@ApiGenerationMode(create = true, update = true) public class GenericTiming { @DataNotRead @CreationTimestamp @Column(nullable = false, insertable = false, updatable = false) - @Schema(description = "Create time of the object", required = false, example = "2000-01-23T01:23:45.678+01:00", readOnly = true) + @Schema(description = "Create time of the object", example = "2000-01-23T01:23:45.678+01:00") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @Nullable + @ApiAccessLimitation(creatable = false, updatable = false) public Date createdAt = null; @DataNotRead @UpdateTimestamp @Column(nullable = false, insertable = false, updatable = false) - @Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true) + @Schema(description = "When update the object", example = "2000-01-23T00:23:45.678Z") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") - // public Instant updatedAt = null; @Nullable + @ApiAccessLimitation(creatable = false, updatable = false) public Date updatedAt = null; } diff --git a/src/org/kar/archidata/model/OIDGenericData.java b/src/org/kar/archidata/model/OIDGenericData.java index f5555a7..00180d2 100644 --- a/src/org/kar/archidata/model/OIDGenericData.java +++ b/src/org/kar/archidata/model/OIDGenericData.java @@ -1,17 +1,21 @@ package org.kar.archidata.model; import org.bson.types.ObjectId; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import dev.morphia.annotations.Id; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; import jakarta.validation.constraints.NotNull; +@ApiGenerationMode(create = true, update = true) public class OIDGenericData extends GenericTiming { @Id @jakarta.persistence.Id @Column(nullable = false, unique = true, name = "_id") - @Schema(description = "Unique ObjectID of the object", required = false, readOnly = true, example = "65161616841351") + @Schema(description = "Unique ObjectID of the object", example = "65161616841351") @NotNull + @ApiAccessLimitation(creatable = false, updatable = false) public ObjectId oid = null; } diff --git a/src/org/kar/archidata/model/OIDGenericDataSoftDelete.java b/src/org/kar/archidata/model/OIDGenericDataSoftDelete.java index 0a69aa7..b41a0f7 100644 --- a/src/org/kar/archidata/model/OIDGenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/OIDGenericDataSoftDelete.java @@ -2,18 +2,22 @@ package org.kar.archidata.model; import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataNotRead; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.persistence.Column; import jakarta.ws.rs.DefaultValue; +@ApiGenerationMode(create = true, update = true) public class OIDGenericDataSoftDelete extends OIDGenericData { @DataNotRead @Column(nullable = false) @DefaultValue("'0'") @DataDeleted - @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) + @Schema(description = "Deleted state", hidden = true) @Nullable + @ApiAccessLimitation(creatable = false, updatable = false) public Boolean deleted = null; } diff --git a/src/org/kar/archidata/model/UUIDGenericData.java b/src/org/kar/archidata/model/UUIDGenericData.java index 57388d8..c6f2a5d 100644 --- a/src/org/kar/archidata/model/UUIDGenericData.java +++ b/src/org/kar/archidata/model/UUIDGenericData.java @@ -2,17 +2,22 @@ package org.kar.archidata.model; import java.util.UUID; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.DefaultValue; +@ApiGenerationMode(create = true, update = true) 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") + @Schema(description = "Unique UUID of the object", example = "e6b33c1c-d24d-11ee-b616-02420a030102") @NotNull + @ApiAccessLimitation(creatable = false, updatable = false) public UUID uuid = null; } diff --git a/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java b/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java index 51a5d8c..2b6a82f 100644 --- a/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java @@ -2,18 +2,22 @@ package org.kar.archidata.model; import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataNotRead; +import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation; +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.persistence.Column; import jakarta.ws.rs.DefaultValue; +@ApiGenerationMode(create = true, update = true) public class UUIDGenericDataSoftDelete extends UUIDGenericData { @DataNotRead @Column(nullable = false) @DefaultValue("'0'") @DataDeleted - @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) + @Schema(description = "Deleted state", hidden = true) @Nullable + @ApiAccessLimitation(creatable = false, updatable = false) public Boolean deleted = null; } diff --git a/src/org/kar/archidata/tools/AnnotationCreator.java b/src/org/kar/archidata/tools/AnnotationCreator.java new file mode 100644 index 0000000..31372e7 --- /dev/null +++ b/src/org/kar/archidata/tools/AnnotationCreator.java @@ -0,0 +1,48 @@ +package org.kar.archidata.tools; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; + +public class AnnotationCreator { + + @SuppressWarnings("unchecked") + public static A createAnnotation(final Class annotationClass, final Object... values) { + return (A) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[] { annotationClass }, + new InvocationHandler() { + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + if ("annotationType".equals(method.getName())) { + return annotationClass; + } + if ("toString".equals(method.getName())) { + return "@" + annotationClass.getName() + values; + } + for (int i = 0; i < values.length; i += 2) { + if (method.getName().equals(values[i])) { + return values[i + 1]; + } + } + return method.getDefaultValue(); + } + }); + } + + public static void main(final String[] args) { + final ApiGenerationMode myAnnotation = AnnotationCreator.createAnnotation(ApiGenerationMode.class, "readable", + true, "creatable", false, "updatable", false); + + System.out.println("readable: " + myAnnotation.read()); // Output: example + System.out.println("creatable: " + myAnnotation.create()); // Output: 100 + System.out.println("updatable: " + myAnnotation.update()); // Output: 100 + final ApiGenerationMode myAnnotation2 = AnnotationCreator.createAnnotation(ApiGenerationMode.class); + + System.out.println("readable: " + myAnnotation2.read()); // Output: example + System.out.println("creatable: " + myAnnotation2.create()); // Output: 100 + System.out.println("updatable: " + myAnnotation2.update()); // Output: 100 + } +} \ No newline at end of file diff --git a/src/resources/rest-tools.ts b/src/resources/rest-tools.ts index 0831ff9..7c12d0d 100644 --- a/src/resources/rest-tools.ts +++ b/src/resources/rest-tools.ts @@ -3,30 +3,29 @@ * @copyright 2024, Edouard DUPIN, all right reserved * @license MPL-2 */ - -import { RestErrorResponse, isRestErrorResponse } from "./model"; +import { RestErrorResponse, isRestErrorResponse } from './model'; export enum HTTPRequestModel { - ARCHIVE = "ARCHIVE", - DELETE = "DELETE", - HEAD = "HEAD", - GET = "GET", - OPTION = "OPTION", - PATCH = "PATCH", - POST = "POST", - PUT = "PUT", - RESTORE = "RESTORE", + ARCHIVE = 'ARCHIVE', + DELETE = 'DELETE', + HEAD = 'HEAD', + GET = 'GET', + OPTION = 'OPTION', + PATCH = 'PATCH', + POST = 'POST', + PUT = 'PUT', + RESTORE = 'RESTORE', } export enum HTTPMimeType { - ALL = "*/*", - CSV = "text/csv", - IMAGE = "image/*", - IMAGE_JPEG = "image/jpeg", - IMAGE_PNG = "image/png", - JSON = "application/json", - MULTIPART = "multipart/form-data", - OCTET_STREAM = "application/octet-stream", - TEXT_PLAIN = "text/plain", + ALL = '*/*', + CSV = 'text/csv', + IMAGE = 'image/*', + IMAGE_JPEG = 'image/jpeg', + IMAGE_PNG = 'image/png', + JSON = 'application/json', + MULTIPART = 'multipart/form-data', + OCTET_STREAM = 'application/octet-stream', + TEXT_PLAIN = 'text/plain', } export interface RESTConfig { @@ -56,12 +55,11 @@ export interface ModelResponseHttp { export type ErrorRestApiCallback = (response: Response) => void; -let errorApiGlobalCallback: ErrorRestApiCallback|undefined = undefined; - -export const setErrorApiGlobalCallback = (callback:ErrorRestApiCallback) => { - errorApiGlobalCallback = callback; -} +let errorApiGlobalCallback: ErrorRestApiCallback | undefined = undefined; +export const setErrorApiGlobalCallback = (callback: ErrorRestApiCallback) => { + errorApiGlobalCallback = callback; +}; function isNullOrUndefined(data: any): data is undefined | null { return data === undefined || data === null; @@ -87,6 +85,7 @@ export interface RESTRequestType { data?: any; params?: object; queries?: object; + headers?: any; callbacks?: RESTCallbacks; } @@ -96,15 +95,15 @@ function replaceAll(input, searchValue, replaceValue) { function removeTrailingSlashes(input: string): string { if (isNullOrUndefined(input)) { - return "undefined"; + return 'undefined'; } - return input.replace(/\/+$/, ""); + return input.replace(/\/+$/, ''); } function removeLeadingSlashes(input: string): string { if (isNullOrUndefined(input)) { - return ""; + return ''; } - return input.replace(/^\/+/, ""); + return input.replace(/^\/+/, ''); } export function RESTUrl({ @@ -142,9 +141,9 @@ export function RESTUrl({ } } if (restConfig.token !== undefined && restModel.tokenInUrl === true) { - searchParams.append("Authorization", `Bearer ${restConfig.token}`); + searchParams.append('Authorization', `Bearer ${restConfig.token}`); } - return generateUrl + "?" + searchParams.toString(); + return generateUrl + '?' + searchParams.toString(); } export function fetchProgress( @@ -168,7 +167,7 @@ export function fetchProgress( return new Promise((resolve, reject) => { // Stream the upload progress if (progressUpload) { - xhr.io?.upload.addEventListener("progress", (dataEvent) => { + xhr.io?.upload.addEventListener('progress', (dataEvent) => { if (dataEvent.lengthComputable) { progressUpload(dataEvent.loaded, dataEvent.total); } @@ -176,7 +175,7 @@ export function fetchProgress( } // Stream the download progress if (progressDownload) { - xhr.io?.addEventListener("progress", (dataEvent) => { + xhr.io?.addEventListener('progress', (dataEvent) => { if (dataEvent.lengthComputable) { progressDownload(dataEvent.loaded, dataEvent.total); } @@ -196,19 +195,19 @@ export function fetchProgress( }; } // Check if we have an internal Fail: - xhr.io?.addEventListener("error", () => { + xhr.io?.addEventListener('error', () => { xhr.io = undefined; - reject(new TypeError("Failed to fetch")); + reject(new TypeError('Failed to fetch')); }); // Capture the end of the stream - xhr.io?.addEventListener("loadend", () => { + xhr.io?.addEventListener('loadend', () => { if (xhr.io?.readyState !== XMLHttpRequest.DONE) { return; } if (xhr.io?.status === 0) { //the stream has been aborted - reject(new TypeError("Fetch has been aborted")); + reject(new TypeError('Fetch has been aborted')); return; } // Stream is ended, transform in a generic response: @@ -218,17 +217,17 @@ export function fetchProgress( }); const headersArray = replaceAll( xhr.io.getAllResponseHeaders().trim(), - "\r\n", - "\n" - ).split("\n"); + '\r\n', + '\n' + ).split('\n'); headersArray.forEach(function (header) { - const firstColonIndex = header.indexOf(":"); + const firstColonIndex = header.indexOf(':'); if (firstColonIndex !== -1) { const key = header.substring(0, firstColonIndex).trim(); const value = header.substring(firstColonIndex + 1).trim(); response.headers.set(key, value); } else { - response.headers.set(header, ""); + response.headers.set(header, ''); } }); xhr.io = undefined; @@ -250,27 +249,29 @@ export function RESTRequest({ data, params, queries, + headers = {}, callbacks, }: RESTRequestType): Promise { // Create the URL PATH: let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); - let headers: any = {}; if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { - headers["Authorization"] = `Bearer ${restConfig.token}`; + headers['Authorization'] = `Bearer ${restConfig.token}`; } if (restModel.accept !== undefined) { - headers["Accept"] = restModel.accept; + headers['Accept'] = restModel.accept; } - if (restModel.requestType !== HTTPRequestModel.GET && - restModel.requestType !== HTTPRequestModel.ARCHIVE && - restModel.requestType !== HTTPRequestModel.RESTORE + if ( + restModel.requestType !== HTTPRequestModel.GET && + restModel.requestType !== HTTPRequestModel.ARCHIVE && + restModel.requestType !== HTTPRequestModel.RESTORE ) { // if Get we have not a content type, the body is empty - if (restModel.contentType !== HTTPMimeType.MULTIPART && - restModel.contentType !== undefined - ) { + if ( + restModel.contentType !== HTTPMimeType.MULTIPART && + restModel.contentType !== undefined + ) { // special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" - headers["Content-Type"] = restModel.contentType; + headers['Content-Type'] = restModel.contentType; } } let body = data; @@ -311,23 +312,27 @@ export function RESTRequest({ } action .then((response: Response) => { - if(errorApiGlobalCallback && 400 <= response.status && response.status <= 499) { - // Detect an error and trigger the generic error callback: - errorApiGlobalCallback(response); - } + if ( + errorApiGlobalCallback && + 400 <= response.status && + response.status <= 499 + ) { + // Detect an error and trigger the generic error callback: + errorApiGlobalCallback(response); + } if (response.status >= 200 && response.status <= 299) { - const contentType = response.headers.get("Content-Type"); + const contentType = response.headers.get('Content-Type'); if ( !isNullOrUndefined(restModel.accept) && restModel.accept !== contentType ) { reject({ - name: "Model accept type incompatible", + name: 'Model accept type incompatible', time: Date().toString(), status: 901, message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`, - statusMessage: "Fetch error", - error: "rest-tools.ts Wrong type in the message return type", + statusMessage: 'Fetch error', + error: 'rest-tools.ts Wrong type in the message return type', } as RestErrorResponse); } else if (contentType === HTTPMimeType.JSON) { response @@ -337,12 +342,12 @@ export function RESTRequest({ }) .catch((reason: Error) => { reject({ - name: "API serialization error", + name: 'API serialization error', time: Date().toString(), status: 902, message: `REST parse json fail: ${reason}`, - statusMessage: "Fetch parse error", - error: "rest-tools.ts Wrong message model to parse", + statusMessage: 'Fetch parse error', + error: 'rest-tools.ts Wrong message model to parse', } as RestErrorResponse); }); } else { @@ -362,22 +367,22 @@ export function RESTRequest({ .text() .then((dataError: string) => { reject({ - name: "API serialization error", + name: 'API serialization error', time: Date().toString(), status: 903, message: `REST parse error json with wrong type fail. ${dataError}`, - statusMessage: "Fetch parse error", - error: "rest-tools.ts Wrong message model to parse", + statusMessage: 'Fetch parse error', + error: 'rest-tools.ts Wrong message model to parse', } as RestErrorResponse); }) .catch((reason: any) => { reject({ - name: "API serialization error", + name: 'API serialization error', time: Date().toString(), status: response.status, message: `unmanaged error model: ??? with error: ${reason}`, - statusMessage: "Fetch ERROR parse error", - error: "rest-tools.ts Wrong message model to parse", + statusMessage: 'Fetch ERROR parse error', + error: 'rest-tools.ts Wrong message model to parse', } as RestErrorResponse); }); } @@ -387,22 +392,22 @@ export function RESTRequest({ .text() .then((dataError: string) => { reject({ - name: "API serialization error", + name: 'API serialization error', time: Date().toString(), status: response.status, message: `unmanaged error model: ${dataError} with error: ${reason}`, - statusMessage: "Fetch ERROR TEXT parse error", - error: "rest-tools.ts Wrong message model to parse", + statusMessage: 'Fetch ERROR TEXT parse error', + error: 'rest-tools.ts Wrong message model to parse', } as RestErrorResponse); }) .catch((reason: any) => { reject({ - name: "API serialization error", + name: 'API serialization error', time: Date().toString(), status: response.status, message: `unmanaged error model: ??? with error: ${reason}`, - statusMessage: "Fetch ERROR TEXT FAIL", - error: "rest-tools.ts Wrong message model to parse", + statusMessage: 'Fetch ERROR TEXT FAIL', + error: 'rest-tools.ts Wrong message model to parse', } as RestErrorResponse); }); }); @@ -413,12 +418,12 @@ export function RESTRequest({ reject(error); } else { reject({ - name: "Request fail", + name: 'Request fail', time: Date(), status: 999, message: error, - statusMessage: "Fetch catch error", - error: "rest-tools.ts detect an error in the fetch request", + statusMessage: 'Fetch catch error', + error: 'rest-tools.ts detect an error in the fetch request', }); } }); @@ -439,12 +444,12 @@ export function RESTRequestJson( resolve(value.data); } else { reject({ - name: "Model check fail", + name: 'Model check fail', time: Date().toString(), status: 950, - error: "REST Fail to verify the data", - statusMessage: "API cast ERROR", - message: "api.ts Check type as fail", + error: 'REST Fail to verify the data', + statusMessage: 'API cast ERROR', + message: 'api.ts Check type as fail', } as RestErrorResponse); } }) diff --git a/test/src/test/kar/archidata/apiExtern/resource/TestResource.java b/test/src/test/kar/archidata/apiExtern/resource/TestResource.java index 5a74ab9..d378157 100644 --- a/test/src/test/kar/archidata/apiExtern/resource/TestResource.java +++ b/test/src/test/kar/archidata/apiExtern/resource/TestResource.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import org.kar.archidata.annotation.AsyncType; +import org.kar.archidata.annotation.apiGenerator.ApiAsyncType; import org.kar.archidata.annotation.method.ARCHIVE; import org.kar.archidata.annotation.method.RESTORE; import org.kar.archidata.exception.NotFoundException; @@ -100,7 +100,7 @@ public class TestResource { @Consumes(MediaType.APPLICATION_JSON) public SimpleArchiveTable patch( @PathParam("id") final Long id, - @AsyncType(SimpleArchiveTable.class) final String jsonRequest) throws Exception { + @ApiAsyncType(SimpleArchiveTable.class) final String jsonRequest) throws Exception { LOGGER.info("patch({})", id); throw new NotFoundException("element does not exist: " + id); } diff --git a/test/src/test/kar/archidata/apiExtern/resource/TestResourceSample.java b/test/src/test/kar/archidata/apiExtern/resource/TestResourceSample.java index 7e494d2..b2318a8 100644 --- a/test/src/test/kar/archidata/apiExtern/resource/TestResourceSample.java +++ b/test/src/test/kar/archidata/apiExtern/resource/TestResourceSample.java @@ -2,7 +2,7 @@ package test.kar.archidata.apiExtern.resource; import java.util.List; -import org.kar.archidata.annotation.AsyncType; +import org.kar.archidata.annotation.apiGenerator.ApiAsyncType; import org.kar.archidata.annotation.method.ARCHIVE; import org.kar.archidata.annotation.method.RESTORE; import org.kar.archidata.dataAccess.DataAccess; @@ -66,7 +66,7 @@ public class TestResourceSample { @Path("{id}") @PermitAll @Consumes(MediaType.APPLICATION_JSON) - public SimpleTable patch(@PathParam("id") final Long id, @AsyncType(SimpleTable.class) final String jsonRequest) + public SimpleTable patch(@PathParam("id") final Long id, @ApiAsyncType(SimpleTable.class) final String jsonRequest) throws Exception { DataAccess.updateWithJson(SimpleTable.class, id, jsonRequest); return DataAccess.get(SimpleTable.class, id);