Compare commits

...

29 Commits

Author SHA1 Message Date
2bc7a2c5e3 [RELEASE] Release v0.25.0 2025-03-17 21:55:44 +01:00
dependabot[bot]
6c69bc63c6 [DEV-OPS] (dependabot) Bump all dependency 2025-03-17 21:13:14 +01:00
85ac72648f [FIX] not ready dependency 2025-03-16 11:48:33 +01:00
1281415c48 [DEV] integrate hybernate validator 2025-03-16 11:24:16 +01:00
dependabot[bot]
a4521853c3 [DEV-OPS] (dependabot) Bump com.twelvemonkeys.imageio:imageio-webp
Bumps com.twelvemonkeys.imageio:imageio-webp from 3.11.0 to 3.12.0.

---
updated-dependencies:
- dependency-name: com.twelvemonkeys.imageio:imageio-webp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 08:51:19 +01:00
dependabot[bot]
abe2dd9480 [DEV-OPS] (dependabot) Bump com.nimbusds:nimbus-jose-jwt
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.41.1 to 10.0.2.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.0.2..9.41.1)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 08:36:46 +01:00
dependabot[bot]
b7af0b4575 [DEV-OPS] (dependabot) Bump org.hibernate.validator:hibernate-validator
Bumps [org.hibernate.validator:hibernate-validator](https://github.com/hibernate/hibernate-validator) from 8.0.1.Final to 8.0.2.Final.
- [Changelog](https://github.com/hibernate/hibernate-validator/blob/8.0.2.Final/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-validator/compare/8.0.1.Final...8.0.2.Final)

---
updated-dependencies:
- dependency-name: org.hibernate.validator:hibernate-validator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 08:36:43 +01:00
dependabot[bot]
5412eadf2e [DEV-OPS] (dependabot) Bump org.apache.maven.plugins:maven-checkstyle-plugin
Bumps [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) from 3.5.0 to 3.6.0.
- [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.5.0...maven-checkstyle-plugin-3.6.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 08:36:41 +01:00
dependabot[bot]
d5e2e0f5b3 [DEV-OPS] (dependabot) Bump org.glassfish.jersey:jersey-bom
Bumps org.glassfish.jersey:jersey-bom from 3.1.5 to 3.1.10.

---
updated-dependencies:
- dependency-name: org.glassfish.jersey:jersey-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 08:36:38 +01:00
28ab6c49d4 [FIX] update PR title 2025-03-15 08:30:14 +01:00
260a3abd13 [FEAT] update dependabot 2025-03-15 08:25:09 +01:00
895e8c2b37 [FEAT] update versions 2025-03-15 08:18:14 +01:00
1673f1680b [FEAT] add a spotbug nullable 2025-03-15 08:06:59 +01:00
04a82250d8 [FEAT] add capability to catch ConstraintViolationException 2025-03-15 08:06:50 +01:00
0326bde209 [FEAT] understable template typing 2025-03-10 07:27:51 +01:00
c1ccaf20ec
[FEAT] rework the generation of code for typescript (#27)
This PR is not compatible with previous code...
Rename some Annotation to have a better experience

Update the generation of the Typescript object 
===================================

By default the generation of object are only the object requested

if you annotate the object with: `@ApiGenerationMode(create = true,
update = true)`

the generation will add 2 object in typescript:
  - `MyClassUpdate` that contain the object specific for `PUT` request
  - `MyClassCreate` that contain the object specific for `POST` request
  - the PATCH request are generate with `Partial<MyClassUpdate>`

If you do not generate the create or update the system will wrap the
bass by default: `MyClass`

Add support of hidding the element in the generated object
=============================================

When generate the object some field are not needed to transmit to the
client, then we add `@ApiAccessLimitation(creatable = false, updatable =
false)` that permit to remove the field when Update or Create object are
generated

It will be used to check the Input validity instead of
`@schema(readonly)` (error of implementation)


TODO:
=====

  - Some issue when request Update generation and parent does not exist
- The checker is not updated with the use of the `@ApiAccessLimitation`
decorator.


dependencies:
===========
  - Closes: https://github.com/kangaroo-and-rabbit/archidata/issues/22
2025-03-08 14:32:16 +01:00
2174d7689f [FEAT] add callback to detect and collect errors 2025-03-05 23:48:15 +01:00
15113807b3 [DOC] add some documentation 2025-03-05 23:45:08 +01:00
ffdc6c1249 [VERSION] update dev tag version 2025-02-24 12:34:45 +01:00
1b4e6ca239 [RELEASE] Release v0.24.0 2025-02-24 12:34:41 +01:00
be8a5c713a [FIX] ObjectId manyToOne and OneToMany 2025-02-17 00:16:11 +01:00
c3f03bc1e8 [VERSION] update dev tag version 2025-02-09 22:06:56 +01:00
c7eadc607d [RELEASE] Release v0.23.6 2025-02-09 22:06:53 +01:00
5f89ff7944 [DEV] add variable to permit not check if the DB exist 2025-02-09 22:05:35 +01:00
20d2d004cb [VERSION] update dev tag version 2025-02-06 07:47:20 +01:00
da3c467569 [RELEASE] Release v0.23.4 2025-02-06 07:47:17 +01:00
0c932d4e92 [FEAT] set the object enpty as identical as their parent.
this prevent react-hook-form resolver erreur, it does not support empty object
2025-02-04 21:15:10 +01:00
a400bb99b8 [FEAT] add Jwt token description to be serialize in front 2025-02-02 19:34:23 +01:00
bdc9a4ac4d [VERSION] update dev tag version 2025-01-30 21:45:29 +01:00
73 changed files with 2073 additions and 466 deletions

View File

@ -30,15 +30,6 @@
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations"> <classpathentry kind="src" path="target/generated-sources/annotations">
<attributes> <attributes>
<attribute name="optional" value="true"/> <attribute name="optional" value="true"/>

View File

@ -1,12 +1,24 @@
# To get started with Dependabot version updates, you'll need to specify which ---
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2 version: 2
updates: updates:
- package-ecosystem: "" # See documentation for possible values - package-ecosystem: "github-actions"
directory: "/" # Location of package manifests directory: "/"
schedule: schedule:
interval: "weekly" interval: "daily"
time: "05:00"
timezone: "Europe/Paris"
commit-message:
prefix: "[DEV-OPS] (dependabot) "
assignees:
- HeeroYui
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
time: "06:00"
timezone: "Europe/Paris"
commit-message:
prefix: "[DEV-OPS] (dependabot) "
assignees:
- HeeroYui

View File

@ -9,6 +9,8 @@ on:
- synchronize - synchronize
- ready_for_review - ready_for_review
- reopened - reopened
- labeled
- unlabeled
jobs: jobs:
check-title: check-title:
@ -17,7 +19,7 @@ jobs:
- name: "Check title" - name: "Check title"
uses: Slashgear/action-check-pr-title@v4.3.0 uses: Slashgear/action-check-pr-title@v4.3.0
with: with:
regexp: "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-]+\\))? [A-Za-z0-9 ,.'\\-!]+$" regexp: "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-/_]+\\))? [A-Za-z0-9: ,.'\\-!/_]+$"
helpMessage: | helpMessage: |
Title of the PR MUST respect format: "[{TYPE}] clear description without typos in english" with {TYPE}: Title of the PR MUST respect format: "[{TYPE}] clear description without typos in english" with {TYPE}:
* [API] Change API that permit to access on the application (un-compatibility only). This one can specifically added with [API,{TYPE}] * [API] Change API that permit to access on the application (un-compatibility only). This one can specifically added with [API,{TYPE}]

87
pom.xml
View File

@ -3,17 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.23.2</version> <version>0.25.0</version>
<properties>
<java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version>
<jersey.version>3.1.5</jersey.version>
<jaxb.version>2.3.1</jaxb.version>
<istack.version>4.1.1</istack.version>
</properties>
<repositories> <repositories>
<repository> <repository>
<id>gitea</id> <id>gitea</id>
@ -35,7 +25,7 @@
<dependency> <dependency>
<groupId>org.glassfish.jersey</groupId> <groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId> <artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version> <version>4.0.0-M2</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -58,13 +48,13 @@
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId> <artifactId>imageio-webp</artifactId>
<version>3.11.0</version> <version>3.12.0</version>
</dependency> </dependency>
<!-- Decode JPEG image --> <!-- Decode JPEG image -->
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<version>3.11.0</version> <version>3.12.0</version>
</dependency> </dependency>
<!-- Encode file in webp --> <!-- Encode file in webp -->
<dependency> <dependency>
@ -76,7 +66,7 @@
<dependency> <dependency>
<groupId>org.apache.tika</groupId> <groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <artifactId>tika-core</artifactId>
<version>3.0.0-BETA2</version> <version>3.1.0</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart --> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart -->
<dependency> <dependency>
@ -95,10 +85,19 @@
<groupId>org.glassfish.jersey.containers</groupId> <groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId> <artifactId>jersey-container-grizzly2-http</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version> <version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>4.0.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.glassfish.jaxb</groupId> <groupId>org.glassfish.jaxb</groupId>
@ -110,15 +109,10 @@
<artifactId>jakarta.ws.rs-api</artifactId> <artifactId>jakarta.ws.rs-api</artifactId>
<version>4.0.0</version> <version>4.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.sun.istack</groupId> <groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId> <artifactId>istack-commons-runtime</artifactId>
<version>${istack.version}</version> <version>4.2.0</version>
</dependency> </dependency>
<!-- continu to be needed ??? --> <!-- continu to be needed ??? -->
<dependency> <dependency>
@ -134,18 +128,18 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.18.1</version> <version>2.18.3</version>
</dependency> </dependency>
<!-- encode output in CSV --> <!-- encode output in CSV -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId> <artifactId>jackson-dataformat-csv</artifactId>
<version>2.18.1</version> <version>2.18.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.1</version> <version>2.18.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.servlet</groupId> <groupId>jakarta.servlet</groupId>
@ -157,18 +151,18 @@
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>9.0.0</version> <version>9.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.0</version> <version>3.49.1.0</version>
</dependency> </dependency>
<!-- Interface for JWT token --> <!-- Interface for JWT token -->
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>9.41.1</version> <version>10.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.persistence</groupId> <groupId>jakarta.persistence</groupId>
@ -179,32 +173,32 @@
<dependency> <dependency>
<groupId>io.swagger.core.v3</groupId> <groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-jakarta</artifactId> <artifactId>swagger-jaxrs2-jakarta</artifactId>
<version>2.2.23</version> <version>2.2.29</version>
</dependency> </dependency>
<!-- spotbug tooling --> <!-- spotbug tooling -->
<dependency> <dependency>
<groupId>com.github.spotbugs</groupId> <groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId> <artifactId>spotbugs-annotations</artifactId>
<version>4.8.6</version> <version>4.9.3</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Morphia --> <!-- Morphia -->
<dependency> <dependency>
<groupId>dev.morphia.morphia</groupId> <groupId>dev.morphia.morphia</groupId>
<artifactId>morphia-core</artifactId> <artifactId>morphia-core</artifactId>
<version>2.3.0</version> <version>2.4.15</version>
</dependency> </dependency>
<!-- MongoDB Java Driver --> <!-- MongoDB Java Driver -->
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>4.3.0</version> <version>5.4.0-alpha0</version>
</dependency> </dependency>
<!-- Bean Validation (JSR 303 / 380) --> <!-- Bean Validation (JSR 303 / 380) -->
<dependency> <dependency>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
<version>7.0.0.Final</version> <version>9.0.0.CR1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>javax.validation</groupId>
@ -219,24 +213,24 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version> <version>5.12.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0</version> <version>5.12.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.revelc.code.formatter</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>2.24.1</version> <version>2.25.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version> <version>3.6.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
@ -256,18 +250,17 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version> <version>3.14.0</version>
<configuration> <configuration>
<source>${maven.compiler.source}</source> <source>21</source>
<target>${maven.compiler.target}</target> <target>21</target>
<!--<encoding>${project.build.sourceEncoding}</encoding>-->
</configuration> </configuration>
</plugin> </plugin>
<!-- Create the source bundle --> <!-- Create the source bundle -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version> <version>3.3.1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@ -295,7 +288,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.5.2</version>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
@ -314,7 +307,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>private</show> <show>private</show>
<nohelp>true</nohelp> <nohelp>true</nohelp>
@ -324,7 +317,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version> <version>3.6.0</version>
<configuration> <configuration>
<configLocation>CheckStyle.xml</configLocation> <configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput> <consoleOutput>true</consoleOutput>
@ -336,7 +329,7 @@
<plugin> <plugin>
<groupId>net.revelc.code.formatter</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version> <version>2.24.1</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding> <lineEnding>LF</lineEnding>
@ -385,7 +378,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>public</show> <show>public</show>
</configuration> </configuration>

View File

@ -2,6 +2,7 @@ package org.kar.archidata.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,6 +42,24 @@ import jakarta.ws.rs.DefaultValue;
public class AnnotationTools { public class AnnotationTools {
static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class); static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class);
public static <TYPE extends Annotation> TYPE get(final Parameter param, final Class<TYPE> clazz) {
final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Parameter param, final Class<TYPE> clazz) {
final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations;
}
public static <TYPE extends Annotation> TYPE get(final Field element, final Class<TYPE> clazz) { public static <TYPE extends Annotation> TYPE get(final Field element, final Class<TYPE> clazz) {
final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz); final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz);
@ -59,6 +78,24 @@ public class AnnotationTools {
return annotations; return annotations;
} }
public static <TYPE extends Annotation> TYPE get(final Class<?> classObject, final Class<TYPE> clazz) {
final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Class<?> classObject, final Class<TYPE> clazz) {
final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations;
}
// For SQL declaration table Name // For SQL declaration table Name
public static String getTableName(final Class<?> clazz, final QueryOptions options) throws DataAccessException { public static String getTableName(final Class<?> clazz, final QueryOptions options) throws DataAccessException {
if (options != null) { if (options != null) {
@ -121,14 +158,6 @@ public class AnnotationTools {
return get(element, CollectionNotEmpty.class); 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) { public static String getSchemaExample(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {
@ -137,14 +166,6 @@ public class AnnotationTools {
return ((Schema) annotation[0]).example(); 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) { public static String getSchemaDescription(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {

View File

@ -5,6 +5,39 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The CreationTimestamp annotation is used to automatically set the creation
* date of an object in the database. This annotation ensures that the field
* marked with @CreationTimestamp is populated with the current timestamp
* when the object is first created.
*
* <p>Usage:
* - Target: This annotation can be applied to fields within a class.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data persistence logic.
*
* <p>Behavior:
* - When a field is annotated with @CreationTimestamp, it will automatically
* be set to the current date and time when the object is inserted into the
* database.
* - This annotation is typically used in conjunction with other annotations
* such as @Column to define database column properties.
*
* <p>Example:
* <pre>{@code
* public class MyEntity {
* @DataNotRead
* @CreationTimestamp
* @Column(nullable = false, insertable = false, updatable = false)
* @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") // optional depend on the configuration
* @Nullable
* public Date createdAt = null;
* }
* }</pre>
*
* In this example, the createdAt field will be automatically set to the
* current timestamp when a new User object is created in the database.
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CreationTimestamp { public @interface CreationTimestamp {

View File

@ -5,6 +5,40 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The DataDeleted annotation is used to manage a boolean variable that marks
* an object as 'deleted' in the database. This annotation helps in soft deletion
* by excluding marked objects from being automatically retrieved unless
* explicitly specified.
*
* <p>Usage:
* - Target: This annotation can be applied to fields within a class.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data retrieval logic.
*
* <p>Behavior:
* - When a field is annotated with @DataDeleted, it will not be included in the
* default data retrieval process from the database if its value is false.
* - To override this behavior and access deleted items, the query must include
* the option AccessDeletedItems.
*
* <p>Example:
* <pre>{@code
* public class MyEntity {
* public String username;
*
* @DataDeleted
* @DataNotRead
* @Column(nullable = false)
* @DefaultValue("'0'")
* public Boolean deleted = null;
* }
* }</pre>
*
* In this example, objects with `deleted` set to true will not be retrieved
* by default. To include them in the query results, the AccessDeletedItems
* option must be specified in the query.
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface DataDeleted { public @interface DataDeleted {

View File

@ -5,6 +5,41 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The DataJson annotation is used to convert fields or classes to JSON format
* for storage in a database. This annotation allows storing complex data types
* such as lists, maps, and other objects in SQL databases as JSON or STRING
* (for SQLite).
*
* <p>Usage:
* - Target: This annotation can be applied to both fields and classes.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data persistence logic.
*
* <p>Behavior:
* - When applied to a field or class, the DataJson annotation enables the
* conversion of the annotated element to JSON format before storing it in
* the database.
* - This is particularly useful in SQL databases where only basic data types
* (char, short, int, long, float, string, timestamp) can be stored directly.
* The DataJson annotation makes it possible to store complex data structures
* by converting them to JSON.
*
* <p>Attributes:
* - targetEntity: Specifies the target entity class to which the JSON data
* should be mapped. Defaults auto-detect if not specified.
*
* <p>Example:
* <pre>{@code
* public class User {
* @DataJson
* public Map<String, Object> additionalData;
* }
* }</pre>
*
* In this example, the additionalData field can store complex data structures
* as JSON in the database.
*/
@Target({ ElementType.TYPE, ElementType.FIELD }) @Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface DataJson { public @interface DataJson {

View File

@ -5,6 +5,37 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The DataNotRead annotation is used to mark fields in a class that should not
* be automatically read from the database. This annotation helps in optimizing
* data retrieval by excluding certain fields from being fetched unless
* explicitly specified.
*
* <p>Usage:
* - Target: This annotation can be applied to fields within a class.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data retrieval logic.
*
* <p>Behavior:
* - When a field is annotated with @DataNotRead, it will not be included in the
* default data retrieval process from the database.
* - To override this behavior and read all columns, including those marked with
* @DataNotRead, the query must include the option ReadAllColumn.
*
* <p>Example:
* <pre>{@code
* public class MyEntity {
* public String username;
*
* @DataNotRead
* private String sensitiveData;
* }
* }</pre>
*
* In this example, the sensitiveData field will not be read from the database
* by default. To include it in the query results, the ReadAllColumn option must
* be specified in the query.
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface DataNotRead { public @interface DataNotRead {

View File

@ -1,12 +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;
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface FormDataOptional {
}

View File

@ -1,13 +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;
/** When we wend to have only One type for read and write mode (Wrapping API). */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NoWriteSpecificMode {
}

View File

@ -1,11 +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;
/** In case of the update parameter with String input to detect null element. */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeScriptProgress {}

View File

@ -5,6 +5,38 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The UpdateTimestamp annotation is used to automatically update the timestamp
* of an object in the database whenever it is modified. This annotation ensures
* that the field marked with @UpdateTimestamp is set to the current timestamp
* each time the object is updated.
*
* <p>Usage:
* - Target: This annotation can be applied to fields within a class.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data persistence logic.
*
* <p>Behavior:
* - When a field is annotated with @UpdateTimestamp, it will automatically
* be updated to the current date and time whenever the object is modified
* in the database.
* - This annotation is typically used in conjunction with other annotations
* such as @Column to define database column properties.
*
* <p>Example:
* <pre>{@code
* public class MyEntity {
* @UpdateTimestamp
* @Column(nullable = false, insertable = false, updatable = false)
* @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
* @Nullable
* public Date updatedAt = null;
* }
* }</pre>
*
* In this example, the updatedAt field will be automatically set to the
* current timestamp whenever the User object is modified in the database.
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface UpdateTimestamp { public @interface UpdateTimestamp {

View File

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

View File

@ -1,4 +1,4 @@
package org.kar.archidata.annotation; package org.kar.archidata.annotation.apiGenerator;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; 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. */ /** In case of the update parameter with String input to detect null element. */
@Target({ ElementType.PARAMETER, ElementType.METHOD }) @Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface AsyncType { public @interface ApiAsyncType {
// Possible class values. // Possible class values.
Class<?>[] value(); Class<?>[] value();

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>Example:
* <pre>{@code
* @ApiGenerationMode(creatable=false, updatable=false)
* public class User {
* public String username;
* public String email;
* }
* }</pre>
*
* 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;
}

View File

@ -0,0 +1,76 @@
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 FormDataOptional annotation is used to indicate that a form data parameter
* is optional when generating client code. By default, form data parameters are
* required, but this annotation allows them to be optional, enabling the creation
* of polymorphic APIs.
*
* <p>Usage:
* - Target: This annotation can be applied to method parameters.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle code generation logic.
*
* <p>Behavior:
* - When applied to a parameter, the FormDataOptional annotation specifies that
* the parameter is optional in the generated client code. This allows for
* more flexible API designs where certain inputs can be omitted.
*
* <p>Example:
* <pre>{@code
* public class AlbumService {
*
* @POST
* @Path("{id}/cover")
* @RolesAllowed("ADMIN")
* @Consumes({ MediaType.MULTIPART_FORM_DATA })
* @Operation(description = "Add a cover on a specific album")
* @TypeScriptProgress
* public Album uploadCover(@PathParam("id") final Long id,
* @ApiInputOptional @FormDataParam("uri") final String uri,
* @ApiInputOptional @FormDataParam("file") final InputStream fileInputStream,
* @ApiInputOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData)
* throws Exception {
* // some code
* }
* }
* }</pre>
*
* Note: @FormDataParam must be allway at the last position.
*
* In this example, the uri, fileInputStream, and fileMetaData parameters are
* marked as optional, allowing the client to omit them when calling the API.
*
* <p>Generated TypeScript code example:
* <pre>{@code
* //Add a cover on a specific album
* export function uploadCover({
* restConfig,
* params,
* data,
* callbacks,
* }: {
* restConfig: RESTConfig,
* params: {
* id: Long,
* },
* data: {
* file?: File, // element is optional
* uri?: string, // element is optional
* },
* callbacks?: RESTCallbacks,
* }): Promise<Album> { ...
* }</pre>
*
* The generated TypeScript function reflects the optional nature of the form data parameters.
*/
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiInputOptional {
}

View File

@ -0,0 +1,71 @@
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 TypeScriptProgress annotation is used to specify that an API method
* will take a significant amount of time to complete, and thus requires a
* callback API to provide more precise progress tracking, particularly for
* upload operations.
*
* <p>Usage:
* - Target: This annotation can be applied to method parameters and methods.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle code generation logic.
*
* <p>Behavior:
* - When applied to a method or parameter, the TypeScriptProgress annotation
* indicates that the client code generator should provide a callback API
* for tracking the progress of the operation.
* - Note: The use of this annotation implies that the standard browser fetch
* API is not used, as the callback API is not yet operational. Instead,
* the older XMLHttpRequest interface is utilized.
*
* <p>Example:
* <pre>{@code
* public class SeasonService {
*
* @POST
* @Path("{id}/cover")
* @RolesAllowed("ADMIN")
* @Consumes(MediaType.MULTIPART_FORM_DATA)
* @Operation(description = "Upload a new season cover season", tags = "GLOBAL")
* @TypeScriptProgress
* public Season uploadCover(@PathParam("id") final Long id,
* @FormDataParam("file") final InputStream fileInputStream,
* @FormDataParam("file") final FormDataContentDisposition fileMetaData)
* throws Exception {
* // Upload logic
* }
* }
* }</pre>
*
* In this example, the uploadCover method will generate a client-side API
* with progress tracking capabilities using XMLHttpRequest.
*
* <p>Generated TypeScript code example:
* <pre>{@code
* export function uploadCover({
* restConfig,
* params,
* data,
* callbacks, // add this callback handle
* }: {
* restConfig: RESTConfig,
* params: {
* id: Long,
* },
* data: {
* file: File,
* },
* callbacks?: RESTCallbacks,
* }): Promise<Season> {...
* }</pre>
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiTypeScriptProgress {}

View File

@ -5,8 +5,18 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.FIELD }) import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = CheckForeignKeyValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CheckForeignKey { public @interface CheckForeignKey {
Class<?> target(); Class<?> target();
String message() default "Foreign-key does not exist in the DB";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} }

View File

@ -0,0 +1,43 @@
package org.kar.archidata.annotation.checker;
import java.util.Collection;
import org.kar.archidata.dataAccess.DataAccess;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CheckForeignKeyValidator implements ConstraintValidator<CheckForeignKey, Object> {
Class<?> target = null;
@Override
public void initialize(final CheckForeignKey annotation) {
this.target = annotation.target();
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value != null) {
return true;
}
if (value instanceof final Collection<?> tmpCollection) {
final Object[] elements = tmpCollection.toArray();
for (final Object element : elements) {
if (element == null) {
continue;
}
try {
final long count = DataAccess.count(this.target, element);
if (count != 1) {
return false;
}
} catch (final Exception e) {
// TODO ...
return false;
}
}
}
return true;
}
}

View File

@ -7,6 +7,44 @@ import java.lang.annotation.Target;
import org.kar.archidata.dataAccess.options.CheckFunctionInterface; import org.kar.archidata.dataAccess.options.CheckFunctionInterface;
/**
* The Checker annotation is used to specify a checker class that automatically
* validates data for a parent class. This annotation can be applied to both
* classes and fields to enforce validation rules defined in the checker class.
*
* <p>Usage:
* - Target: This annotation can be applied to types (classes) and fields.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle data validation logic.
*
* <p>Behavior:
* - When applied to a class or field, the Checker annotation specifies a
* checker class that implements the CheckFunctionInterface. This checker
* class is responsible for validating the data associated with the annotated
* element.
* - The validation is automatically triggered when the data of the parent class
* is validated, ensuring that the data adheres to the specified rules.
*
* <p>Attributes:
* - value: Specifies the checker class that implements the validation logic.
* This class must extend the CheckFunctionInterface.
*
* <p>Example:
* <pre>{@code
* public class User {
*
* @Checker(UserDataChecker.class)
* public String email;
* }
*
* public class UserDataChecker implements CheckFunctionInterface {
* ...
* }
* }</pre>
*
* In this example, the email field in the User class is validated using the
* UserDataChecker class whenever the User class data is validated.
*/
@Target({ ElementType.TYPE, ElementType.FIELD }) @Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Checker { public @interface Checker {

View File

@ -5,8 +5,16 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.FIELD) import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = CollectionItemNotNullValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CollectionItemNotNull { public @interface CollectionItemNotNull {
String message() default "Collection can not contain NULL item";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} }

View File

@ -0,0 +1,31 @@
package org.kar.archidata.annotation.checker;
import java.util.Collection;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CollectionItemNotNullValidator implements ConstraintValidator<CollectionItemNotNull, Object> {
@Override
public void initialize(final CollectionItemNotNull annotation) {
// nothing to do...
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value instanceof final Collection<?> tmpCollection) {
final Object[] elements = tmpCollection.toArray();
for (final Object element : elements) {
if (element == null) {
return false;
//throw new InputException(baseName + fieldName + '[' + iii + ']', "Collection can not contain NULL item");
}
}
}
return true;
}
}

View File

@ -5,8 +5,17 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.FIELD) import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = CollectionItemUniqueValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CollectionItemUnique { public @interface CollectionItemUnique {
String message() default "Cannot insert multiple times the same elements";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} }

View File

@ -0,0 +1,30 @@
package org.kar.archidata.annotation.checker;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CollectionItemUniqueValidator implements ConstraintValidator<CollectionItemUnique, Object> {
@Override
public void initialize(final CollectionItemUnique annotation) {
// nothing to do...
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value instanceof final Collection<?> tmpCollection) {
final Set<Object> uniqueValues = new HashSet<>(tmpCollection);
if (uniqueValues.size() != tmpCollection.size()) {
return false;
}
}
return true;
}
}

View File

@ -5,8 +5,17 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.FIELD) import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = CollectionNotEmptyValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CollectionNotEmpty { public @interface CollectionNotEmpty {
String message() default "Collection can not be empty";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} }

View File

@ -0,0 +1,27 @@
package org.kar.archidata.annotation.checker;
import java.util.Collection;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CollectionNotEmptyValidator implements ConstraintValidator<CollectionNotEmpty, Object> {
@Override
public void initialize(final CollectionNotEmpty annotation) {
// nothing to do...
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value instanceof final Collection<?> tmpCollection) {
if (tmpCollection.isEmpty()) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,20 @@
package org.kar.archidata.annotation.checker;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = ReadOnlyFieldValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyField {
String message() default "Field can not be set, it is a read-only field.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,20 @@
package org.kar.archidata.annotation.checker;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class ReadOnlyFieldValidator implements ConstraintValidator<ReadOnlyField, Object> {
@Override
public void initialize(final ReadOnlyField annotation) {
// nothing to do...
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value != null) {
return false;
}
return true;
}
}

View File

@ -24,6 +24,7 @@ import javax.imageio.ImageIO;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam; 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.annotation.security.PermitTokenInURI;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
@ -426,7 +427,7 @@ public class DataResource {
public Response retrieveDataFull( public Response retrieveDataFull(
@Context final SecurityContext sc, @Context final SecurityContext sc,
@QueryParam(HttpHeaders.AUTHORIZATION) final String token, @QueryParam(HttpHeaders.AUTHORIZATION) final String token,
@HeaderParam("Range") final String range, @ApiInputOptional @HeaderParam("Range") final String range,
@PathParam("oid") final ObjectId oid, @PathParam("oid") final ObjectId oid,
@PathParam("name") final String name) throws Exception { @PathParam("name") final String name) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();

View File

@ -0,0 +1,47 @@
package org.kar.archidata.catcher;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.validation.ConstraintViolationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class ConstraintViolationExceptionCatcher implements ExceptionMapper<ConstraintViolationException> {
private static final Logger LOGGER = LoggerFactory.getLogger(ConstraintViolationExceptionCatcher.class);
@Override
public Response toResponse(final ConstraintViolationException exception) {
LOGGER.warn("Catch ConstraintViolationException: {}", exception.getLocalizedMessage());
final RestErrorResponse ret = build(exception);
LOGGER.error("Error OID={}", ret.oid);
return Response.status(Response.Status.BAD_REQUEST).entity(ret).type(MediaType.APPLICATION_JSON).build();
}
private RestErrorResponse build(final ConstraintViolationException exception) {
final List<RestInputError> inputError = new ArrayList<>();
for (final var cv : exception.getConstraintViolations()) {
if (cv == null) {
continue;
}
inputError.add(new RestInputError(cv.getPropertyPath(), cv.getMessage()));
}
Collections.sort(inputError, Comparator.comparing(RestInputError::getFullPath));
String errorType = "Multiple error on input";
if (inputError.size() == 0) {
errorType = "Constraint Violation";
} else if (inputError.size() == 1) {
errorType = "Error on input='" + inputError.get(0).path + "'";
}
return new RestErrorResponse(Response.Status.BAD_REQUEST, Instant.now().toString(), errorType,
exception.getMessage(), inputError);
}
}

View File

@ -19,6 +19,7 @@ public class GenericCatcher {
rc.register(FailExceptionCatcher.class); rc.register(FailExceptionCatcher.class);
// generic Exception catcher // generic Exception catcher
rc.register(ExceptionCatcher.class); rc.register(ExceptionCatcher.class);
rc.register(ConstraintViolationExceptionCatcher.class);
} }
} }

View File

@ -1,15 +1,16 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.kar.archidata.annotation.NoWriteSpecificMode; import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@NoWriteSpecificMode @ApiGenerationMode
public class RestErrorResponse { public class RestErrorResponse {
public ObjectId oid = new ObjectId(); public ObjectId oid = new ObjectId();
@NotNull @NotNull
@ -27,6 +28,18 @@ public class RestErrorResponse {
@Column(length = 0) @Column(length = 0)
final public String statusMessage; final public String statusMessage;
final public List<RestInputError> inputError;
public RestErrorResponse(final Response.Status status, final String time, final String error, final String message,
final List<RestInputError> inputError) {
this.time = time;
this.name = error;
this.message = message;
this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase();
this.inputError = inputError;
}
public RestErrorResponse(final Response.Status status, final String time, final String error, public RestErrorResponse(final Response.Status status, final String time, final String error,
final String message) { final String message) {
this.time = time; this.time = time;
@ -34,6 +47,7 @@ public class RestErrorResponse {
this.message = message; this.message = message;
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
public RestErrorResponse(final Response.Status status, final String error, final String message) { public RestErrorResponse(final Response.Status status, final String error, final String message) {
@ -42,6 +56,7 @@ public class RestErrorResponse {
this.message = message; this.message = message;
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
public RestErrorResponse(final Response.Status status) { public RestErrorResponse(final Response.Status status) {
@ -50,6 +65,7 @@ public class RestErrorResponse {
this.time = Instant.now().toString(); this.time = Instant.now().toString();
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
} }

View File

@ -0,0 +1,50 @@
package org.kar.archidata.catcher;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.persistence.Column;
import jakarta.validation.Path;
import jakarta.validation.constraints.NotNull;
public class RestInputError {
private static Pattern PATTERN = Pattern.compile("^([^.]+)\\.([^.]+)(\\.(.*))?");
@Column(length = 0)
public String argument;
@Column(length = 0)
public String path;
@NotNull
@Column(length = 0)
public String message;
@Override
public String toString() {
return "RestInputError [argument=" + this.argument + ", path=" + this.path + ", message=" + this.message + "]";
}
public RestInputError() {}
public RestInputError(final Path path, final String message) {
final Matcher matcher = PATTERN.matcher(path.toString());
if (matcher.find()) {
//String firstPart = matcher.group(1); this is the request base element ==> not needed
this.argument = matcher.group(2);
this.path = matcher.group(4);
} else {
this.path = path.toString();
}
this.message = message;
}
public RestInputError(final String path, final String message) {
this.path = path;
this.message = message;
}
String getFullPath() {
if (this.path == null) {
return this.argument;
}
return this.argument + "." + this.path;
}
}

View File

@ -9,6 +9,7 @@ import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.InternalServerErrorException;
@ -189,6 +190,7 @@ public class DataAccess {
} }
} }
@Nullable
public static <T, ID_TYPE> T get(final Class<T> clazz, final ID_TYPE id, final QueryOption... options) public static <T, ID_TYPE> T get(final Class<T> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception { throws Exception {
try (DBAccess db = DBAccess.createInterface()) { try (DBAccess db = DBAccess.createInterface()) {

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName; import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut; import org.kar.archidata.dataAccess.CountInOut;
@ -72,7 +73,7 @@ public class AddOnManyToMany implements DataAccessAddOn {
} }
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) { if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
return true; return true;
} }
final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class); final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class);
@ -135,7 +136,11 @@ public class AddOnManyToMany implements DataAccessAddOn {
querySelect.append("'"); querySelect.append("'");
if (objectClass == Long.class) { if (objectClass == Long.class) {
querySelect.append(SEPARATOR_LONG); querySelect.append(SEPARATOR_LONG);
} else if (objectClass == UUID.class) {} else { } else if (objectClass == UUID.class) {
// ???
} else if (objectClass == ObjectId.class) {
// ???
} else {
final Class<?> foreignKeyType = AnnotationTools.getPrimaryKeyField(objectClass).getType(); final Class<?> foreignKeyType = AnnotationTools.getPrimaryKeyField(objectClass).getType();
if (foreignKeyType == Long.class) { if (foreignKeyType == Long.class) {
querySelect.append(SEPARATOR_LONG); querySelect.append(SEPARATOR_LONG);
@ -193,7 +198,7 @@ public class AddOnManyToMany implements DataAccessAddOn {
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
// TODO: manage better the eager and lazy !! // TODO: manage better the eager and lazy !!
if (objectClass == Long.class || objectClass == UUID.class) { if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options); generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options);
} }
final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class); final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class);
@ -234,6 +239,11 @@ public class AddOnManyToMany implements DataAccessAddOn {
field.set(data, idList); field.set(data, idList);
count.inc(); count.inc();
return; return;
} else if (objectClass == ObjectId.class) {
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
} }
final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class); final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class);
if (decorators == null) { if (decorators == null) {
@ -285,6 +295,27 @@ public class AddOnManyToMany implements DataAccessAddOn {
}; };
lazyCall.add(lambda); lazyCall.add(lambda);
} }
} else if (foreignKeyType == ObjectId.class) {
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
// field.set(data, idList);
count.inc();
if (idList != null && idList.size() > 0) {
final FieldName idField = AnnotationTools.getFieldName(AnnotationTools.getIdField(objectClass),
options);
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
final List<ObjectId> childs = new ArrayList<>(idList);
// TODO: update to have get with abstract types ....
@SuppressWarnings("unchecked")
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryInList<>(idField.inTable(), childs)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} }
} }
} }
@ -309,9 +340,10 @@ public class AddOnManyToMany implements DataAccessAddOn {
} }
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
if (objectClass != Long.class && objectClass != UUID.class) { if (objectClass != Long.class && objectClass != UUID.class && objectClass != ObjectId.class) {
throw new DataAccessException("Can not ManyToMany with other than List<Long> or List<UUID> Model: List<" throw new DataAccessException(
+ objectClass.getCanonicalName() + ">"); "Can not ManyToMany with other than List<Long> or List<UUID> or List<ObjectId> Model: List<"
+ objectClass.getCanonicalName() + ">");
} }
final FieldName columnName = AnnotationTools.getFieldName(field, options); final FieldName columnName = AnnotationTools.getFieldName(field, options);
final String linkTableName = generateLinkTableName(tableName, columnName.inTable()); final String linkTableName = generateLinkTableName(tableName, columnName.inTable());
@ -348,9 +380,10 @@ public class AddOnManyToMany implements DataAccessAddOn {
} }
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
if (objectClass != Long.class && objectClass != UUID.class) { if (objectClass != Long.class && objectClass != UUID.class && objectClass != ObjectId.class) {
throw new DataAccessException("Can not ManyToMany with other than List<Long> or List<UUID> Model: List<" throw new DataAccessException(
+ objectClass.getCanonicalName() + ">"); "Can not ManyToMany with other than List<Long> or List<UUID> or List<ObjectId> Model: List<"
+ objectClass.getCanonicalName() + ">");
} }
final FieldName columnName = AnnotationTools.getFieldName(field, options); final FieldName columnName = AnnotationTools.getFieldName(field, options);
final String linkTableName = generateLinkTableName(tableName, columnName.inTable()); final String linkTableName = generateLinkTableName(tableName, columnName.inTable());

View File

@ -7,6 +7,7 @@ import java.sql.Types;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName; import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut; import org.kar.archidata.dataAccess.CountInOut;
@ -66,6 +67,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
ps.setNull(iii.value, Types.VARCHAR); ps.setNull(iii.value, Types.VARCHAR);
} else if (field.getType() == UUID.class) { } else if (field.getType() == UUID.class) {
ps.setNull(iii.value, Types.BINARY); ps.setNull(iii.value, Types.BINARY);
} else if (field.getType() == ObjectId.class) {
ps.setNull(iii.value, Types.BINARY);
} }
} else if (field.getType() == Long.class) { } else if (field.getType() == Long.class) {
final Long dataTyped = (Long) data; final Long dataTyped = (Long) data;
@ -84,6 +87,11 @@ public class AddOnManyToOne implements DataAccessAddOn {
LOGGER.info("Generate UUTD for DB: {}", dataTyped); LOGGER.info("Generate UUTD for DB: {}", dataTyped);
final byte[] dataByte = UuidUtils.asBytes(dataTyped); final byte[] dataByte = UuidUtils.asBytes(dataTyped);
ps.setBytes(iii.value, dataByte); ps.setBytes(iii.value, dataByte);
} else if (field.getType() == ObjectId.class) {
final ObjectId dataTyped = (ObjectId) data;
LOGGER.info("Generate ObjectId for DB: {}", dataTyped);
final byte[] dataByte = dataTyped.toByteArray();
ps.setBytes(iii.value, dataByte);
} else { } else {
final Field idField = AnnotationTools.getFieldOfId(field.getType()); final Field idField = AnnotationTools.getFieldOfId(field.getType());
final Object uid = idField.get(data); final Object uid = idField.get(data);
@ -101,7 +109,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
@Override @Override
public boolean canInsert(final Field field) { public boolean canInsert(final Field field) {
if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class
|| field.getType() == String.class || field.getType() == UUID.class) { || field.getType() == String.class || field.getType() == UUID.class
|| field.getType() == ObjectId.class) {
return true; return true;
} }
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class); final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
@ -120,7 +129,7 @@ public class AddOnManyToOne implements DataAccessAddOn {
public boolean canRetrieve(final Field field) { public boolean canRetrieve(final Field field) {
final Class<?> classType = field.getType(); final Class<?> classType = field.getType();
if (classType == Long.class || classType == Integer.class || classType == Short.class if (classType == Long.class || classType == Integer.class || classType == Short.class
|| classType == String.class || classType == UUID.class) { || classType == String.class || classType == UUID.class || classType == ObjectId.class) {
return true; return true;
} }
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class); final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
@ -141,7 +150,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
@NotNull final CountInOut count, @NotNull final CountInOut count,
final QueryOptions options) throws Exception { final QueryOptions options) throws Exception {
if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class
|| field.getType() == String.class || field.getType() == UUID.class) { || field.getType() == String.class || field.getType() == UUID.class
|| field.getType() == ObjectId.class) {
querySelect.append(" "); querySelect.append(" ");
querySelect.append(tableName); querySelect.append(tableName);
querySelect.append("."); querySelect.append(".");
@ -230,6 +240,15 @@ public class AddOnManyToOne implements DataAccessAddOn {
} }
return; return;
} }
if (field.getType() == ObjectId.class) {
final byte[] tmp = rs.getBytes(count.value);
count.inc();
if (!rs.wasNull()) {
final ObjectId foreignKey = new ObjectId(tmp);
field.set(data, foreignKey);
}
return;
}
final Class<?> objectClass = field.getType(); final Class<?> objectClass = field.getType();
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class); final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
if (decorators == null) { if (decorators == null) {
@ -279,6 +298,22 @@ public class AddOnManyToOne implements DataAccessAddOn {
}; };
lazyCall.add(lambda); lazyCall.add(lambda);
} }
} else if (remotePrimaryKeyType == ObjectId.class) {
// here we have the field, the data and the the remote value ==> can create callback that generate the update of the value ...
final ObjectId foreignKey = ioDb.getListOfRawOID(rs, count.value);
count.inc();
if (foreignKey != null) {
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.get(decorators.targetEntity(), foreignKey);
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} }
} }
} }
@ -298,7 +333,7 @@ public class AddOnManyToOne implements DataAccessAddOn {
final QueryOptions options) throws Exception { final QueryOptions options) throws Exception {
final Class<?> classType = field.getType(); final Class<?> classType = field.getType();
if (classType == Long.class || classType == Integer.class || classType == Short.class if (classType == Long.class || classType == Integer.class || classType == Short.class
|| classType == String.class || classType == UUID.class) { || classType == String.class || classType == UUID.class || classType == ObjectId.class) {
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList, DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, classType, options); postActionList, createIfNotExist, createDrop, fieldId, classType, options);
} else { } else {

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName; import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut; import org.kar.archidata.dataAccess.CountInOut;
@ -110,7 +111,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
} }
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) { if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
return true; return true;
} }
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class); final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
@ -201,7 +202,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
return; return;
} }
// TODO: manage better the eager and lazy !! // TODO: manage better the eager and lazy !!
if (objectClass == Long.class || objectClass == UUID.class) { if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options, generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options,
decorators.targetEntity(), decorators.mappedBy()); decorators.targetEntity(), decorators.mappedBy());
return; return;
@ -250,22 +251,35 @@ public class AddOnOneToMany implements DataAccessAddOn {
field.set(data, idList); field.set(data, idList);
count.inc(); count.inc();
return; return;
} else if (objectClass == ObjectId.class) {
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
} }
if (objectClass == decorators.targetEntity()) { if (objectClass == decorators.targetEntity()) {
Long parentIdTmp = null; Long parentIdTmp = null;
UUID parendUuidTmp = null; UUID parendUuidTmp = null;
ObjectId parendOidTmp = null;
try { try {
final String modelData = rs.getString(count.value); final String modelData = rs.getString(count.value);
parentIdTmp = Long.valueOf(modelData); parentIdTmp = Long.valueOf(modelData);
count.inc(); count.inc();
} catch (final NumberFormatException ex) { } catch (final NumberFormatException ex) {
final List<UUID> idList = ioDb.getListOfRawUUIDs(rs, count.value); try {
parendUuidTmp = idList.get(0); final List<UUID> idList = ioDb.getListOfRawUUIDs(rs, count.value);
count.inc(); parendUuidTmp = idList.get(0);
count.inc();
} catch (final NumberFormatException ex2) {
// TODO : How to manage ObjectId ==> I am not sure it works well...
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
parendOidTmp = idList.get(0);
count.inc();
}
} }
final Long parentId = parentIdTmp; final Long parentId = parentIdTmp;
final UUID parendUuid = parendUuidTmp; final UUID parendUuid = parendUuidTmp;
final ObjectId parendOid = parendOidTmp;
final String mappingKey = decorators.mappedBy(); final String mappingKey = decorators.mappedBy();
// We get the parent ID ... ==> need to request the list of elements // We get the parent ID ... ==> need to request the list of elements
if (objectClass == Long.class) { if (objectClass == Long.class) {
@ -276,6 +290,10 @@ public class AddOnOneToMany implements DataAccessAddOn {
LOGGER.error("Need to retreive all primary key of all elements"); LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList); //field.set(data, idList);
return; return;
} else if (objectClass == ObjectId.class) {
LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList);
return;
} }
if (objectClass == decorators.targetEntity()) { if (objectClass == decorators.targetEntity()) {
if (decorators.fetch() == FetchType.EAGER) { if (decorators.fetch() == FetchType.EAGER) {
@ -303,6 +321,17 @@ public class AddOnOneToMany implements DataAccessAddOn {
field.set(data, foreignData); field.set(data, foreignData);
}; };
lazyCall.add(lambda); lazyCall.add(lambda);
} else if (parendOid != null) {
final LazyGetter lambda = () -> {
@SuppressWarnings("unchecked")
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendOid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
} }
} }
} }

View File

@ -1,6 +1,9 @@
package org.kar.archidata.exception; package org.kar.archidata.exception;
import java.util.List;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.kar.archidata.catcher.RestInputError;
public class RESTErrorResponseException extends Exception { public class RESTErrorResponseException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -10,6 +13,7 @@ public class RESTErrorResponseException extends Exception {
public String message; public String message;
public int status; public int status;
public String statusMessage; public String statusMessage;
public List<RestInputError> inputError;
public RESTErrorResponseException() { public RESTErrorResponseException() {
this.oid = new ObjectId(); this.oid = new ObjectId();
@ -18,16 +22,18 @@ public class RESTErrorResponseException extends Exception {
this.message = null; this.message = null;
this.status = 0; this.status = 0;
this.statusMessage = null; this.statusMessage = null;
this.inputError = null;
} }
public RESTErrorResponseException(final ObjectId oid, final String time, final String name, final String message, public RESTErrorResponseException(final ObjectId oid, final String time, final String name, final String message,
final int status, final String statusMessage) { final int status, final String statusMessage, final List<RestInputError> inputError) {
this.oid = oid; this.oid = oid;
this.time = time; this.time = time;
this.name = name; this.name = name;
this.message = message; this.message = message;
this.status = status; this.status = status;
this.statusMessage = statusMessage; this.statusMessage = statusMessage;
this.inputError = inputError;
} }
@Override @Override

View File

@ -151,13 +151,6 @@ public class DotClassElement {
return ".optional()"; return ".optional()";
} }
public String readOnlyZod(final FieldProperty field) {
if (field.readOnly()) {
return ".readonly()";
}
return "";
}
public String generateBaseObject() { public String generateBaseObject() {
final StringBuilder out = new StringBuilder(); final StringBuilder out = new StringBuilder();
return out.toString(); return out.toString();

View File

@ -9,9 +9,13 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.kar.archidata.annotation.AnnotationTools;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.core.Context;
public class ApiModel { public class ApiModel {
static final Logger LOGGER = LoggerFactory.getLogger(ApiModel.class); static final Logger LOGGER = LoggerFactory.getLogger(ApiModel.class);
@ -37,6 +41,8 @@ public class ApiModel {
public String name; public String name;
// list of all parameters (/{key}/... // list of all parameters (/{key}/...
public final Map<String, List<ClassModel>> parameters = new HashMap<>(); public final Map<String, List<ClassModel>> parameters = new HashMap<>();
// list of all headers of the request (/{key}/...
public final Map<String, OptionalClassModel> headers = new HashMap<>();
// list of all query (?key...) // list of all query (?key...)
public final Map<String, List<ClassModel>> queries = new HashMap<>(); public final Map<String, List<ClassModel>> queries = new HashMap<>();
// when request multi-part, need to separate it. // when request multi-part, need to separate it.
@ -153,11 +159,12 @@ public class ApiModel {
} else { } else {
parameterModel.add(previousModel.add(parameterType)); 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 pathParam = ApiTool.apiAnnotationGetPathParam(parameter);
final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter); final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter);
final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter); final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter);
final boolean formDataParamOptional = ApiTool.apiAnnotationGetFormDataOptional(parameter); final boolean apiInputOptional = ApiTool.apiAnnotationGetApiInputOptional(parameter);
if (queryParam != null) { if (queryParam != null) {
if (!this.queries.containsKey(queryParam)) { if (!this.queries.containsKey(queryParam)) {
this.queries.put(queryParam, parameterModel); this.queries.put(queryParam, parameterModel);
@ -169,7 +176,13 @@ public class ApiModel {
} else if (formDataParam != null) { } else if (formDataParam != null) {
if (!this.multiPartParameters.containsKey(formDataParam)) { if (!this.multiPartParameters.containsKey(formDataParam)) {
this.multiPartParameters.put(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 { } else {
this.unnamedElement.addAll(parameterModel); this.unnamedElement.addAll(parameterModel);

View File

@ -7,9 +7,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataParam; import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType; import org.kar.archidata.annotation.apiGenerator.ApiInputOptional;
import org.kar.archidata.annotation.FormDataOptional; import org.kar.archidata.annotation.apiGenerator.ApiAsyncType;
import org.kar.archidata.annotation.TypeScriptProgress; import org.kar.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.kar.archidata.annotation.method.ARCHIVE; import org.kar.archidata.annotation.method.ARCHIVE;
import org.kar.archidata.annotation.method.RESTORE; import org.kar.archidata.annotation.method.RESTORE;
@ -53,7 +53,7 @@ public class ApiTool {
} }
public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception { 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) { if (annotation.length == 0) {
return false; return false;
} }
@ -159,8 +159,8 @@ public class ApiTool {
return ((QueryParam) annotation[0]).value(); return ((QueryParam) annotation[0]).value();
} }
public static boolean apiAnnotationGetFormDataOptional(final Parameter element) throws Exception { public static boolean apiAnnotationGetApiInputOptional(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataOptional.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiInputOptional.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
} }
@ -176,19 +176,19 @@ public class ApiTool {
} }
public static Class<?>[] apiAnnotationGetAsyncType(final Parameter element) throws Exception { 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) { if (annotation.length == 0) {
return null; return null;
} }
return ((AsyncType) annotation[0]).value(); return ((ApiAsyncType) annotation[0]).value();
} }
public static Class<?>[] apiAnnotationGetAsyncType(final Method element) throws Exception { 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) { if (annotation.length == 0) {
return null; return null;
} }
return ((AsyncType) annotation[0]).value(); return ((ApiAsyncType) annotation[0]).value();
} }
public static List<String> apiAnnotationGetConsumes(final Method element) throws Exception { public static List<String> apiAnnotationGetConsumes(final Method element) throws Exception {

View File

@ -6,11 +6,14 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; 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) { protected ClassEnumModel(final Class<?> clazz) {
this.originClasses = clazz; this.originClasses = clazz;
this.noWriteSpecificMode = true; this.apiGenerationMode = AnnotationCreator.createAnnotation(ApiGenerationMode.class, "readable", true,
"creatable", false, "updatable", false);
} }
@Override @Override

View File

@ -8,18 +8,24 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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 { public abstract class ClassModel {
private static final Logger LOGGER = LoggerFactory.getLogger(ClassModel.class);
protected boolean analyzeDone = false; protected boolean analyzeDone = false;
protected Class<?> originClasses = null; protected Class<?> originClasses = null;
protected boolean noWriteSpecificMode = false; protected ApiGenerationMode apiGenerationMode = AnnotationCreator.createAnnotation(ApiGenerationMode.class);
protected List<ClassModel> dependencyModels = new ArrayList<>(); protected List<ClassModel> dependencyModels = new ArrayList<>();
public Class<?> getOriginClasses() { public Class<?> getOriginClasses() {
return this.originClasses; return this.originClasses;
} }
public boolean isNoWriteSpecificMode() { public ApiGenerationMode getApiGenerationMode() {
return this.noWriteSpecificMode; return this.apiGenerationMode;
} }
protected boolean isCompatible(final Class<?> clazz) { protected boolean isCompatible(final Class<?> clazz) {
@ -76,7 +82,15 @@ public abstract class ClassModel {
public abstract Set<ClassModel> getAlls(); public abstract Set<ClassModel> getAlls();
public List<String> getReadOnlyField() { public List<String> getReadOnlyFields() {
return List.of();
}
public List<String> getCreateFields() {
return List.of();
}
public List<String> getUpdateFields() {
return List.of(); return List.of();
} }

View File

@ -11,7 +11,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.kar.archidata.annotation.AnnotationTools; 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.exception.DataAccessException;
import org.kar.archidata.tools.AnnotationCreator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,6 +34,10 @@ public class ClassObjectModel extends ClassModel {
public ClassObjectModel(final Class<?> clazz) { public ClassObjectModel(final Class<?> clazz) {
this.originClasses = clazz; this.originClasses = clazz;
final ApiGenerationMode tmp = AnnotationTools.get(clazz, ApiGenerationMode.class);
if (tmp != null) {
this.apiGenerationMode = tmp;
}
} }
@Override @Override
@ -74,15 +81,16 @@ public class ClassObjectModel extends ClassModel {
DecimalMax decimalMax, DecimalMax decimalMax,
Pattern pattern, Pattern pattern,
Email email, Email email,
Boolean readOnly, ApiAccessLimitation accessLimitation,
Boolean notNull, Boolean notNull,
Boolean columnNotNull, Boolean columnNotNull,
Boolean nullable) { Boolean nullable) {
public FieldProperty(final String name, final ClassModel model, final ClassModel linkClass, 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 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 DecimalMax decimalMax, final Pattern pattern, final Email email,
final Boolean notNull, final Boolean columnNotNull, final Boolean nullable) { final ApiAccessLimitation accessLimitation, final Boolean notNull, final Boolean columnNotNull,
final Boolean nullable) {
this.name = name; this.name = name;
this.model = model; this.model = model;
this.linkClass = linkClass; this.linkClass = linkClass;
@ -94,7 +102,11 @@ public class ClassObjectModel extends ClassModel {
this.email = email; this.email = email;
this.min = min; this.min = min;
this.max = max; this.max = max;
this.readOnly = readOnly; if (accessLimitation == null) {
this.accessLimitation = AnnotationCreator.createAnnotation(ApiAccessLimitation.class);
} else {
this.accessLimitation = accessLimitation;
}
this.notNull = notNull; this.notNull = notNull;
this.columnNotNull = columnNotNull; this.columnNotNull = columnNotNull;
this.nullable = nullable; this.nullable = nullable;
@ -146,7 +158,7 @@ public class ClassObjectModel extends ClassModel {
AnnotationTools.getConstraintsDecimalMax(field), // AnnotationTools.getConstraintsDecimalMax(field), //
AnnotationTools.getConstraintsPattern(field), // AnnotationTools.getConstraintsPattern(field), //
AnnotationTools.getConstraintsEmail(field), // AnnotationTools.getConstraintsEmail(field), //
AnnotationTools.getSchemaReadOnly(field), // AnnotationTools.get(field, ApiAccessLimitation.class), //
AnnotationTools.getConstraintsNotNull(field), // AnnotationTools.getConstraintsNotNull(field), //
AnnotationTools.getColumnNotNull(field), // AnnotationTools.getColumnNotNull(field), //
AnnotationTools.getNullable(field)); AnnotationTools.getNullable(field));
@ -192,7 +204,6 @@ public class ClassObjectModel extends ClassModel {
} }
this.analyzeDone = true; this.analyzeDone = true;
final Class<?> clazz = this.originClasses; final Class<?> clazz = this.originClasses;
this.noWriteSpecificMode = AnnotationTools.getNoWriteSpecificMode(clazz);
this.isPrimitive = clazz.isPrimitive(); this.isPrimitive = clazz.isPrimitive();
if (this.isPrimitive) { if (this.isPrimitive) {
return; return;
@ -259,15 +270,43 @@ public class ClassObjectModel extends ClassModel {
} }
@Override @Override
public List<String> getReadOnlyField() { public List<String> getReadOnlyFields() {
final List<String> out = new ArrayList<>(); final List<String> out = new ArrayList<>();
for (final FieldProperty field : this.fields) { for (final FieldProperty field : this.fields) {
if (field.readOnly()) { if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) {
out.add(field.name); out.add(field.name);
} }
} }
if (this.extendsClass != null) { if (this.extendsClass != null) {
out.addAll(this.extendsClass.getReadOnlyField()); out.addAll(this.extendsClass.getReadOnlyFields());
}
return out;
}
@Override
public List<String> getCreateFields() {
final List<String> 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<String> getUpdateFields() {
final List<String> 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; return out;
} }

View File

@ -45,7 +45,9 @@ public class TsApiGeneration {
final ClassEnumModel model, final ClassEnumModel model,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
imports.add(model); imports.add(model);
final TsClassElement tsModel = tsGroup.find(model); final TsClassElement tsModel = tsGroup.find(model);
return tsModel.tsTypeName; return tsModel.tsTypeName;
@ -55,34 +57,47 @@ public class TsApiGeneration {
final ClassObjectModel model, final ClassObjectModel model,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
final TsClassElement tsModel = tsGroup.find(model); final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType != DefinedPosition.NATIVE) { if (tsModel.nativeType != DefinedPosition.NATIVE) {
if (importWrite == null || tsModel.models.get(0).isNoWriteSpecificMode()) { if (importCreate != null && tsModel.models.get(0).getApiGenerationMode().create()) {
imports.add(model); importCreate.add(model);
} else if (importUpdate != null && tsModel.models.get(0).getApiGenerationMode().update()) {
importUpdate.add(model);
} else { } else {
importWrite.add(model); imports.add(model);
} }
} }
String out = tsModel.tsTypeName;
if (tsModel.nativeType != DefinedPosition.NORMAL) { 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()) { if (partialObject) {
return tsModel.tsTypeName + "Write"; return "Partial<" + out + ">";
} }
return tsModel.tsTypeName; return out;
} }
public static String generateClassMapModelTypescript( public static String generateClassMapModelTypescript(
final ClassMapModel model, final ClassMapModel model,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
final StringBuilder out = new StringBuilder(); final StringBuilder out = new StringBuilder();
out.append("{[key: "); 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("]: ");
out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importWrite)); out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importUpdate, importCreate,
partialObject));
out.append(";}"); out.append(";}");
return out.toString(); return out.toString();
} }
@ -91,9 +106,12 @@ public class TsApiGeneration {
final ClassListModel model, final ClassListModel model,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
final StringBuilder out = new StringBuilder(); 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("[]"); out.append("[]");
return out.toString(); return out.toString();
} }
@ -102,18 +120,24 @@ public class TsApiGeneration {
final ClassModel model, final ClassModel model,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
if (model instanceof final ClassObjectModel objectModel) { 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) { 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) { 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) { 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); throw new IOException("Impossible model:" + model);
} }
@ -122,7 +146,9 @@ public class TsApiGeneration {
final List<ClassModel> models, final List<ClassModel> models,
final TsClassElementGroup tsGroup, final TsClassElementGroup tsGroup,
final Set<ClassModel> imports, final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException { final Set<ClassModel> importUpdate,
final Set<ClassModel> importCreate,
final boolean partialObject) throws IOException {
if (models.size() == 0) { if (models.size() == 0) {
return "void"; return "void";
} }
@ -134,7 +160,8 @@ public class TsApiGeneration {
} else { } else {
out.append(" | "); out.append(" | ");
} }
final String data = generateClassModelTypescript(model, tsGroup, imports, importWrite); final String data = generateClassModelTypescript(model, tsGroup, imports, importUpdate, importCreate,
partialObject);
out.append(data); out.append(data);
} }
return out.toString(); return out.toString();
@ -159,7 +186,8 @@ public class TsApiGeneration {
final Set<ClassModel> imports = new HashSet<>(); final Set<ClassModel> imports = new HashSet<>();
final Set<ClassModel> zodImports = new HashSet<>(); final Set<ClassModel> zodImports = new HashSet<>();
final Set<ClassModel> isImports = new HashSet<>(); final Set<ClassModel> isImports = new HashSet<>();
final Set<ClassModel> writeImports = new HashSet<>(); final Set<ClassModel> updateImports = new HashSet<>();
final Set<ClassModel> createImports = new HashSet<>();
final Set<String> toolImports = new HashSet<>(); final Set<String> toolImports = new HashSet<>();
for (final ApiModel interfaceElement : element.interfaces) { for (final ApiModel interfaceElement : element.interfaces) {
final List<String> consumes = interfaceElement.consumes; final List<String> consumes = interfaceElement.consumes;
@ -172,6 +200,7 @@ public class TsApiGeneration {
data.append("\n\n"); data.append("\n\n");
data.append(returnComplexModel.replaceAll("(?m)^", "\t")); data.append(returnComplexModel.replaceAll("(?m)^", "\t"));
for (final ClassModel elem : interfaceElement.returnTypes) { 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()); zodImports.addAll(elem.getDependencyGroupModels());
} }
} }
@ -195,6 +224,9 @@ public class TsApiGeneration {
if (interfaceElement.unnamedElement.size() == 1 || interfaceElement.multiPartParameters.size() != 0) { if (interfaceElement.unnamedElement.size() == 1 || interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\t\tdata,"); data.append("\n\t\t\tdata,");
} }
if (!interfaceElement.headers.isEmpty()) {
data.append("\n\t\t\theaders,");
}
if (needGenerateProgress) { if (needGenerateProgress) {
data.append("\n\t\t\tcallbacks,"); data.append("\n\t\t\tcallbacks,");
} }
@ -207,7 +239,8 @@ public class TsApiGeneration {
data.append("\n\t\t\t"); data.append("\n\t\t\t");
data.append(queryEntry.getKey()); data.append(queryEntry.getKey());
data.append("?: "); data.append("?: ");
data.append(generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, null)); data.append(
generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, null, null, false));
data.append(","); data.append(",");
} }
data.append("\n\t\t},"); data.append("\n\t\t},");
@ -218,15 +251,27 @@ public class TsApiGeneration {
data.append("\n\t\t\t"); data.append("\n\t\t\t");
data.append(paramEntry.getKey()); data.append(paramEntry.getKey());
data.append(": "); data.append(": ");
data.append(generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, null)); data.append(
generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, null, null, false));
data.append(","); data.append(",");
} }
data.append("\n\t\t},"); data.append("\n\t\t},");
} }
if (interfaceElement.unnamedElement.size() == 1) { if (interfaceElement.unnamedElement.size() == 1) {
data.append("\n\t\tdata: "); data.append("\n\t\tdata: ");
data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports, if (interfaceElement.restTypeRequest == RestTypeRequest.POST) {
writeImports)); 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(","); data.append(",");
} else if (interfaceElement.multiPartParameters.size() != 0) { } else if (interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\tdata: {"); data.append("\n\t\tdata: {");
@ -238,8 +283,34 @@ public class TsApiGeneration {
data.append("?"); data.append("?");
} }
data.append(": "); data.append(": ");
data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports, if (interfaceElement.restTypeRequest == RestTypeRequest.POST) {
writeImports)); 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<String, OptionalClassModel> 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(",");
} }
data.append("\n\t\t},"); data.append("\n\t\t},");
@ -287,7 +358,7 @@ public class TsApiGeneration {
toolImports.add("RESTRequestJson"); toolImports.add("RESTRequestJson");
} else { } else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports, final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports,
null); null, null, false);
data.append(returnType); data.append(returnType);
data.append("> {"); data.append("> {");
if ("void".equals(returnType)) { if ("void".equals(returnType)) {
@ -332,7 +403,7 @@ public class TsApiGeneration {
data.append("\n\t\t\t\taccept: produce,"); data.append("\n\t\t\t\taccept: produce,");
} else { } else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup,
imports, null); imports, null, null, false);
if (!"void".equals(returnType)) { if (!"void".equals(returnType)) {
for (final String elem : produces) { for (final String elem : produces) {
if (MediaType.APPLICATION_JSON.equals(elem)) { if (MediaType.APPLICATION_JSON.equals(elem)) {
@ -360,6 +431,9 @@ public class TsApiGeneration {
if (needGenerateProgress) { if (needGenerateProgress) {
data.append("\n\t\t\tcallbacks,"); data.append("\n\t\t\tcallbacks,");
} }
if (!interfaceElement.headers.isEmpty()) {
data.append("\n\t\t\theaders,");
}
data.append("\n\t\t}"); data.append("\n\t\t}");
if (returnComplexModel != null) { if (returnComplexModel != null) {
data.append(", is"); data.append(", is");
@ -419,17 +493,30 @@ public class TsApiGeneration {
if (tsModel.nativeType == DefinedPosition.NATIVE) { if (tsModel.nativeType == DefinedPosition.NATIVE) {
continue; continue;
} }
if (!tsModel.models.get(0).getApiGenerationMode().read()) {
continue;
}
finalImportSet.add("Zod" + tsModel.tsTypeName); finalImportSet.add("Zod" + tsModel.tsTypeName);
} }
for (final ClassModel model : writeImports) { for (final ClassModel model : updateImports) {
final TsClassElement tsModel = tsGroup.find(model); final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType != DefinedPosition.NORMAL) { if (tsModel.nativeType != DefinedPosition.NORMAL) {
continue; continue;
} }
if (tsModel.models.get(0).isNoWriteSpecificMode()) { if (!tsModel.models.get(0).getApiGenerationMode().update()) {
continue; 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) { if (finalImportSet.size() != 0) {

View File

@ -27,6 +27,9 @@ public class TsClassElement {
NORMAL // Normal Object to interpret. NORMAL // Normal Object to interpret.
} }
public static final String MODEL_TYPE_UPDATE = "Update";
public static final String MODEL_TYPE_CREATE = "Create";
public List<ClassModel> models; public List<ClassModel> models;
public String zodName; public String zodName;
public String tsTypeName; public String tsTypeName;
@ -138,7 +141,7 @@ public class TsClassElement {
out.append(this.tsTypeName); out.append(this.tsTypeName);
out.append(");\n"); out.append(");\n");
} }
out.append(generateExportCheckFunctionWrite("")); out.append(generateExportCheckFunctionAppended(""));
return out.toString(); return out.toString();
} }
@ -168,9 +171,9 @@ public class TsClassElement {
return out.toString(); return out.toString();
} }
private String generateExportCheckFunctionWrite(final String writeString) { private String generateExportCheckFunctionAppended(final String appendString) {
return generateExportCheckFunction(this.tsCheckType + writeString, this.tsTypeName + writeString, return generateExportCheckFunction(this.tsCheckType + appendString, this.tsTypeName + appendString,
this.zodName + writeString); this.zodName + appendString);
} }
public String generateImports(final List<ClassModel> depModels, final TsClassElementGroup tsGroup) public String generateImports(final List<ClassModel> depModels, final TsClassElementGroup tsGroup)
@ -180,11 +183,23 @@ public class TsClassElement {
final TsClassElement tsModel = tsGroup.find(depModel); final TsClassElement tsModel = tsGroup.find(depModel);
if (tsModel.nativeType != DefinedPosition.NATIVE) { if (tsModel.nativeType != DefinedPosition.NATIVE) {
out.append("import {"); out.append("import {");
out.append(tsModel.zodName); if (tsModel.nativeType != DefinedPosition.NORMAL
if (tsModel.nativeType == DefinedPosition.NORMAL && !(tsModel.models.get(0).isNoWriteSpecificMode())) { || 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(", ");
out.append(tsModel.zodName); 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("} from \"./");
out.append(tsModel.fileName); out.append(tsModel.fileName);
@ -321,7 +336,7 @@ public class TsClassElement {
} }
public String readOnlyZod(final FieldProperty field) { public String readOnlyZod(final FieldProperty field) {
if (field.readOnly()) { if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) {
return ".readonly()"; return ".readonly()";
} }
return ""; return "";
@ -343,26 +358,43 @@ public class TsClassElement {
public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException { public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException {
final StringBuilder out = new StringBuilder(); final StringBuilder out = new StringBuilder();
out.append(getBaseHeader()); out.append(getBaseHeader());
out.append(generateImports(model.getDependencyModels(), tsGroup)); out.append(generateImports(model.getDependencyModels(), tsGroup));
out.append("\n"); 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(generateComment(model));
out.append("export const "); out.append("export const ");
out.append(this.zodName); out.append(this.zodName);
out.append(" = "); out.append(" = ");
// Check if the object is empty:
final boolean isEmpty = model.getFields().size() == 0;
if (model.getExtendsClass() != null) { if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass(); final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass); final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName); out.append(tsParentModel.zodName);
out.append(".extend({"); if (!isEmpty) {
out.append(".extend({\n");
}
} else { } else {
out.append("zod.object({"); out.append("zod.object({\n");
} }
out.append("\n");
for (final FieldProperty field : model.getFields()) { for (final FieldProperty field : model.getFields()) {
final ClassModel fieldModel = field.model(); final ClassModel fieldModel = field.model();
if (field.comment() != null) { if (field.comment() != null) {
@ -389,89 +421,138 @@ public class TsClassElement {
out.append(optionalTypeZod(field)); out.append(optionalTypeZod(field));
out.append(",\n"); out.append(",\n");
} }
final List<String> omitField = model.getReadOnlyField(); if (model.getExtendsClass() != null && isEmpty) {
out.append("\n});\n"); out.append(";\n");
out.append(generateZodInfer(this.tsTypeName, this.zodName)); } else {
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 = ");
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName);
out.append("Write");
out.append(".extend({");
} else {
out.append("zod.object({");
}
out.append("\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");
}
out.append("\n});\n"); 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");
*/
} }
out.append(generateZodInfer(this.tsTypeName, this.zodName));
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");
}
} 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(); return out.toString();
} }

View File

@ -11,6 +11,7 @@ import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.db.DbConfig; import org.kar.archidata.db.DbConfig;
import org.kar.archidata.migration.model.Migration; import org.kar.archidata.migration.model.Migration;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -130,35 +131,40 @@ public class MigrationEngine {
} }
private void createTableIfAbleOrWaitAdmin(final DbConfig configInput) throws MigrationException { private void createTableIfAbleOrWaitAdmin(final DbConfig configInput) throws MigrationException {
final DbConfig config = configInput.clone(); if (ConfigBaseVariable.getDBAbleToCreate()) {
config.setDbName(null); final DbConfig config = configInput.clone();
final String dbName = configInput.getDbName(); config.setDbName(null);
LOGGER.info("Verify existance of '{}'", dbName); final String dbName = configInput.getDbName();
try (final DBAccess da = DBAccess.createInterface(config)) { LOGGER.info("Verify existance of '{}'", dbName);
boolean exist = da.isDBExist(dbName); try (final DBAccess da = DBAccess.createInterface(config)) {
if (!exist) { boolean exist = da.isDBExist(dbName);
LOGGER.warn("DB: '{}' DOES NOT EXIST ==> create one", dbName); if (!exist) {
// create the local DB: LOGGER.warn("DB: '{}' DOES NOT EXIST ==> create one", dbName);
da.createDB(dbName); // create the local DB:
} da.createDB(dbName);
exist = da.isDBExist(dbName);
while (!exist) {
LOGGER.error("DB: '{}' DOES NOT EXIST after trying to create one ", dbName);
LOGGER.error("Waiting administrator create a new one, we check after 30 seconds...");
try {
Thread.sleep(30000);
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
exist = da.isDBExist(dbName); exist = da.isDBExist(dbName);
while (!exist) {
LOGGER.error("DB: '{}' DOES NOT EXIST after trying to create one ", dbName);
LOGGER.error("Waiting administrator create a new one, we check after 30 seconds...");
try {
Thread.sleep(30000);
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
exist = da.isDBExist(dbName);
}
} catch (final InternalServerErrorException e) {
e.printStackTrace();
throw new MigrationException("TODO ...");
} catch (final IOException e) {
e.printStackTrace();
throw new MigrationException("TODO ...");
} }
} catch (final InternalServerErrorException e) { } else {
e.printStackTrace(); final String dbName = configInput.getDbName();
throw new MigrationException("TODO ..."); LOGGER.warn("DB: '{}' is not check if it EXIST", dbName);
} catch (final IOException e) {
e.printStackTrace();
throw new MigrationException("TODO ...");
} }
} }

View File

@ -1,6 +1,7 @@
package org.kar.archidata.model; package org.kar.archidata.model;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@ -16,12 +17,15 @@ public class Data extends OIDGenericDataSoftDelete {
@Column(length = 128, nullable = false) @Column(length = 128, nullable = false)
@Schema(description = "Sha512 of the data") @Schema(description = "Sha512 of the data")
@Size(max = 512) @Size(max = 512)
@ApiAccessLimitation(creatable = false, updatable = false)
public String sha512; public String sha512;
@Column(length = 128, nullable = false) @Column(length = 128, nullable = false)
@Schema(description = "Mime -type of the media") @Schema(description = "Mime -type of the media")
@Size(max = 512) @Size(max = 512)
@ApiAccessLimitation(creatable = false, updatable = false)
public String mimeType; public String mimeType;
@Column(nullable = false) @Column(nullable = false)
@Schema(description = "Size in Byte of the data") @Schema(description = "Size in Byte of the data")
@ApiAccessLimitation(creatable = false, updatable = false)
public Long size; public Long size;
} }

View File

@ -1,15 +1,20 @@
package org.kar.archidata.model; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ApiGenerationMode(create = true, update = true)
public class GenericData extends GenericTiming { public class GenericData extends GenericTiming {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, unique = true) @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; public Long id = null;
} }

View File

@ -2,18 +2,22 @@ package org.kar.archidata.model;
import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataDeleted;
import org.kar.archidata.annotation.DataNotRead; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@ApiGenerationMode(create = true, update = true)
public class GenericDataSoftDelete extends GenericData { public class GenericDataSoftDelete extends GenericData {
@DataNotRead @DataNotRead
@Column(nullable = false) @Column(nullable = false)
@DefaultValue("'0'") @DefaultValue("'0'")
@DataDeleted @DataDeleted
@Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) @Schema(description = "Deleted state", hidden = true)
@Nullable @Nullable
@ApiAccessLimitation(creatable = false, updatable = false)
public Boolean deleted = null; public Boolean deleted = null;
} }

View File

@ -5,6 +5,8 @@ import java.util.Date;
import org.kar.archidata.annotation.CreationTimestamp; import org.kar.archidata.annotation.CreationTimestamp;
import org.kar.archidata.annotation.DataNotRead; import org.kar.archidata.annotation.DataNotRead;
import org.kar.archidata.annotation.UpdateTimestamp; 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; import com.fasterxml.jackson.annotation.JsonFormat;
@ -12,20 +14,22 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@ApiGenerationMode(create = true, update = true)
public class GenericTiming { public class GenericTiming {
@DataNotRead @DataNotRead
@CreationTimestamp @CreationTimestamp
@Column(nullable = false) @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") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@Nullable @Nullable
@ApiAccessLimitation(creatable = false, updatable = false)
public Date createdAt = null; public Date createdAt = null;
@DataNotRead @DataNotRead
@UpdateTimestamp @UpdateTimestamp
@Column(nullable = false) @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") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
// public Instant updatedAt = null;
@Nullable @Nullable
@ApiAccessLimitation(creatable = false, updatable = false)
public Date updatedAt = null; public Date updatedAt = null;
} }

View File

@ -1,17 +1,21 @@
package org.kar.archidata.model; package org.kar.archidata.model;
import org.bson.types.ObjectId; 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 dev.morphia.annotations.Id;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ApiGenerationMode(create = true, update = true)
public class OIDGenericData extends GenericTiming { public class OIDGenericData extends GenericTiming {
@Id @Id
@jakarta.persistence.Id @jakarta.persistence.Id
@Column(nullable = false, unique = true, name = "_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 @NotNull
@ApiAccessLimitation(creatable = false, updatable = false)
public ObjectId oid = null; public ObjectId oid = null;
} }

View File

@ -2,18 +2,22 @@ package org.kar.archidata.model;
import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataDeleted;
import org.kar.archidata.annotation.DataNotRead; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@ApiGenerationMode(create = true, update = true)
public class OIDGenericDataSoftDelete extends OIDGenericData { public class OIDGenericDataSoftDelete extends OIDGenericData {
@DataNotRead @DataNotRead
@Column(nullable = false) @Column(nullable = false)
@DefaultValue("'0'") @DefaultValue("'0'")
@DataDeleted @DataDeleted
@Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) @Schema(description = "Deleted state", hidden = true)
@Nullable @Nullable
@ApiAccessLimitation(creatable = false, updatable = false)
public Boolean deleted = null; public Boolean deleted = null;
} }

View File

@ -2,17 +2,22 @@ package org.kar.archidata.model;
import java.util.UUID; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@ApiGenerationMode(create = true, update = true)
public class UUIDGenericData extends GenericTiming { public class UUIDGenericData extends GenericTiming {
@Id @Id
@DefaultValue("(UUID_TO_BIN(UUID(), TRUE))") @DefaultValue("(UUID_TO_BIN(UUID(), TRUE))")
@Column(nullable = false, unique = 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 @NotNull
@ApiAccessLimitation(creatable = false, updatable = false)
public UUID uuid = null; public UUID uuid = null;
} }

View File

@ -2,18 +2,22 @@ package org.kar.archidata.model;
import org.kar.archidata.annotation.DataDeleted; import org.kar.archidata.annotation.DataDeleted;
import org.kar.archidata.annotation.DataNotRead; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@ApiGenerationMode(create = true, update = true)
public class UUIDGenericDataSoftDelete extends UUIDGenericData { public class UUIDGenericDataSoftDelete extends UUIDGenericData {
@DataNotRead @DataNotRead
@Column(nullable = false) @Column(nullable = false)
@DefaultValue("'0'") @DefaultValue("'0'")
@DataDeleted @DataDeleted
@Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) @Schema(description = "Deleted state", hidden = true)
@Nullable @Nullable
@ApiAccessLimitation(creatable = false, updatable = false)
public Boolean deleted = null; public Boolean deleted = null;
} }

View File

@ -0,0 +1,13 @@
package org.kar.archidata.model.token;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class JwtHeader {
@Size(max = 128)
@NotNull
public String typ;
@Size(max = 128)
@NotNull
public String alg;
}

View File

@ -0,0 +1,29 @@
package org.kar.archidata.model.token;
import java.util.Map;
import jakarta.validation.constraints.NotNull;
public class JwtPayload {
// User identification
@NotNull
public String sub;
// Application destination
@NotNull
public String application;
// Emitter of the token
@NotNull
public String iss;
// Access Right Map<application, Map< section, right>>
@NotNull
public Map<String, Map<String, Long>> right;
// user name
@NotNull
public String login;
// Expiration (timestamp)
@NotNull
public Long exp;
// Create time (timestamp)
@NotNull
public Long iat;
}

View File

@ -0,0 +1,12 @@
package org.kar.archidata.model.token;
import jakarta.validation.constraints.NotNull;
public class JwtToken {
@NotNull
public JwtHeader header;
@NotNull
public JwtPayload payload;
@NotNull
public String signature;
}

View File

@ -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 extends Annotation> A createAnnotation(final Class<A> 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
}
}

View File

@ -3,6 +3,7 @@ package org.kar.archidata.tools;
public class ConfigBaseVariable { public class ConfigBaseVariable {
static public String tmpDataFolder; static public String tmpDataFolder;
static public String dataFolder; static public String dataFolder;
static public String dbAbleToCreate;
static public String dbType; static public String dbType;
static public String dbHost; static public String dbHost;
static public String dbPort; static public String dbPort;
@ -23,6 +24,7 @@ public class ConfigBaseVariable {
public static void clearAllValue() { public static void clearAllValue() {
tmpDataFolder = System.getenv("DATA_TMP_FOLDER"); tmpDataFolder = System.getenv("DATA_TMP_FOLDER");
dataFolder = System.getenv("DATA_FOLDER"); dataFolder = System.getenv("DATA_FOLDER");
dbAbleToCreate = System.getenv("DB_ABLE_TO_CREATE");
dbType = System.getenv("DB_TYPE"); dbType = System.getenv("DB_TYPE");
dbHost = System.getenv("DB_HOST"); dbHost = System.getenv("DB_HOST");
dbPort = System.getenv("DB_PORT"); dbPort = System.getenv("DB_PORT");
@ -58,6 +60,13 @@ public class ConfigBaseVariable {
return dataFolder; return dataFolder;
} }
public static boolean getDBAbleToCreate() {
if (dbAbleToCreate == null) {
return true;
}
return Boolean.getBoolean(dbAbleToCreate);
}
public static String getDBType() { public static String getDBType() {
if (dbType == null) { if (dbType == null) {
return "mysql"; return "mysql";

View File

@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
@ -36,7 +37,7 @@ public class RESTApi {
this.token = token; this.token = token;
} }
public <T> List<T> gets(final Class<T> clazz, final String urlOffset) public <TYPE_RESPONSE> List<TYPE_RESPONSE> gets(final Class<TYPE_RESPONSE> clazz, final String urlOffset)
throws RESTErrorResponseException, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient(); final HttpClient client = HttpClient.newHttpClient();
Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1) Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1)
@ -60,58 +61,79 @@ public class RESTApi {
this.mapper.getTypeFactory().constructCollectionType(List.class, clazz)); this.mapper.getTypeFactory().constructCollectionType(List.class, clazz));
} }
public <T> T get(final Class<T> clazz, final String urlOffset) public <TYPE_RESPONSE> TYPE_RESPONSE get(final Class<TYPE_RESPONSE> clazz, final String urlOffset)
throws RESTErrorResponseException, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendJson("GET", clazz, urlOffset, null); return modelSendJson("GET", clazz, urlOffset, null);
} }
public <T, U> T post(final Class<T> clazz, final String urlOffset, final U data) public <TYPE_RESPONSET, TYPE_BODY> TYPE_RESPONSET post(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSET> clazz,
final String urlOffset,
final TYPE_BODY data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSend("POST", clazz, urlOffset, data); return modelSend("POST", clazz, urlOffset, data);
} }
public <T, U> T postJson(final Class<T> clazz, final String urlOffset, final String body) public <TYPE_RESPONSE> TYPE_RESPONSE postJson(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final String body) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendJson("POST", clazz, urlOffset, body); return modelSendJson("POST", clazz, urlOffset, body);
} }
public <T> T postMap(final Class<T> clazz, final String urlOffset, final Map<String, Object> data) public <TYPE_RESPONSE> TYPE_RESPONSE postMap(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendMap("POST", clazz, urlOffset, data); return modelSendMap("POST", clazz, urlOffset, data);
} }
public <T, U> T put(final Class<T> clazz, final String urlOffset, final U data) public <TYPE_RESPONSE, TYPE_BODY> TYPE_RESPONSE put(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final TYPE_BODY data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSend("PUT", clazz, urlOffset, data); return modelSend("PUT", clazz, urlOffset, data);
} }
public <T, U> T putJson(final Class<T> clazz, final String urlOffset, final String body) public <TYPE_RESPONSE> TYPE_RESPONSE putJson(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final String body) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendJson("PUT", clazz, urlOffset, body); return modelSendJson("PUT", clazz, urlOffset, body);
} }
public <T> T putMap(final Class<T> clazz, final String urlOffset, final Map<String, Object> data) public <TYPE_RESPONSE> TYPE_RESPONSE putMap(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendMap("PUT", clazz, urlOffset, data); return modelSendMap("PUT", clazz, urlOffset, data);
} }
public <T, U> T patch(final Class<T> clazz, final String urlOffset, final U data) public <TYPE_RESPONSE, TYPE_BODY> TYPE_RESPONSE patch(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final TYPE_BODY data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSend("PATCH", clazz, urlOffset, data); return modelSend("PATCH", clazz, urlOffset, data);
} }
public <T, U> T patchJson(final Class<T> clazz, final String urlOffset, final String body) public <TYPE_RESPONSE, TYPE_BODY> TYPE_RESPONSE patchJson(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final String body) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendJson("PATCH", clazz, urlOffset, body); return modelSendJson("PATCH", clazz, urlOffset, body);
} }
public <T> T patchMap(final Class<T> clazz, final String urlOffset, final Map<String, Object> data) public <TYPE_RESPONSE> TYPE_RESPONSE patchMap(
throws RESTErrorResponseException, IOException, InterruptedException { final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException {
return modelSendMap("PATCH", clazz, urlOffset, data); return modelSendMap("PATCH", clazz, urlOffset, data);
} }
protected <T, U> T modelSend(final String model, final Class<T> clazz, final String urlOffset, final U data) protected <TYPE_RESPONSE, TYPE_BODY> TYPE_RESPONSE modelSend(
throws RESTErrorResponseException, IOException, InterruptedException { final String model,
final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
final TYPE_BODY data) throws RESTErrorResponseException, IOException, InterruptedException {
if (data == null) { if (data == null) {
return modelSendJson(model, clazz, urlOffset, null); return modelSendJson(model, clazz, urlOffset, null);
} else { } else {
@ -121,8 +143,11 @@ public class RESTApi {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T, U> T modelSendJson(final String model, final Class<T> clazz, final String urlOffset, String body) public <TYPE_RESPONSE> TYPE_RESPONSE modelSendJson(
throws RESTErrorResponseException, IOException, InterruptedException { final String model,
final Class<TYPE_RESPONSE> clazz,
final String urlOffset,
String body) throws RESTErrorResponseException, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient(); final HttpClient client = HttpClient.newHttpClient();
// client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1) Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1)
@ -146,30 +171,37 @@ public class RESTApi {
final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseException.class); RESTErrorResponseException.class);
throw out; throw out;
} catch (final InvalidDefinitionException ex) {
ex.printStackTrace();
LOGGER.error("body: {}", httpResponse.body());
throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
+ httpResponse.statusCode() + "] " + httpResponse.body());
} catch (final MismatchedInputException ex) { } catch (final MismatchedInputException ex) {
throw new IOException( ex.printStackTrace();
"Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); LOGGER.error("body: {}", httpResponse.body());
throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
+ httpResponse.statusCode() + "] " + httpResponse.body());
} catch (final JsonParseException ex) { } catch (final JsonParseException ex) {
ex.printStackTrace(); ex.printStackTrace();
LOGGER.error("body: {}", httpResponse.body()); LOGGER.error("body: {}", httpResponse.body());
throw new IOException( throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
"Fail to get the ERROR data [" + httpResponse.statusCode() + "] " + httpResponse.body()); + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
if (clazz == Void.class || clazz == void.class) { if (clazz == Void.class || clazz == void.class) {
return null; return null;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T) httpResponse.body(); return (TYPE_RESPONSE) httpResponse.body();
} }
LOGGER.trace("Receive model: {} with data: '{}'", clazz.getCanonicalName(), httpResponse.body()); LOGGER.trace("Receive model: {} with data: '{}'", clazz.getCanonicalName(), httpResponse.body());
return this.mapper.readValue(httpResponse.body(), clazz); return this.mapper.readValue(httpResponse.body(), clazz);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T modelSendMap( public <TYPE_RESPONSE> TYPE_RESPONSE modelSendMap(
final String model, final String model,
final Class<T> clazz, final Class<TYPE_RESPONSE> clazz,
final String urlOffset, final String urlOffset,
final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException { final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient(); final HttpClient client = HttpClient.newHttpClient();
@ -201,7 +233,7 @@ public class RESTApi {
return null; return null;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T) httpResponse.body(); return (TYPE_RESPONSE) httpResponse.body();
} }
return this.mapper.readValue(httpResponse.body(), clazz); return this.mapper.readValue(httpResponse.body(), clazz);
} }
@ -216,12 +248,12 @@ public class RESTApi {
/** /**
* Call a DELETE on a REST API with retrieving some data * Call a DELETE on a REST API with retrieving some data
* @param <T> Type of data that might be received. * @param <TYPE_RESPONSE> Type of data that might be received.
* @param clazz Class model of the data that might be parsed. * @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
* @return The parsed object received. * @return The parsed object received.
*/ */
public <T> T delete(final Class<T> clazz, final String urlOffset) public <TYPE_RESPONSE> TYPE_RESPONSE delete(final Class<TYPE_RESPONSE> clazz, final String urlOffset)
throws RESTErrorResponseException, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return simpleRequest("DELETE", clazz, urlOffset); return simpleRequest("DELETE", clazz, urlOffset);
} }
@ -236,12 +268,12 @@ public class RESTApi {
/** /**
* Call a ARCHIVE on a REST API with retrieving some data * Call a ARCHIVE on a REST API with retrieving some data
* @param <T> Type of data that might be received. * @param <TYPE_RESPONSE> Type of data that might be received.
* @param clazz Class model of the data that might be parsed. * @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
* @return The parsed object received. * @return The parsed object received.
*/ */
public <T> T archive(final Class<T> clazz, final String urlOffset) public <TYPE_RESPONSE> TYPE_RESPONSE archive(final Class<TYPE_RESPONSE> clazz, final String urlOffset)
throws RESTErrorResponseException, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return simpleRequest("ARCHIVE", clazz, urlOffset); return simpleRequest("ARCHIVE", clazz, urlOffset);
} }
@ -268,14 +300,16 @@ public class RESTApi {
/** /**
* Call a key on a REST API with retrieving some data * Call a key on a REST API with retrieving some data
* @param <T> Type of data that might be received. * @param <TYPE_RESPONSE> Type of data that might be received.
* @param model name of the key for the REST call * @param model name of the key for the REST call
* @param clazz Class model of the data that might be parsed. * @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
* @return The parsed object received. * @return The parsed object received.
*/ */
public <T> T simpleRequest(final String model, final Class<T> clazz, final String urlOffset) public <TYPE_RESPONSE> TYPE_RESPONSE simpleRequest(
throws RESTErrorResponseException, IOException, InterruptedException { final String model,
final Class<TYPE_RESPONSE> clazz,
final String urlOffset) throws RESTErrorResponseException, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient(); final HttpClient client = HttpClient.newHttpClient();
Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1) Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1)
.uri(URI.create(this.baseUrl + urlOffset)); .uri(URI.create(this.baseUrl + urlOffset));
@ -298,7 +332,7 @@ public class RESTApi {
return null; return null;
} }
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T) httpResponse.body(); return (TYPE_RESPONSE) httpResponse.body();
} }
return this.mapper.readValue(httpResponse.body(), clazz); return this.mapper.readValue(httpResponse.body(), clazz);
} }

View File

@ -3,30 +3,29 @@
* @copyright 2024, Edouard DUPIN, all right reserved * @copyright 2024, Edouard DUPIN, all right reserved
* @license MPL-2 * @license MPL-2
*/ */
import { RestErrorResponse, isRestErrorResponse } from './model';
import { RestErrorResponse, isRestErrorResponse } from "./model";
export enum HTTPRequestModel { export enum HTTPRequestModel {
ARCHIVE = "ARCHIVE", ARCHIVE = 'ARCHIVE',
DELETE = "DELETE", DELETE = 'DELETE',
HEAD = "HEAD", HEAD = 'HEAD',
GET = "GET", GET = 'GET',
OPTION = "OPTION", OPTION = 'OPTION',
PATCH = "PATCH", PATCH = 'PATCH',
POST = "POST", POST = 'POST',
PUT = "PUT", PUT = 'PUT',
RESTORE = "RESTORE", RESTORE = 'RESTORE',
} }
export enum HTTPMimeType { export enum HTTPMimeType {
ALL = "*/*", ALL = '*/*',
CSV = "text/csv", CSV = 'text/csv',
IMAGE = "image/*", IMAGE = 'image/*',
IMAGE_JPEG = "image/jpeg", IMAGE_JPEG = 'image/jpeg',
IMAGE_PNG = "image/png", IMAGE_PNG = 'image/png',
JSON = "application/json", JSON = 'application/json',
MULTIPART = "multipart/form-data", MULTIPART = 'multipart/form-data',
OCTET_STREAM = "application/octet-stream", OCTET_STREAM = 'application/octet-stream',
TEXT_PLAIN = "text/plain", TEXT_PLAIN = 'text/plain',
} }
export interface RESTConfig { export interface RESTConfig {
@ -54,6 +53,14 @@ export interface ModelResponseHttp {
data: any; data: any;
} }
export type ErrorRestApiCallback = (response: Response) => void;
let errorApiGlobalCallback: ErrorRestApiCallback | undefined = undefined;
export const setErrorApiGlobalCallback = (callback: ErrorRestApiCallback) => {
errorApiGlobalCallback = callback;
};
function isNullOrUndefined(data: any): data is undefined | null { function isNullOrUndefined(data: any): data is undefined | null {
return data === undefined || data === null; return data === undefined || data === null;
} }
@ -78,6 +85,7 @@ export interface RESTRequestType {
data?: any; data?: any;
params?: object; params?: object;
queries?: object; queries?: object;
headers?: any;
callbacks?: RESTCallbacks; callbacks?: RESTCallbacks;
} }
@ -87,15 +95,15 @@ function replaceAll(input, searchValue, replaceValue) {
function removeTrailingSlashes(input: string): string { function removeTrailingSlashes(input: string): string {
if (isNullOrUndefined(input)) { if (isNullOrUndefined(input)) {
return "undefined"; return 'undefined';
} }
return input.replace(/\/+$/, ""); return input.replace(/\/+$/, '');
} }
function removeLeadingSlashes(input: string): string { function removeLeadingSlashes(input: string): string {
if (isNullOrUndefined(input)) { if (isNullOrUndefined(input)) {
return ""; return '';
} }
return input.replace(/^\/+/, ""); return input.replace(/^\/+/, '');
} }
export function RESTUrl({ export function RESTUrl({
@ -133,9 +141,9 @@ export function RESTUrl({
} }
} }
if (restConfig.token !== undefined && restModel.tokenInUrl === true) { 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( export function fetchProgress(
@ -159,7 +167,7 @@ export function fetchProgress(
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Stream the upload progress // Stream the upload progress
if (progressUpload) { if (progressUpload) {
xhr.io?.upload.addEventListener("progress", (dataEvent) => { xhr.io?.upload.addEventListener('progress', (dataEvent) => {
if (dataEvent.lengthComputable) { if (dataEvent.lengthComputable) {
progressUpload(dataEvent.loaded, dataEvent.total); progressUpload(dataEvent.loaded, dataEvent.total);
} }
@ -167,7 +175,7 @@ export function fetchProgress(
} }
// Stream the download progress // Stream the download progress
if (progressDownload) { if (progressDownload) {
xhr.io?.addEventListener("progress", (dataEvent) => { xhr.io?.addEventListener('progress', (dataEvent) => {
if (dataEvent.lengthComputable) { if (dataEvent.lengthComputable) {
progressDownload(dataEvent.loaded, dataEvent.total); progressDownload(dataEvent.loaded, dataEvent.total);
} }
@ -187,19 +195,19 @@ export function fetchProgress(
}; };
} }
// Check if we have an internal Fail: // Check if we have an internal Fail:
xhr.io?.addEventListener("error", () => { xhr.io?.addEventListener('error', () => {
xhr.io = undefined; xhr.io = undefined;
reject(new TypeError("Failed to fetch")); reject(new TypeError('Failed to fetch'));
}); });
// Capture the end of the stream // Capture the end of the stream
xhr.io?.addEventListener("loadend", () => { xhr.io?.addEventListener('loadend', () => {
if (xhr.io?.readyState !== XMLHttpRequest.DONE) { if (xhr.io?.readyState !== XMLHttpRequest.DONE) {
return; return;
} }
if (xhr.io?.status === 0) { if (xhr.io?.status === 0) {
//the stream has been aborted //the stream has been aborted
reject(new TypeError("Fetch has been aborted")); reject(new TypeError('Fetch has been aborted'));
return; return;
} }
// Stream is ended, transform in a generic response: // Stream is ended, transform in a generic response:
@ -209,17 +217,17 @@ export function fetchProgress(
}); });
const headersArray = replaceAll( const headersArray = replaceAll(
xhr.io.getAllResponseHeaders().trim(), xhr.io.getAllResponseHeaders().trim(),
"\r\n", '\r\n',
"\n" '\n'
).split("\n"); ).split('\n');
headersArray.forEach(function (header) { headersArray.forEach(function (header) {
const firstColonIndex = header.indexOf(":"); const firstColonIndex = header.indexOf(':');
if (firstColonIndex !== -1) { if (firstColonIndex !== -1) {
const key = header.substring(0, firstColonIndex).trim(); const key = header.substring(0, firstColonIndex).trim();
const value = header.substring(firstColonIndex + 1).trim(); const value = header.substring(firstColonIndex + 1).trim();
response.headers.set(key, value); response.headers.set(key, value);
} else { } else {
response.headers.set(header, ""); response.headers.set(header, '');
} }
}); });
xhr.io = undefined; xhr.io = undefined;
@ -241,27 +249,29 @@ export function RESTRequest({
data, data,
params, params,
queries, queries,
headers = {},
callbacks, callbacks,
}: RESTRequestType): Promise<ModelResponseHttp> { }: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH: // Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
let headers: any = {};
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { if (restConfig.token !== undefined && restModel.tokenInUrl !== true) {
headers["Authorization"] = `Bearer ${restConfig.token}`; headers['Authorization'] = `Bearer ${restConfig.token}`;
} }
if (restModel.accept !== undefined) { if (restModel.accept !== undefined) {
headers["Accept"] = restModel.accept; headers['Accept'] = restModel.accept;
} }
if (restModel.requestType !== HTTPRequestModel.GET && if (
restModel.requestType !== HTTPRequestModel.ARCHIVE && restModel.requestType !== HTTPRequestModel.GET &&
restModel.requestType !== HTTPRequestModel.RESTORE restModel.requestType !== HTTPRequestModel.ARCHIVE &&
restModel.requestType !== HTTPRequestModel.RESTORE
) { ) {
// if Get we have not a content type, the body is empty // if Get we have not a content type, the body is empty
if (restModel.contentType !== HTTPMimeType.MULTIPART && if (
restModel.contentType !== undefined restModel.contentType !== HTTPMimeType.MULTIPART &&
) { restModel.contentType !== undefined
) {
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" // 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; let body = data;
@ -302,19 +312,27 @@ export function RESTRequest({
} }
action action
.then((response: Response) => { .then((response: 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) { if (response.status >= 200 && response.status <= 299) {
const contentType = response.headers.get("Content-Type"); const contentType = response.headers.get('Content-Type');
if ( if (
!isNullOrUndefined(restModel.accept) && !isNullOrUndefined(restModel.accept) &&
restModel.accept !== contentType restModel.accept !== contentType
) { ) {
reject({ reject({
name: "Model accept type incompatible", name: 'Model accept type incompatible',
time: Date().toString(), time: Date().toString(),
status: 901, status: 901,
message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`, message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`,
statusMessage: "Fetch error", statusMessage: 'Fetch error',
error: "rest-tools.ts Wrong type in the message return type", error: 'rest-tools.ts Wrong type in the message return type',
} as RestErrorResponse); } as RestErrorResponse);
} else if (contentType === HTTPMimeType.JSON) { } else if (contentType === HTTPMimeType.JSON) {
response response
@ -324,12 +342,12 @@ export function RESTRequest({
}) })
.catch((reason: Error) => { .catch((reason: Error) => {
reject({ reject({
name: "API serialization error", name: 'API serialization error',
time: Date().toString(), time: Date().toString(),
status: 902, status: 902,
message: `REST parse json fail: ${reason}`, message: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error", statusMessage: 'Fetch parse error',
error: "rest-tools.ts Wrong message model to parse", error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse); } as RestErrorResponse);
}); });
} else { } else {
@ -349,22 +367,22 @@ export function RESTRequest({
.text() .text()
.then((dataError: string) => { .then((dataError: string) => {
reject({ reject({
name: "API serialization error", name: 'API serialization error',
time: Date().toString(), time: Date().toString(),
status: 903, status: 903,
message: `REST parse error json with wrong type fail. ${dataError}`, message: `REST parse error json with wrong type fail. ${dataError}`,
statusMessage: "Fetch parse error", statusMessage: 'Fetch parse error',
error: "rest-tools.ts Wrong message model to parse", error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse); } as RestErrorResponse);
}) })
.catch((reason: any) => { .catch((reason: any) => {
reject({ reject({
name: "API serialization error", name: 'API serialization error',
time: Date().toString(), time: Date().toString(),
status: response.status, status: response.status,
message: `unmanaged error model: ??? with error: ${reason}`, message: `unmanaged error model: ??? with error: ${reason}`,
statusMessage: "Fetch ERROR parse error", statusMessage: 'Fetch ERROR parse error',
error: "rest-tools.ts Wrong message model to parse", error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse); } as RestErrorResponse);
}); });
} }
@ -374,22 +392,22 @@ export function RESTRequest({
.text() .text()
.then((dataError: string) => { .then((dataError: string) => {
reject({ reject({
name: "API serialization error", name: 'API serialization error',
time: Date().toString(), time: Date().toString(),
status: response.status, status: response.status,
message: `unmanaged error model: ${dataError} with error: ${reason}`, message: `unmanaged error model: ${dataError} with error: ${reason}`,
statusMessage: "Fetch ERROR TEXT parse error", statusMessage: 'Fetch ERROR TEXT parse error',
error: "rest-tools.ts Wrong message model to parse", error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse); } as RestErrorResponse);
}) })
.catch((reason: any) => { .catch((reason: any) => {
reject({ reject({
name: "API serialization error", name: 'API serialization error',
time: Date().toString(), time: Date().toString(),
status: response.status, status: response.status,
message: `unmanaged error model: ??? with error: ${reason}`, message: `unmanaged error model: ??? with error: ${reason}`,
statusMessage: "Fetch ERROR TEXT FAIL", statusMessage: 'Fetch ERROR TEXT FAIL',
error: "rest-tools.ts Wrong message model to parse", error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse); } as RestErrorResponse);
}); });
}); });
@ -400,12 +418,12 @@ export function RESTRequest({
reject(error); reject(error);
} else { } else {
reject({ reject({
name: "Request fail", name: 'Request fail',
time: Date(), time: Date(),
status: 999, status: 999,
message: error, message: error,
statusMessage: "Fetch catch error", statusMessage: 'Fetch catch error',
error: "rest-tools.ts detect an error in the fetch request", error: 'rest-tools.ts detect an error in the fetch request',
}); });
} }
}); });
@ -426,12 +444,12 @@ export function RESTRequestJson<TYPE>(
resolve(value.data); resolve(value.data);
} else { } else {
reject({ reject({
name: "Model check fail", name: 'Model check fail',
time: Date().toString(), time: Date().toString(),
status: 950, status: 950,
error: "REST Fail to verify the data", error: 'REST Fail to verify the data',
statusMessage: "API cast ERROR", statusMessage: 'API cast ERROR',
message: "api.ts Check type as fail", message: 'api.ts Check type as fail',
} as RestErrorResponse); } as RestErrorResponse);
} }
}) })

View File

@ -30,8 +30,6 @@ public class TestTime {
static WebLauncherTest webInterface = null; static WebLauncherTest webInterface = null;
static RESTApi api = null; static RESTApi api = null;
private static Long idTest = 0L;
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
ConfigureDb.configure(); ConfigureDb.configure();

View File

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; 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.ARCHIVE;
import org.kar.archidata.annotation.method.RESTORE; import org.kar.archidata.annotation.method.RESTORE;
import org.kar.archidata.exception.NotFoundException; import org.kar.archidata.exception.NotFoundException;
@ -100,7 +100,7 @@ public class TestResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public SimpleArchiveTable patch( public SimpleArchiveTable patch(
@PathParam("id") final Long id, @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); LOGGER.info("patch({})", id);
throw new NotFoundException("element does not exist: " + id); throw new NotFoundException("element does not exist: " + id);
} }

View File

@ -2,7 +2,7 @@ package test.kar.archidata.apiExtern.resource;
import java.util.List; 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.ARCHIVE;
import org.kar.archidata.annotation.method.RESTORE; import org.kar.archidata.annotation.method.RESTORE;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
@ -66,7 +66,7 @@ public class TestResourceSample {
@Path("{id}") @Path("{id}")
@PermitAll @PermitAll
@Consumes(MediaType.APPLICATION_JSON) @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 { throws Exception {
DataAccess.updateWithJson(SimpleTable.class, id, jsonRequest); DataAccess.updateWithJson(SimpleTable.class, id, jsonRequest);
return DataAccess.get(SimpleTable.class, id); return DataAccess.get(SimpleTable.class, id);

View File

@ -0,0 +1,95 @@
package test.kar.archidata.hybernateValidator;
import java.util.ArrayList;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.exception.RESTErrorResponseException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import test.kar.archidata.ConfigureDb;
import test.kar.archidata.StepwiseExtension;
import test.kar.archidata.apiExtern.Common;
import test.kar.archidata.hybernateValidator.model.ValidatorModel;
import test.kar.archidata.hybernateValidator.model.ValidatorSubModel;
@ExtendWith(StepwiseExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestValidator {
private final static Logger LOGGER = LoggerFactory.getLogger(TestValidator.class);
public final static String ENDPOINT_NAME = "TestResourceValidator";
static WebLauncherTest webInterface = null;
static RESTApi api = null;
@BeforeAll
public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ...");
webInterface = new WebLauncherTest();
LOGGER.info("Clean previous table");
LOGGER.info("Start REST (BEGIN)");
webInterface.process();
LOGGER.info("Start REST (DONE)");
api = new RESTApi(ConfigBaseVariable.apiAdress);
api.setToken(Common.ADMIN_TOKEN);
}
@AfterAll
public static void stopWebServer() throws Exception {
LOGGER.info("Kill the web server");
webInterface.stop();
webInterface = null;
ConfigureDb.clear();
}
@Order(2)
@Test
public void DetectGenericError() throws Exception {
final ValidatorModel data = new ValidatorModel();
data.value = "plop";
data.data = "klsdfsdfsdfsdfj";
data.multipleElement = new ArrayList<>();
ValidatorSubModel tmp = new ValidatorSubModel();
tmp.data = "lkmkmlkmlklm";
data.multipleElement.add(tmp);
tmp = new ValidatorSubModel();
tmp.data = "1";
data.multipleElement.add(tmp);
data.subElement = new ValidatorSubModel();
data.subElement.data = "k";
final RESTErrorResponseException exception = Assertions.assertThrows(RESTErrorResponseException.class,
() -> api.post(void.class, TestValidator.ENDPOINT_NAME + "?queryParametersName=2", data));
Assertions.assertNotNull(exception);
LOGGER.debug("error on input:{}", exception);
Assertions.assertNull(exception.getMessage());
Assertions.assertNotNull(exception.inputError);
Assertions.assertEquals(5, exception.inputError.size());
Assertions.assertEquals("arg0", exception.inputError.get(0).argument);
Assertions.assertEquals(null, exception.inputError.get(0).path);
Assertions.assertEquals("must be greater than or equal to 5", exception.inputError.get(0).message);
Assertions.assertEquals("arg1", exception.inputError.get(1).argument);
Assertions.assertEquals("data", exception.inputError.get(1).path);
Assertions.assertEquals("size must be between 0 and 5", exception.inputError.get(1).message);
Assertions.assertEquals("arg1", exception.inputError.get(2).argument);
Assertions.assertEquals("multipleElement[1].data", exception.inputError.get(2).path);
Assertions.assertEquals("size must be between 2 and 2147483647", exception.inputError.get(2).message);
Assertions.assertEquals("arg1", exception.inputError.get(3).argument);
Assertions.assertEquals("subElement.data", exception.inputError.get(3).path);
Assertions.assertEquals("size must be between 2 and 2147483647", exception.inputError.get(3).message);
Assertions.assertEquals("arg1", exception.inputError.get(4).argument);
Assertions.assertEquals("value", exception.inputError.get(4).path);
Assertions.assertEquals("Field can not be set, it is a read-only field.", exception.inputError.get(4).message);
}
}

View File

@ -0,0 +1,163 @@
package test.kar.archidata.hybernateValidator;
import java.net.URI;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.validation.ValidationFeature;
import org.kar.archidata.UpdateJwtPublicKey;
import org.kar.archidata.catcher.GenericCatcher;
import org.kar.archidata.db.DbConfig;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.filter.CORSFilter;
import org.kar.archidata.filter.OptionFilter;
import org.kar.archidata.migration.MigrationEngine;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.ContextGenericTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.UriBuilder;
import test.kar.archidata.hybernateValidator.resource.TestResourceValidator;
public class WebLauncher {
final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class);
protected UpdateJwtPublicKey keyUpdater = null;
protected HttpServer server = null;
public WebLauncher() {}
private static URI getBaseURI() {
return UriBuilder.fromUri(ConfigBaseVariable.getlocalAddress()).build();
}
public void migrateDB() throws Exception {
WebLauncher.LOGGER.info("Create migration engine");
final MigrationEngine migrationEngine = new MigrationEngine();
WebLauncher.LOGGER.info("Add initialization");
//migrationEngine.setInit(new Initialization());
WebLauncher.LOGGER.info("Add migration since last version");
//migrationEngine.add(new Migration20231126());
WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(new DbConfig());
WebLauncher.LOGGER.info("Migrate the DB [STOP]");
}
public static void main(final String[] args) throws Exception {
WebLauncher.LOGGER.info("[START] application wake UP");
final WebLauncher launcher = new WebLauncher();
launcher.migrateDB();
launcher.process();
WebLauncher.LOGGER.info("end-configure the server & wait finish process:");
Thread.currentThread().join();
WebLauncher.LOGGER.info("STOP Key updater");
launcher.stopOther();
WebLauncher.LOGGER.info("STOP the REST server:");
}
public void plop(final String aaa) {
// List available Image Readers
WebLauncher.LOGGER.trace("Available Image Readers:");
final Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(aaa);
while (readers.hasNext()) {
final ImageReader reader = readers.next();
WebLauncher.LOGGER.trace("Reader: " + reader.getOriginatingProvider().getDescription(null));
WebLauncher.LOGGER.trace("Reader CN: " + reader.getOriginatingProvider().getPluginClassName());
// ImageIO.deregisterServiceProvider(reader.getOriginatingProvider());
}
// List available Image Writers
WebLauncher.LOGGER.trace("\nAvailable Image Writers:");
final Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(aaa);
while (writers.hasNext()) {
final ImageWriter writer = writers.next();
WebLauncher.LOGGER.trace("Writer: " + writer.getOriginatingProvider().getDescription(null));
WebLauncher.LOGGER.trace("Writer CN: " + writer.getOriginatingProvider().getPluginClassName());
}
}
public void process() throws InterruptedException, DataAccessException {
ImageIO.scanForPlugins();
plop("jpeg");
plop("png");
plop("webmp");
plop("webp");
// ===================================================================
// Configure resources
// ===================================================================
final ResourceConfig rc = new ResourceConfig();
// add multipart models ..
rc.register(MultiPartFeature.class);
// global authentication system
rc.register(OptionFilter.class);
// remove cors ==> all time called by an other system...
rc.register(CORSFilter.class);
// register exception catcher
GenericCatcher.addAll(rc);
// add default resource:
rc.register(TestResourceValidator.class);
// enable jersey specific validations (@Valid
rc.register(ValidationFeature.class);
ContextGenericTools.addJsr310(rc);
// add jackson to be discover when we are ins standalone server
rc.register(JacksonFeature.class);
LOGGER.info(" ==> {}", new DbConfig());
LOGGER.info("OAuth service {}", getBaseURI());
this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc);
final HttpServer serverLink = this.server;
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
LOGGER.warn("Stopping server..");
serverLink.shutdownNow();
}
}, "shutdownHook"));
// ===================================================================
// start periodic update of the token ...
// ===================================================================
this.keyUpdater = new UpdateJwtPublicKey();
this.keyUpdater.start();
// ===================================================================
// run JERSEY
// ===================================================================
try {
this.server.start();
LOGGER.info("Jersey app started at {}", getBaseURI());
} catch (final Exception e) {
LOGGER.error("There was an error while starting Grizzly HTTP server.");
e.printStackTrace();
}
}
public void stop() {
if (this.server != null) {
this.server.shutdownNow();
this.server = null;
}
}
public void stopOther() {
this.keyUpdater.kill();
try {
this.keyUpdater.join(4000, 0);
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,19 @@
package test.kar.archidata.hybernateValidator;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebLauncherTest extends WebLauncher {
final private static Logger LOGGER = LoggerFactory.getLogger(WebLauncherTest.class);
public WebLauncherTest() {
LOGGER.debug("Configure REST system");
// for local test:
ConfigBaseVariable.apiAdress = "http://127.0.0.1:12345/test/api/";
// Enable the test mode permit to access to the test token (never use it in production).
ConfigBaseVariable.testMode = "true";
// ConfigBaseVariable.dbPort = "3306";
}
}

View File

@ -0,0 +1,21 @@
package test.kar.archidata.hybernateValidator.model;
import java.util.List;
import org.kar.archidata.annotation.checker.ReadOnlyField;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
public class ValidatorModel {
@ReadOnlyField
public String value;
@Size(max = 5)
public String data;
@Valid
public List<ValidatorSubModel> multipleElement;
@Valid
public ValidatorSubModel subElement;
}

View File

@ -0,0 +1,8 @@
package test.kar.archidata.hybernateValidator.model;
import jakarta.validation.constraints.Size;
public class ValidatorSubModel {
@Size(min = 2)
public String data;
}

View File

@ -0,0 +1,32 @@
package test.kar.archidata.hybernateValidator.resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import test.kar.archidata.apiExtern.resource.TestResource;
import test.kar.archidata.hybernateValidator.model.ValidatorModel;
@Path("/TestResourceValidator")
@Produces({ MediaType.APPLICATION_JSON })
public class TestResourceValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(TestResource.class);
@POST
@PermitAll
@Consumes(MediaType.APPLICATION_JSON)
public void post(final @QueryParam("queryParametersName") @Min(5) Long value, final @Valid ValidatorModel data)
throws Exception {
return;
}
}

View File

@ -1 +1 @@
0.23.2 0.25.0