Compare commits

...

68 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
b0bf103195 [RELEASE] Release v0.23.2 2025-01-30 21:45:26 +01:00
d36c366ab6 [FIX] zod number gte/lte 2025-01-30 21:25:15 +01:00
cddb4dd7fe [DEV] update dev tag version 2025-01-30 10:01:58 +01:00
218fa3be2e [RELEASE] new version 0.23.0 2025-01-29 23:30:01 +01:00
015c38ff5b [FEAT] add @CheckForeignKey but not tested 2025-01-29 23:30:01 +01:00
24590b2a1e [FIX] correct min and mas size of string 2025-01-29 23:10:53 +01:00
c04660c01a [FEAT] simplify annotation tool 2025-01-29 23:09:49 +01:00
3e81673d38 [FIX] normalize comment when check size 2025-01-29 22:43:22 +01:00
69880df4aa [FIX] missing string size checker 2025-01-29 22:41:08 +01:00
510553550f [FIX] remove unneeded comment 2025-01-29 22:40:34 +01:00
e071d3dbf7 [FEAT] correct the output generation of the typescript ==> missing decimal min and decimal max ==> need to test pattern and email 2025-01-29 00:34:49 +01:00
249e6ad2c8 set JPAChecker agnostic of @ManyToOne primary key type 2025-01-28 23:48:40 +01:00
e156e528c1 [FIX] use instance of DB instead of create a new one 2025-01-28 23:44:36 +01:00
89ab0f3b6a [DEV] update dev tag version 2025-01-28 23:27:30 +01:00
3d5a024084 [RELEASE] new version 0.22.0 2025-01-28 23:27:05 +01:00
ba6478182d [API] move the cheker in the correct folder 2025-01-28 23:25:32 +01:00
cc639243fc [FEAT] add support of @DecimalMin and @DecimalMax 2025-01-28 23:17:34 +01:00
24c226e92c [FEAT] add @Size @Min @Max @parttern @email unit test for JPAChecker 2025-01-28 23:16:18 +01:00
b5fcc3e20c [FEAT,API] remove checker from JsonData and creadte @CheckerAnnotation 2025-01-28 23:15:11 +01:00
d028eb2261 Manage correct rights 2025-01-26 23:38:48 +01:00
ca18d3759d [FIX] congig in config 2025-01-25 15:48:35 +01:00
a7c9bb5e1b [FIX] do not set the table name at unknow when null is find 2025-01-25 15:48:04 +01:00
d011b3a587 [FEAT] add decorator CollectionNotEmpty 2025-01-25 14:41:39 +01:00
6974adbfdf [FEAT] add decorator CollectionItemUnique 2025-01-25 14:41:20 +01:00
461aece7a0 [FEAT] add decorator @CollectionItemNotNull 2025-01-25 14:40:07 +01:00
3f15d560ed [FEAT] better interface for dataJson 2025-01-25 14:38:53 +01:00
8f3c14e28d [FEAT] assert when try to insart null data 2025-01-25 12:16:39 +01:00
0d419f651e [FIX] dataJson checker is not responsive for null data pointer 2025-01-25 12:16:13 +01:00
9dad14d200 [FIX] many-many support every key type 2025-01-25 12:15:25 +01:00
990b7c08da [FIX] correct right: set it when no element is requested 2025-01-18 09:57:55 +01:00
7f393a9e44 [FIX] set User block boolean nullable for creation capability (front pb) 2025-01-18 09:57:52 +01:00
c91291dbce [DOC] log better display of path when call filter authentication 2025-01-18 09:57:47 +01:00
d684b5eaa9 [VERSION] update dev tag version 2025-01-11 17:29:13 +01:00
85b27c0b31 [RELEASE] Release v0.21.0 2025-01-11 17:29:10 +01:00
1abbac944d [FIX] correct the bug of multiple access on the add-on list 2025-01-11 10:42:29 +01:00
cef06889ee [DOC] add comment 2025-01-11 10:42:00 +01:00
1e05e8361a [DEV] update UUID error in ObjectId to be simple 2025-01-08 19:54:08 +01:00
4adc097c6b [DEV] rename RESTErrorResponseExeption in RESTErrorResponseException 2025-01-08 19:53:27 +01:00
69e076e991 [VERSION] update dev tag version 2025-01-06 23:47:01 +01:00
104 changed files with 4324 additions and 1276 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.20.4</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,9 +2,14 @@ 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;
import org.kar.archidata.annotation.checker.Checker;
import org.kar.archidata.annotation.checker.CollectionItemNotNull;
import org.kar.archidata.annotation.checker.CollectionItemUnique;
import org.kar.archidata.annotation.checker.CollectionNotEmpty;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.OptionRenameColumn; import org.kar.archidata.dataAccess.options.OptionRenameColumn;
import org.kar.archidata.dataAccess.options.OverrideTableName; import org.kar.archidata.dataAccess.options.OverrideTableName;
@ -24,6 +29,8 @@ import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
@ -35,6 +42,60 @@ 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) {
final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Field element, final Class<TYPE> clazz) {
final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
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) {
@ -85,12 +146,16 @@ public class AnnotationTools {
return tmp; return tmp;
} }
public static boolean getSchemaReadOnly(final Field element) { public static CollectionItemNotNull getCollectionItemNotNull(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); return get(element, CollectionItemNotNull.class);
if (annotation.length == 0) { }
return false;
} public static CollectionItemUnique getCollectionItemUnique(final Field element) {
return ((Schema) annotation[0]).readOnly(); return get(element, CollectionItemUnique.class);
}
public static CollectionNotEmpty getCollectionNotEmpty(final Field element) {
return get(element, CollectionNotEmpty.class);
} }
public static String getSchemaExample(final Class<?> element) { public static String getSchemaExample(final Class<?> element) {
@ -101,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) {
@ -134,51 +191,39 @@ public class AnnotationTools {
} }
public static ManyToOne getManyToOne(final Field element) { public static ManyToOne getManyToOne(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class); return get(element, ManyToOne.class);
if (annotation.length == 0) {
return null;
}
return (ManyToOne) annotation[0];
} }
public static ManyToMany getManyToMany(final Field element) { public static ManyToMany getManyToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToMany.class); return get(element, ManyToMany.class);
if (annotation.length == 0) {
return null;
}
return (ManyToMany) annotation[0];
} }
public static OneToMany getOneToMany(final Field element) { public static OneToMany getOneToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(OneToMany.class); return get(element, OneToMany.class);
if (annotation.length == 0) {
return null;
}
return (OneToMany) annotation[0];
} }
public static DataJson getDataJson(final Field element) { public static DataJson getDataJson(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class); return get(element, DataJson.class);
if (annotation.length == 0) {
return null;
}
return (DataJson) annotation[0];
} }
public static Long getConstraintsMax(final Field element) { public static Checker[] getConstraintsCheckers(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class); return gets(element, Checker.class);
if (annotation.length == 0) {
return null;
}
return ((Max) annotation[0]).value();
} }
public static Long getConstraintsMin(final Field element) { public static DecimalMin getConstraintsDecimalMin(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class); return get(element, DecimalMin.class);
if (annotation.length == 0) { }
return null;
} public static DecimalMax getConstraintsDecimalMax(final Field element) {
return ((Min) annotation[0]).value(); return get(element, DecimalMax.class);
}
public static Max getConstraintsMax(final Field element) {
return get(element, Max.class);
}
public static Min getConstraintsMin(final Field element) {
return get(element, Min.class);
} }
public static int getLimitSize(final Field element) { public static int getLimitSize(final Field element) {
@ -191,27 +236,15 @@ public class AnnotationTools {
} }
public static Size getConstraintsSize(final Field element) { public static Size getConstraintsSize(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Size.class); return get(element, Size.class);
if (annotation.length == 0) {
return null;
}
return (Size) annotation[0];
} }
public static String getConstraintsPattern(final Field element) { public static Pattern getConstraintsPattern(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Pattern.class); return get(element, Pattern.class);
if (annotation.length == 0) {
return null;
}
return ((Pattern) annotation[0]).regexp();
} }
public static boolean getConstraintsEmail(final Field element) { public static Email getConstraintsEmail(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Email.class); return get(element, Email.class);
if (annotation.length == 0) {
return false;
}
return true;
} }
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) { public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {

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,13 +5,43 @@ 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;
import org.kar.archidata.dataAccess.options.CheckFunctionInterface; /**
import org.kar.archidata.dataAccess.options.CheckFunctionVoid; * 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 {
Class<? extends CheckFunctionInterface> checker() default CheckFunctionVoid.class;
Class<?> targetEntity() default Void.class; Class<?> targetEntity() default Void.class;
} }

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

@ -0,0 +1,22 @@
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 = CheckForeignKeyValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckForeignKey {
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

@ -0,0 +1,52 @@
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 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 })
@Retention(RetentionPolicy.RUNTIME)
public @interface Checker {
Class<? extends CheckFunctionInterface> value();
}

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 = CollectionItemNotNullValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
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

@ -0,0 +1,21 @@
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 = CollectionItemUniqueValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
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

@ -0,0 +1,21 @@
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 = CollectionNotEmptyValidator.class)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
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

@ -1,4 +1,4 @@
package org.kar.archidata.annotation; package org.kar.archidata.annotation.method;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -1,4 +1,4 @@
package org.kar.archidata.annotation; package org.kar.archidata.annotation.method;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

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

@ -14,7 +14,7 @@ public class ExceptionCatcher implements ExceptionMapper<Exception> {
public Response toResponse(final Exception exception) { public Response toResponse(final Exception exception) {
LOGGER.warn("Catch exception (not managed...):"); LOGGER.warn("Catch exception (not managed...):");
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
exception.printStackTrace(); exception.printStackTrace();
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON) return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON)
.build(); .build();

View File

@ -15,7 +15,7 @@ public class FailExceptionCatcher implements ExceptionMapper<FailException> {
public Response toResponse(final FailException exception) { public Response toResponse(final FailException exception) {
LOGGER.warn("Catch FailException: {}", exception.getLocalizedMessage()); LOGGER.warn("Catch FailException: {}", exception.getLocalizedMessage());
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
if (exception.exception != null) { if (exception.exception != null) {
exception.exception.printStackTrace(); exception.exception.printStackTrace();
} }

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

@ -8,19 +8,35 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.ExceptionMapper;
/**
* This class catches InputException and maps it to a HTTP response.
*/
public class InputExceptionCatcher implements ExceptionMapper<InputException> { public class InputExceptionCatcher implements ExceptionMapper<InputException> {
private static final Logger LOGGER = LoggerFactory.getLogger(InputExceptionCatcher.class); private static final Logger LOGGER = LoggerFactory.getLogger(InputExceptionCatcher.class);
/**
* This method is called when an InputException is thrown.
* It logs the exception and builds a response with the error details.
*
* @param exception the InputException that was thrown
* @return a Response object containing the error details
*/
@Override @Override
public Response toResponse(final InputException exception) { public Response toResponse(final InputException exception) {
LOGGER.warn("Catch InputException:"); LOGGER.warn("Catch InputException:");
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={} ==> '{}'=>'{}'", ret.uuid, exception.missingVariable, LOGGER.error("Error OID={} ==> '{}'=>'{}'", ret.oid, exception.missingVariable,
exception.getLocalizedMessage()); exception.getLocalizedMessage());
// exception.printStackTrace(); // exception.printStackTrace();
return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build(); return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
} }
/**
* This method builds a RestErrorResponse object from the InputException.
*
* @param exception the InputException that was thrown
* @return a RestErrorResponse object containing the error details
*/
private RestErrorResponse build(final InputException exception) { private RestErrorResponse build(final InputException exception) {
return new RestErrorResponse(exception.status, "Error on input='" + exception.missingVariable + "'", return new RestErrorResponse(exception.status, "Error on input='" + exception.missingVariable + "'",
exception.getMessage()); exception.getMessage());

View File

@ -12,16 +12,29 @@ import jakarta.ws.rs.ext.ExceptionMapper;
public class JacksonExceptionCatcher implements ExceptionMapper<JacksonException> { public class JacksonExceptionCatcher implements ExceptionMapper<JacksonException> {
private static final Logger LOGGER = LoggerFactory.getLogger(JacksonExceptionCatcher.class); private static final Logger LOGGER = LoggerFactory.getLogger(JacksonExceptionCatcher.class);
/**
* This method is called when a JacksonException is thrown.
* It logs the exception, builds a response with the error details, and returns it.
*
* @param exception the JacksonException that was thrown
* @return a Response object containing the error details
*/
@Override @Override
public Response toResponse(final JacksonException exception) { public Response toResponse(final JacksonException exception) {
LOGGER.warn("Catch exception Input data parsing:"); LOGGER.warn("Catch exception Input data parsing:");
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
exception.printStackTrace(); exception.printStackTrace();
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON) return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON)
.build(); .build();
} }
/**
* Builds a RestErrorResponse object from the given exception.
*
* @param exception the Exception that was thrown
* @return a RestErrorResponse object containing the error details
*/
private RestErrorResponse build(final Exception exception) { private RestErrorResponse build(final Exception exception) {
return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch JSON Exception", return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch JSON Exception",
exception.getMessage()); exception.getMessage());

View File

@ -1,18 +1,18 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.List;
import org.kar.archidata.annotation.NoWriteSpecificMode; import org.bson.types.ObjectId;
import org.kar.archidata.tools.UuidUtils; 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 UUID uuid = UuidUtils.nextUUID(); public ObjectId oid = new ObjectId();
@NotNull @NotNull
@Column(length = 0) @Column(length = 0)
public String name; // Mandatory for TS generic error public String name; // Mandatory for TS generic error
@ -28,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;
@ -35,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) {
@ -43,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) {
@ -51,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

@ -15,7 +15,7 @@ public class SystemExceptionCatcher implements ExceptionMapper<SystemException>
public Response toResponse(final SystemException exception) { public Response toResponse(final SystemException exception) {
LOGGER.warn("Catch SystemException:"); LOGGER.warn("Catch SystemException:");
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
exception.printStackTrace(); exception.printStackTrace();
return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build(); return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
} }

View File

@ -14,7 +14,7 @@ public class WebApplicationExceptionCatcher implements ExceptionMapper<WebApplic
@Override @Override
public Response toResponse(final WebApplicationException exception) { public Response toResponse(final WebApplicationException exception) {
final RestErrorResponse ret = build(exception); final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
return Response.status(exception.getResponse().getStatusInfo().toEnum()).entity(ret) return Response.status(exception.getResponse().getStatusInfo().toEnum()).entity(ret)
.type(MediaType.APPLICATION_JSON).build(); .type(MediaType.APPLICATION_JSON).build();
} }

View File

@ -0,0 +1,898 @@
package org.kar.archidata.checker;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.checker.CheckForeignKey;
import org.kar.archidata.annotation.checker.Checker;
import org.kar.archidata.annotation.checker.CollectionItemNotNull;
import org.kar.archidata.annotation.checker.CollectionItemUnique;
import org.kar.archidata.annotation.checker.CollectionNotEmpty;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.CheckFunctionInterface;
import org.kar.archidata.dataAccess.options.CheckFunctionVoid;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.ConditionChecker;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.exception.InputException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
public class CheckJPA<T> implements CheckFunctionInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(CheckJPA.class);
private final Class<?> clazz;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckInterface<K> {
/** This function implementation is design to check if the updated class is valid of not for insertion
* @param ioDb Access on the Data-Base
* @param baseName Base of the name input that is displayed in exception generated.
* @param data The object that might be injected.
* @param modifiedValue List of fields that modification is requested.
* @param options Some query option that the checker can need to generate basic check.
* @throws Exception Exception is generate if the data are incorrect. */
void check(
final DBAccess ioDb,
final String baseName,
final K data,
List<String> modifiedValue,
final QueryOptions options) throws Exception;
}
protected Map<String, List<CheckInterface<T>>> checking = null;
protected void add(final String field, final CheckInterface<T> checkFunction) throws DataAccessException {
if (!AnnotationTools.hasFieldsName(this.clazz, field)) {
LOGGER.error("Try to add a JPA Filter on an inexistant Field: '{}' not in {}", field,
AnnotationTools.getAllFieldsNames(this.clazz));
throw new DataAccessException("Try to add a JPA Filter on an inexistant Field: '" + field + "' not in "
+ AnnotationTools.getAllFieldsNames(this.clazz));
}
List<CheckInterface<T>> actions = this.checking.get(field);
if (actions == null) {
actions = new ArrayList<>();
this.checking.put(field, actions);
}
actions.add(checkFunction);
}
public CheckJPA(final Class<T> clazz) {
this.clazz = clazz;
}
public void initialize() throws Exception {
if (this.checking != null) {
return;
}
try {
this.checking = new HashMap<>();
// create Table:
final List<String> primaryKeys = new ArrayList<>();
for (final Field field : this.clazz.getFields()) {
final String fieldName = field.getName(); // AnnotationTools.getFieldName(field);
if (AnnotationTools.isPrimaryKey(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
throw new InputException(baseName + fieldName,
"This is a '@Id' (primaryKey) ==> can not be change");
});
}
if (AnnotationTools.getConstraintsNotNull(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
if (field.get(data) == null) {
throw new InputException(baseName + fieldName, "Can not be null");
}
});
}
if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
throw new InputException(baseName + fieldName, "It is forbidden to change this field");
});
}
final Class<?> type = field.getType();
if (type == Long.class || type == long.class) {
final DecimalMax maxValueDecimal = AnnotationTools.getConstraintsDecimalMax(field);
if (maxValueDecimal != null) {
final long maxValue = Long.parseLong(maxValueDecimal.value());
final boolean inclusive = maxValueDecimal.inclusive();
final String exceptionComment = "Value too height max=" + maxValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (inclusive) {
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped >= maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final DecimalMin minValueDecimal = AnnotationTools.getConstraintsDecimalMin(field);
if (minValueDecimal != null) {
final long minValue = Long.parseLong(minValueDecimal.value());
final boolean inclusive = minValueDecimal.inclusive();
final String exceptionComment = "Value too low min=" + minValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (inclusive) {
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped <= minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Max maxValue = AnnotationTools.getConstraintsMax(field);
if (maxValue != null) {
final Long maxValueTmp = maxValue.value();
final String exceptionComment = "Value too height max=" + maxValueTmp + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (elemTyped > maxValueTmp) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Min minValue = AnnotationTools.getConstraintsMin(field);
if (minValue != null) {
final Long minValueTmp = minValue.value();
final String exceptionComment = "Value too low min=" + minValueTmp + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (elemTyped < minValueTmp) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
} else if (type == Integer.class || type == int.class) {
final DecimalMax maxValueDecimal = AnnotationTools.getConstraintsDecimalMax(field);
if (maxValueDecimal != null) {
final int maxValue = Integer.parseInt(maxValueDecimal.value());
final boolean inclusive = maxValueDecimal.inclusive();
final String exceptionComment = "Value too height max=" + maxValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (inclusive) {
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped >= maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final DecimalMin minValueDecimal = AnnotationTools.getConstraintsDecimalMin(field);
if (minValueDecimal != null) {
final int minValue = Integer.parseInt(minValueDecimal.value());
final boolean inclusive = minValueDecimal.inclusive();
final String exceptionComment = "Value too low min=" + minValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (inclusive) {
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped <= minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Max maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final int maxValue = (int) maxValueRoot.value();
final String exceptionComment = "Value too height max=" + maxValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Min minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final int minValue = (int) minValueRoot.value();
final String exceptionComment = "Value too low min=" + minValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
} else if (type == Boolean.class || type == boolean.class) {
} else if (type == Float.class || type == float.class) {
final DecimalMax maxValueDecimal = AnnotationTools.getConstraintsDecimalMax(field);
if (maxValueDecimal != null) {
final float maxValue = Float.parseFloat(maxValueDecimal.value());
final boolean inclusive = maxValueDecimal.inclusive();
final String exceptionComment = "Value too height max=" + maxValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (inclusive) {
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped >= maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final DecimalMin minValueDecimal = AnnotationTools.getConstraintsDecimalMin(field);
if (minValueDecimal != null) {
final float minValue = Float.parseFloat(minValueDecimal.value());
final boolean inclusive = minValueDecimal.inclusive();
final String exceptionComment = "Value too low min=" + minValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (inclusive) {
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped <= minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Max maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final float maxValue = maxValueRoot.value();
final String exceptionComment = "Value too height max=" + maxValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Min minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final float minValue = minValueRoot.value();
final String exceptionComment = "Value too low min=" + minValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
} else if (type == Double.class || type == double.class) {
final DecimalMax maxValueDecimal = AnnotationTools.getConstraintsDecimalMax(field);
if (maxValueDecimal != null) {
final double maxValue = Float.parseFloat(maxValueDecimal.value());
final boolean inclusive = maxValueDecimal.inclusive();
final String exceptionComment = "Value too height max=" + maxValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (inclusive) {
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
} else if (elemTyped >= maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final DecimalMin minValueDecimal = AnnotationTools.getConstraintsDecimalMin(field);
if (minValueDecimal != null) {
final double minValue = Float.parseFloat(minValueDecimal.value());
final boolean inclusive = minValueDecimal.inclusive();
final String exceptionComment = "Value too low min=" + minValue
+ (inclusive ? " (inclusive)" : " (exclusive)");
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (inclusive) {
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName,
"Value too Low min: " + minValue);
}
} else if (elemTyped <= minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Max maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final double maxValue = maxValueRoot.value();
final String exceptionComment = "Value too height max=" + maxValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
final Min minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final double minValue = minValueRoot.value();
final String exceptionComment = "Value too low min=" + minValue + " (inclusive)";
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, exceptionComment);
}
});
}
} else if (type == Date.class || type == Timestamp.class) {
} else if (type == LocalDate.class) {
} else if (type == LocalTime.class) {
} else if (type == String.class) {
final Size limitSize = AnnotationTools.getConstraintsSize(field);
if (limitSize != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (elemTyped.length() > limitSize.max()) {
throw new InputException(baseName + fieldName,
"Too long size (constraints) must be <= " + limitSize.max());
}
if (elemTyped.length() < limitSize.min()) {
throw new InputException(baseName + fieldName,
"Too small size (constraints) must be >= " + limitSize.min());
}
});
}
final jakarta.validation.constraints.Pattern patternString = AnnotationTools
.getConstraintsPattern(field);
if (patternString != null && patternString.regexp() != null) {
final Pattern pattern = Pattern.compile(patternString.regexp());
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (!pattern.matcher(elemTyped).find()) {
throw new InputException(baseName + fieldName,
"does not match the required pattern (constraints) must be '" + pattern
+ "'");
}
});
}
if (AnnotationTools.getConstraintsEmail(field) != null) {
final String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
final Pattern pattern = Pattern.compile(emailPattern);
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (!pattern.matcher(elemTyped).find()) {
throw new InputException(baseName + fieldName,
"does not match the required pattern[email] (constraints) must be '"
+ emailPattern + "'");
}
});
}
} else if (type.isEnum()) {
// nothing to do.
}
final Checker[] checkers = AnnotationTools.getConstraintsCheckers(field);
if (checkers != null) {
for (final Checker checker : checkers) {
if (checker == null || checker.value() == CheckFunctionVoid.class) {
continue;
}
final CheckFunctionInterface checkerInstance = checker.value().getDeclaredConstructor()
.newInstance();
if (Collection.class.isAssignableFrom(field.getType())) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
// get the field of the specific element
final Object tmpData = field.get(data);
// It is not the objective of this element to check if it is authorize to set NULL
if (tmpData == null) {
return;
}
final Collection<?> tmpCollection = (Collection<?>) tmpData;
final Object[] elements = tmpCollection.toArray();
for (int iii = 0; iii < elements.length; iii++) {
if (elements[iii] != null) {
checkerInstance.check(ioDb, baseName + fieldName + '[' + iii + "].",
elements[iii], null, options);
}
}
});
} else {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
// get the field of the specific element
final Object tmpData = field.get(data);
// It is not the objective of this element to check if it is authorize to set NULL
if (tmpData == null) {
return;
}
checkerInstance.check(ioDb, baseName + fieldName + '.', tmpData, null, options);
});
}
}
}
final CheckForeignKey foreighKey = AnnotationTools.get(field, CheckForeignKey.class);
if (foreighKey != null) {
if (Collection.class.isAssignableFrom(field.getType())) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
// get the field of the specific element
final Object tmpData = field.get(data);
// It is not the objective of this element to check if it is authorize to set NULL
if (tmpData == null) {
return;
}
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
final Condition conditionCheck = condCheckers.isEmpty() ? null
: condCheckers.get(0).toCondition();
final Collection<?> tmpCollection = (Collection<?>) tmpData;
final Object[] elements = tmpCollection.toArray();
for (int iii = 0; iii < elements.length; iii++) {
if (elements[iii] == null) {
continue;
}
final Long count = ioDb.count(foreighKey.target(), elements[iii],
conditionCheck);
if (count != 1) {
throw new InputException(baseName + fieldName + '[' + iii + ']',
"Foreign-key does not exist in the DB:" + elements[iii]);
}
}
});
} else {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object tmpData = field.get(data);
if (tmpData == null) {
return;
}
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
final Condition conditionCheck = condCheckers.isEmpty() ? null
: condCheckers.get(0).toCondition();
final Long count = ioDb.count(foreighKey.target(), tmpData, conditionCheck);
if (count != 1) {
throw new InputException(baseName + fieldName,
"Foreign-key does not exist in the DB:" + tmpData);
}
});
}
}
// check if we really want to keep it ...
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
final Condition conditionCheck = condCheckers.isEmpty() ? null
: condCheckers.get(0).toCondition();
final long count = ioDb.count(annotationManyToOne.targetEntity(), elem, conditionCheck);
if (count == 0) {
throw new InputException(baseName + fieldName,
"Foreign element does not exist in the DB:" + elem);
}
});
}
final CollectionItemUnique collectionUnique = AnnotationTools.getCollectionItemUnique(field);
if (collectionUnique != null) {
if (!Collection.class.isAssignableFrom(field.getType())) {
throw new DataAccessException(
"Request @CollectionItemUnique on a non collection field: '" + fieldName + "'");
}
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object tmpData = field.get(data);
if (tmpData == null) {
return;
}
final Collection<?> tmpCollection = (Collection<?>) tmpData;
final Set<Object> uniqueValues = new HashSet<>(tmpCollection);
if (uniqueValues.size() != tmpCollection.size()) {
throw new InputException(baseName + fieldName,
"Cannot insert multiple times the same elements");
}
});
}
final CollectionItemNotNull collectionNotNull = AnnotationTools.getCollectionItemNotNull(field);
if (collectionNotNull != null) {
if (!Collection.class.isAssignableFrom(field.getType())) {
throw new DataAccessException(
"Request @CollectionItemNotNull on a non collection field: '" + fieldName + "'");
}
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object tmpData = field.get(data);
if (tmpData == null) {
return;
}
final Collection<?> tmpCollection = (Collection<?>) tmpData;
final Object[] elements = tmpCollection.toArray();
for (int iii = 0; iii < elements.length; iii++) {
if (elements[iii] == null) {
throw new InputException(baseName + fieldName + '[' + iii + ']',
"Collection can not contain NULL item");
}
}
});
}
final CollectionNotEmpty collectionNotEmpty = AnnotationTools.getCollectionNotEmpty(field);
if (collectionNotEmpty != null) {
if (!Collection.class.isAssignableFrom(field.getType())) {
throw new DataAccessException(
"Request @collectionNotEmpty on a non collection field: '" + fieldName + "'");
}
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object tmpData = field.get(data);
if (tmpData == null) {
return;
}
final Collection<?> tmpCollection = (Collection<?>) tmpData;
if (tmpCollection.isEmpty()) {
throw new InputException(baseName + fieldName, "Collection can not be empty");
}
});
}
// keep this is last ==> take more time...
if (AnnotationTools.isUnique(field)) {
// Create the request ...
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
Object other = null;
if (condCheckers.isEmpty()) {
other = ioDb.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))));
} else {
other = ioDb.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))),
condCheckers.get(0).toCondition());
}
if (other != null) {
throw new InputException(baseName + fieldName,
"The field is already exist in the DB");
}
});
}
}
} catch (final Exception ex) {
this.checking = null;
throw ex;
}
}
public void check(final Object data) throws Exception {
check(null, "", data, null, null);
}
public void check(final String baseName, final Object data) throws Exception {
check(null, baseName, data, null, null);
}
public void check(final DBAccess ioDb, final String baseName, final Object data) throws Exception {
check(ioDb, baseName, data, null, null);
}
public void check(final DBAccess ioDb, final String baseName, final Object data, final List<String> modifiedValue)
throws Exception {
check(ioDb, baseName, data, modifiedValue, null);
}
@Override
public void check(
final DBAccess ioDb,
final String baseName,
final Object data,
List<String> modifiedValue,
final QueryOptions options) throws Exception {
if (this.checking == null) {
initialize();
}
if (modifiedValue == null) {
modifiedValue = AnnotationTools.getAllFieldsNames(this.clazz);
}
if (!(this.clazz.isAssignableFrom(data.getClass()))) {
throw new DataAccessException("Incompatatyble type of Object" + data.getClass().getCanonicalName());
}
@SuppressWarnings("unchecked")
final T dataCasted = (T) data;
for (final String filter : modifiedValue) {
final List<CheckInterface<T>> actions = this.checking.get(filter);
if (actions == null) {
continue;
}
for (final CheckInterface<T> action : actions) {
action.check(ioDb, baseName, dataCasted, modifiedValue, options);
}
}
checkTyped(dataCasted, modifiedValue, options);
}
public void checkTyped(final T data, final List<String> modifiedValue, final QueryOptions options)
throws Exception {
// nothing to do ...
}
}

View File

@ -61,20 +61,8 @@ import jakarta.ws.rs.InternalServerErrorException;
public class DBAccessSQL extends DBAccess { public class DBAccessSQL extends DBAccess {
final static Logger LOGGER = LoggerFactory.getLogger(DBAccessSQL.class); final static Logger LOGGER = LoggerFactory.getLogger(DBAccessSQL.class);
// by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...) // by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...)
final static List<DataAccessAddOn> addOn = new ArrayList<>(); final static List<DataAccessAddOn> addOn = List.of(new AddOnManyToMany(), new AddOnManyToOne(),
new AddOnOneToMany(), new AddOnDataJson());
{
addOn.add(new AddOnManyToMany());
addOn.add(new AddOnManyToOne());
addOn.add(new AddOnOneToMany());
addOn.add(new AddOnDataJson());
}
/** Add a new add-on on the current management.
* @param addOn instantiate object on the Add-on */
public static void addAddOn(final DataAccessAddOn addOn) {
DBAccessSQL.addOn.add(addOn);
}
private final DbIoSql db; private final DbIoSql db;
@ -170,7 +158,7 @@ public class DBAccessSQL extends DBAccess {
} else { } else {
// TODO : Maybe connect with a temporary not specified connection interface to a db ... // TODO : Maybe connect with a temporary not specified connection interface to a db ...
final PreparedStatement ps = this.db.getConnection() final PreparedStatement ps = this.db.getConnection()
.prepareStatement("SHOW TABLES IN `" + this.db.getCongig().getDbName() + "`"); .prepareStatement("SHOW TABLES IN `" + this.db.getConfig().getDbName() + "`");
final ResultSet rs = ps.executeQuery(); final ResultSet rs = ps.executeQuery();
// LOGGER.info("List all tables: equals? '{}'", name); // LOGGER.info("List all tables: equals? '{}'", name);
while (rs.next()) { while (rs.next()) {
@ -827,6 +815,9 @@ public class DBAccessSQL extends DBAccess {
@Override @Override
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public <T> T insert(final T data, final QueryOption... option) throws Exception { public <T> T insert(final T data, final QueryOption... option) throws Exception {
if (data == null) {
throw new DataAccessException("Try to check a null data ==> wrong API");
}
final Class<?> clazz = data.getClass(); final Class<?> clazz = data.getClass();
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);

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,19 +380,20 @@ 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());
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final List<Long> dataCasted = (List<Long>) data; final List<Object> dataCasted = (List<Object>) data;
if (dataCasted.size() == 0) { if (dataCasted.size() == 0) {
return; return;
} }
final List<LinkTableGeneric> insertElements = new ArrayList<>(); final List<LinkTableGeneric> insertElements = new ArrayList<>();
for (final Long remoteKey : dataCasted) { for (final Object remoteKey : dataCasted) {
if (remoteKey == null) { if (remoteKey == null) {
throw new DataAccessException("Try to insert remote key with null value"); throw new DataAccessException("Try to insert remote key with null value");
} }

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

@ -9,7 +9,7 @@ import org.kar.archidata.dataAccess.QueryOptions;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckFunctionInterface { public interface CheckFunctionInterface {
/** This function implementation is design to check if the updated class is valid of not for insertion /** This function implementation is design to check if the updated class is valid of not for insertion
* @param baseName NAme of the object to be precise with the use of what fail. * @param baseName Name of the object to be precise with the use of what fail.
* @param data The object that might be injected. * @param data The object that might be injected.
* @param modifiedValue List of fields that might be check. If null, then all column must be checked. * @param modifiedValue List of fields that might be check. If null, then all column must be checked.
* @throws Exception Exception is generate if the data are incorrect. */ * @throws Exception Exception is generate if the data are incorrect. */

View File

@ -1,575 +0,0 @@
package org.kar.archidata.dataAccess.options;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.exception.InputException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.Size;
public class CheckJPA<T> implements CheckFunctionInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(CheckJPA.class);
private final Class<?> clazz;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckInterface<K> {
/** This function implementation is design to check if the updated class is valid of not for insertion
* @param ioDb Access on the Data-Base
* @param baseName Base of the name input that is displayed in exception generated.
* @param data The object that might be injected.
* @param modifiedValue List of fields that modification is requested.
* @param options Some query option that the checker can need to generate basic check.
* @throws Exception Exception is generate if the data are incorrect. */
void check(
final DBAccess ioDb,
final String baseName,
final K data,
List<String> modifiedValue,
final QueryOptions options) throws Exception;
}
protected Map<String, List<CheckInterface<T>>> checking = null;
protected void add(final String field, final CheckInterface<T> checkFunction) throws DataAccessException {
if (!AnnotationTools.hasFieldsName(this.clazz, field)) {
LOGGER.error("Try to add a JPA Filter on an inexistant Field: '{}' not in {}", field,
AnnotationTools.getAllFieldsNames(this.clazz));
throw new DataAccessException("Try to add a JPA Filter on an inexistant Field: '" + field + "' not in "
+ AnnotationTools.getAllFieldsNames(this.clazz));
}
List<CheckInterface<T>> actions = this.checking.get(field);
if (actions == null) {
actions = new ArrayList<>();
this.checking.put(field, actions);
}
actions.add(checkFunction);
}
public CheckJPA(final Class<T> clazz) {
this.clazz = clazz;
}
public void initialize() throws Exception {
if (this.checking != null) {
return;
}
try {
this.checking = new HashMap<>();
// create Table:
final List<String> primaryKeys = new ArrayList<>();
for (final Field field : this.clazz.getFields()) {
final String fieldName = field.getName(); // AnnotationTools.getFieldName(field);
if (AnnotationTools.isPrimaryKey(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
throw new InputException(baseName + fieldName,
"This is a '@Id' (primaryKey) ==> can not be change");
});
}
if (AnnotationTools.getConstraintsNotNull(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
if (field.get(data) == null) {
throw new InputException(baseName + fieldName, "Can not be null");
}
});
}
if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
throw new InputException(baseName + fieldName, "It is forbidden to change this field");
});
}
final Class<?> type = field.getType();
if (type == Long.class || type == long.class) {
final Long maxValue = AnnotationTools.getConstraintsMax(field);
if (maxValue != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName,
"Value too height max: " + maxValue);
}
});
}
final Long minValue = AnnotationTools.getConstraintsMin(field);
if (minValue != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName,
"Value too Low min: " + minValue);
}
});
}
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
final Condition conditionCheck = condCheckers.isEmpty() ? null
: condCheckers.get(0).toCondition();
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem,
conditionCheck);
if (count == 0) {
throw new InputException(baseName + fieldName,
"Foreign element does not exist in the DB:" + elem);
}
});
}
} else if (type == Integer.class || type == int.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final int maxValue = maxValueRoot.intValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName,
"Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final int minValue = minValueRoot.intValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Integer elemTyped = (Integer) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName,
"Value too Low min: " + minValue);
}
});
}
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
if (count == 0) {
throw new InputException(baseName + fieldName,
"Foreign element does not exist in the DB:" + elem);
}
});
}
} else if (type == UUID.class) {
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
if (count == 0) {
throw new InputException(baseName + fieldName,
"Foreign element does not exist in the DB:" + elem);
}
});
}
} else if (type == Boolean.class || type == boolean.class) {
} else if (type == Float.class || type == float.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final float maxValue = maxValueRoot.floatValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName,
"Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final float minValue = minValueRoot.floatValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Float elemTyped = (Float) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName,
"Value too Low min: " + minValue);
}
});
}
} else if (type == Double.class || type == double.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
final double maxValue = maxValueRoot.doubleValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName,
"Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
final double minValue = minValueRoot.doubleValue();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Double elemTyped = (Double) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName,
"Value too Low min: " + minValue);
}
});
}
} else if (type == Date.class || type == Timestamp.class) {
} else if (type == LocalDate.class) {
} else if (type == LocalTime.class) {
} else if (type == String.class) {
final int maxSizeString = AnnotationTools.getLimitSize(field);
if (maxSizeString > 0) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (elemTyped.length() > maxSizeString) {
throw new InputException(baseName + fieldName,
"Too long size must be <= " + maxSizeString);
}
});
}
final Size limitSize = AnnotationTools.getConstraintsSize(field);
if (limitSize != null) {
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (elemTyped.length() > limitSize.max()) {
throw new InputException(baseName + fieldName,
"Too long size (constraints) must be <= " + limitSize.max());
}
if (elemTyped.length() < limitSize.min()) {
throw new InputException(baseName + fieldName,
"Too small size (constraints) must be >= " + limitSize.min());
}
});
}
final String patternString = AnnotationTools.getConstraintsPattern(field);
if (patternString != null) {
final Pattern pattern = Pattern.compile(patternString);
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (!pattern.matcher(elemTyped).find()) {
throw new InputException(baseName + fieldName,
"does not match the required pattern (constraints) must be '"
+ patternString + "'");
}
});
}
if (AnnotationTools.getConstraintsEmail(field)) {
final String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
final Pattern pattern = Pattern.compile(emailPattern);
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (!pattern.matcher(elemTyped).find()) {
throw new InputException(baseName + fieldName,
"does not match the required pattern[email] (constraints) must be '"
+ emailPattern + "'");
}
});
}
} else if (type == JsonValue.class) {
final DataJson jsonAnnotation = AnnotationTools.getDataJson(field);
if (jsonAnnotation != null && jsonAnnotation.checker() != CheckFunctionVoid.class) {
// Here if we have an error it crash at start and no new instance after creation...
final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor()
.newInstance();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
instance.checkAll(ioDb, baseName + fieldName + ".", field.get(data), options);
});
}
} else if (type.isEnum()) {
// nothing to do.
}
final DataJson dataJson = AnnotationTools.getDataJson(field);
if (dataJson != null && dataJson.checker() != null) {
final CheckFunctionInterface checkerInstance = dataJson.checker().getDeclaredConstructor()
.newInstance();
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
// get the field of the specific element
final Object tmpData = field.get(data);
checkerInstance.check(ioDb, baseName, tmpData, null, options);
});
}
// keep this is last ==> take more time...
if (AnnotationTools.isUnique(field)) {
// Create the request ...
add(fieldName,
(
final DBAccess ioDb,
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
Object other = null;
if (condCheckers.isEmpty()) {
other = DataAccess.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))));
} else {
other = DataAccess.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))),
condCheckers.get(0).toCondition());
}
if (other != null) {
throw new InputException(baseName + fieldName, "Name already exist in the DB");
}
});
}
}
} catch (final Exception ex) {
this.checking = null;
throw ex;
}
}
public void check(final Object data) throws Exception {
check(null, "", data, null, null);
}
public void check(final String baseName, final Object data) throws Exception {
check(null, baseName, data, null, null);
}
public void check(final DBAccess ioDb, final String baseName, final Object data) throws Exception {
check(ioDb, baseName, data, null, null);
}
public void check(final DBAccess ioDb, final String baseName, final Object data, final List<String> modifiedValue)
throws Exception {
check(ioDb, baseName, data, modifiedValue, null);
}
@Override
public void check(
final DBAccess ioDb,
final String baseName,
final Object data,
List<String> modifiedValue,
final QueryOptions options) throws Exception {
if (this.checking == null) {
initialize();
}
if (modifiedValue == null) {
modifiedValue = AnnotationTools.getAllFieldsNames(this.clazz);
}
if (!(this.clazz.isAssignableFrom(data.getClass()))) {
throw new DataAccessException("Incompatatyble type of Object" + data.getClass().getCanonicalName());
}
@SuppressWarnings("unchecked")
final T dataCasted = (T) data;
for (final String filter : modifiedValue) {
final List<CheckInterface<T>> actions = this.checking.get(filter);
if (actions == null) {
continue;
}
for (final CheckInterface<T> action : actions) {
action.check(ioDb, baseName, dataCasted, modifiedValue, options);
}
}
checkTyped(dataCasted, modifiedValue, options);
}
public void checkTyped(final T data, final List<String> modifiedValue, final QueryOptions options)
throws Exception {
// nothing to do ...
}
}

View File

@ -76,7 +76,7 @@ public abstract class DbIo implements Closeable {
return this.config.equals(config); return this.config.equals(config);
} }
public DbConfig getCongig() { public DbConfig getConfig() {
return this.config; return this.config;
} }
} }

View File

@ -0,0 +1,46 @@
package org.kar.archidata.exception;
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.catcher.RestInputError;
public class RESTErrorResponseException extends Exception {
private static final long serialVersionUID = 1L;
public ObjectId oid;
public String time;
public String name;
public String message;
public int status;
public String statusMessage;
public List<RestInputError> inputError;
public RESTErrorResponseException() {
this.oid = new ObjectId();
this.time = null;
this.name = null;
this.message = null;
this.status = 0;
this.statusMessage = null;
this.inputError = null;
}
public RESTErrorResponseException(final ObjectId oid, final String time, final String name, final String message,
final int status, final String statusMessage, final List<RestInputError> inputError) {
this.oid = oid;
this.time = time;
this.name = name;
this.message = message;
this.status = status;
this.statusMessage = statusMessage;
this.inputError = inputError;
}
@Override
public String toString() {
return "RESTErrorResponseExeption [oid=" + this.oid + ", time=" + this.time + ", name=" + this.name
+ ", message=" + this.message + ", status=" + this.status + ", statusMessage=" + this.statusMessage
+ "]";
}
}

View File

@ -1,39 +0,0 @@
package org.kar.archidata.exception;
import java.util.UUID;
public class RESTErrorResponseExeption extends Exception {
public UUID uuid;
public String time;
public String name;
public String message;
public int status;
public String statusMessage;
public RESTErrorResponseExeption() {
this.uuid = null;
this.time = null;
this.name = null;
this.message = null;
this.status = 0;
this.statusMessage = null;
}
public RESTErrorResponseExeption(final UUID uuid, final String time, final String name, final String message,
final int status, final String statusMessage) {
this.uuid = uuid;
this.time = time;
this.name = name;
this.message = message;
this.status = status;
this.statusMessage = statusMessage;
}
@Override
public String toString() {
return "RESTErrorResponseExeption [uuid=" + this.uuid + ", time=" + this.time + ", name=" + this.name
+ ", message=" + this.message + ", status=" + this.status + ", statusMessage=" + this.statusMessage
+ "]";
}
}

View File

@ -151,45 +151,6 @@ public class DotClassElement {
return ".optional()"; return ".optional()";
} }
public String maxSizeZod(final FieldProperty field) {
final StringBuilder builder = new StringBuilder();
final Class<?> clazz = field.model().getOriginClasses();
if (clazz == String.class) {
if (field.sizeMin() > 0) {
builder.append(".min(");
builder.append(field.sizeMin());
builder.append(")");
}
if (field.sizeMax() > 0) {
builder.append(".max(");
builder.append(field.sizeMax());
builder.append(")");
}
}
if (clazz == short.class || clazz == Short.class || clazz == int.class || clazz == Integer.class
|| clazz == long.class || clazz == Long.class || clazz == float.class || clazz == Float.class
|| clazz == double.class || clazz == Double.class) {
if (field.min() != null && field.min() > 0) {
builder.append(".min(");
builder.append(field.min());
builder.append(")");
}
if (field.max() != null && field.max() > 0) {
builder.append(".max(");
builder.append(field.max());
builder.append(")");
}
}
return builder.toString();
}
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,11 +7,11 @@ 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.ARCHIVE; import org.kar.archidata.annotation.apiGenerator.ApiInputOptional;
import org.kar.archidata.annotation.AsyncType; import org.kar.archidata.annotation.apiGenerator.ApiAsyncType;
import org.kar.archidata.annotation.FormDataOptional; import org.kar.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.kar.archidata.annotation.RESTORE; import org.kar.archidata.annotation.method.ARCHIVE;
import org.kar.archidata.annotation.TypeScriptProgress; import org.kar.archidata.annotation.method.RESTORE;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
@ -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,13 +11,22 @@ 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;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
public class ClassObjectModel extends ClassModel { public class ClassObjectModel extends ClassModel {
@ -25,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
@ -61,44 +74,45 @@ public class ClassObjectModel extends ClassModel {
ClassModel model, ClassModel model,
ClassModel linkClass, // link class when use remote ID (ex: list<UUID>) ClassModel linkClass, // link class when use remote ID (ex: list<UUID>)
String comment, String comment,
int sizeMin, // String SizeMin Size stringSize, // String Size
int sizeMax, // String SizeMax Min min, // number min value
Long min, // number min value Max max, // number max value
Long max, // number max value DecimalMin decimalMin,
Boolean readOnly, DecimalMax decimalMax,
Pattern pattern,
Email email,
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 int sizeMin, final int sizeMax, final Long min, final Long max, final String comment, final Size stringSize, final Min min, final Max max, final DecimalMin decimalMin,
final Boolean readOnly, final Boolean notNull, final Boolean columnNotNull, final Boolean nullable) { final DecimalMax decimalMax, final Pattern pattern, final Email email,
final ApiAccessLimitation accessLimitation, final Boolean notNull, final Boolean columnNotNull,
final Boolean nullable) {
this.name = name; this.name = name;
this.model = model; this.model = model;
this.linkClass = linkClass; this.linkClass = linkClass;
this.comment = comment; this.comment = comment;
this.sizeMin = sizeMin; this.stringSize = stringSize;
this.sizeMax = sizeMax; this.decimalMin = decimalMin;
this.decimalMax = decimalMax;
this.pattern = pattern;
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;
} }
private static int getStringMinSize(final Field field) throws DataAccessException {
final Size size = AnnotationTools.getConstraintsSize(field);
return size != null ? size.min() : 0;
}
private static int getStringMaxSize(final Field field) throws DataAccessException {
final Size size = AnnotationTools.getConstraintsSize(field);
final int colomnLimitSize = AnnotationTools.getLimitSize(field);
return size == null ? colomnLimitSize : colomnLimitSize < size.max() ? colomnLimitSize : size.max();
}
private static Class<?> getSubModelIfExist2(final Field field) { private static Class<?> getSubModelIfExist2(final Field field) {
final ManyToOne manyToOne = AnnotationTools.getManyToOne(field); final ManyToOne manyToOne = AnnotationTools.getManyToOne(field);
if (manyToOne != null) { if (manyToOne != null) {
@ -137,11 +151,14 @@ public class ClassObjectModel extends ClassModel {
ClassModel.getModel(field.getGenericType(), previous), // ClassModel.getModel(field.getGenericType(), previous), //
getSubModelIfExist(field, previous), // getSubModelIfExist(field, previous), //
AnnotationTools.getSchemaDescription(field), // AnnotationTools.getSchemaDescription(field), //
getStringMinSize(field), // AnnotationTools.getConstraintsSize(field), //
getStringMaxSize(field), //
AnnotationTools.getConstraintsMin(field), // AnnotationTools.getConstraintsMin(field), //
AnnotationTools.getConstraintsMax(field), // AnnotationTools.getConstraintsMax(field), //
AnnotationTools.getSchemaReadOnly(field), // AnnotationTools.getConstraintsDecimalMin(field), //
AnnotationTools.getConstraintsDecimalMax(field), //
AnnotationTools.getConstraintsPattern(field), //
AnnotationTools.getConstraintsEmail(field), //
AnnotationTools.get(field, ApiAccessLimitation.class), //
AnnotationTools.getConstraintsNotNull(field), // AnnotationTools.getConstraintsNotNull(field), //
AnnotationTools.getColumnNotNull(field), // AnnotationTools.getColumnNotNull(field), //
AnnotationTools.getNullable(field)); AnnotationTools.getNullable(field));
@ -187,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;
@ -254,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);
@ -259,28 +274,61 @@ public class TsClassElement {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
final Class<?> clazz = field.model().getOriginClasses(); final Class<?> clazz = field.model().getOriginClasses();
if (clazz == String.class) { if (clazz == String.class) {
if (field.sizeMin() > 0) { if (field.stringSize() != null) {
builder.append(".min("); if (field.stringSize().min() > 0) {
builder.append(field.sizeMin()); // A string size can not be lower at 0
builder.append(")"); builder.append(".min(");
builder.append(field.stringSize().min());
builder.append(")");
}
if (field.stringSize().max() != Integer.MAX_VALUE) {
builder.append(".max(");
builder.append(field.stringSize().max());
builder.append(")");
}
} }
if (field.sizeMax() > 0) { /*Must be tested before
builder.append(".max("); if (field.pattern() != null) {
builder.append(field.sizeMax()); builder.append(".regex((");
builder.append(field.pattern().regexp());
builder.append(")"); builder.append(")");
} }*/
/*Must be tested before
if (field.email() != null) {
builder.append(".regex((");
builder.append(field.email().regexp());
builder.append(")");
}*/
} }
if (clazz == short.class || clazz == Short.class || clazz == int.class || clazz == Integer.class if (clazz == short.class || clazz == Short.class || clazz == int.class || clazz == Integer.class
|| clazz == long.class || clazz == Long.class || clazz == float.class || clazz == Float.class || clazz == long.class || clazz == Long.class || clazz == float.class || clazz == Float.class
|| clazz == double.class || clazz == Double.class) { || clazz == double.class || clazz == Double.class) {
if (field.min() != null && field.min() > 0) { if (field.min() != null) {
builder.append(".min("); builder.append(".gte(");
builder.append(field.min()); builder.append(field.min().value());
builder.append(")"); builder.append(")");
} }
if (field.max() != null && field.max() > 0) { if (field.max() != null) {
builder.append(".max("); builder.append(".lte(");
builder.append(field.max()); builder.append(field.max().value());
builder.append(")");
}
if (field.decimalMax() != null) {
if (field.decimalMax().inclusive()) {
builder.append(".lte(");
} else {
builder.append(".lt(");
}
builder.append(field.decimalMax().value());
builder.append(")");
}
if (field.decimalMin() != null) {
if (field.decimalMin().inclusive()) {
builder.append(".gte(");
} else {
builder.append(".gt(");
}
builder.append(field.decimalMin().value());
builder.append(")"); builder.append(")");
} }
} }
@ -288,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 "";
@ -310,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) {
@ -356,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

@ -4,7 +4,6 @@ import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey // https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey/45814178#45814178 // https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey/45814178#45814178
@ -119,7 +118,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
final boolean isApplicationToken = apikeyHeader != null; final boolean isApplicationToken = apikeyHeader != null;
final boolean isJwtToken = isTokenBasedAuthentication(authorizationHeader); final boolean isJwtToken = isTokenBasedAuthentication(authorizationHeader);
if (!isApplicationToken && !isJwtToken) { if (!isApplicationToken && !isJwtToken) {
LOGGER.warn("REJECTED unauthorized: {}", requestContext.getUriInfo().getPath()); LOGGER.warn("REJECTED unauthorized: /{}", requestContext.getUriInfo().getPath());
abortWithUnauthorized(requestContext, "REJECTED unauthorized: " + requestContext.getUriInfo().getPath()); abortWithUnauthorized(requestContext, "REJECTED unauthorized: " + requestContext.getUriInfo().getPath());
return; return;
} }
@ -208,7 +207,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
// The WWW-Authenticate header is sent along with the response // The WWW-Authenticate header is sent along with the response
LOGGER.warn("abortWithUnauthorized:"); LOGGER.warn("abortWithUnauthorized:");
final RestErrorResponse ret = new RestErrorResponse(Response.Status.UNAUTHORIZED, "Unauthorized", message); final RestErrorResponse ret = new RestErrorResponse(Response.Status.UNAUTHORIZED, "Unauthorized", message);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
requestContext.abortWith(Response.status(ret.status) requestContext.abortWith(Response.status(ret.status)
.header(HttpHeaders.WWW_AUTHENTICATE, .header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " base64(HEADER).base64(CONTENT).base64(KEY)") AUTHENTICATION_SCHEME + " base64(HEADER).base64(CONTENT).base64(KEY)")
@ -217,7 +216,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
private void abortWithForbidden(final ContainerRequestContext requestContext, final String message) { private void abortWithForbidden(final ContainerRequestContext requestContext, final String message) {
final RestErrorResponse ret = new RestErrorResponse(Response.Status.FORBIDDEN, "FORBIDDEN", message); final RestErrorResponse ret = new RestErrorResponse(Response.Status.FORBIDDEN, "FORBIDDEN", message);
LOGGER.error("Error UUID={}", ret.uuid); LOGGER.error("Error OID={}", ret.oid);
requestContext.abortWith(Response.status(ret.status).header(HttpHeaders.WWW_AUTHENTICATE, message).entity(ret) requestContext.abortWith(Response.status(ret.status).header(HttpHeaders.WWW_AUTHENTICATE, message).entity(ret)
.type(MediaType.APPLICATION_JSON).build()); .type(MediaType.APPLICATION_JSON).build());
} }
@ -246,7 +245,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
final Object rowRight = ret.getClaim("right"); final Object rowRight = ret.getClaim("right");
if (rowRight != null) { if (rowRight != null) {
LOGGER.info("Detect right in Authentication Filter: {}", rowRight); LOGGER.info("Detect right in Authentication Filter: {}", rowRight);
user.right = (Map<String, Map<String, Object>>) ret.getClaim("right"); user.right = RightSafeCaster.safeCastAndTransform(ret.getClaim("right"));
/* /*
if (rights.containsKey(this.applicationName)) { if (rights.containsKey(this.applicationName)) {
user.right = rights.get(this.applicationName); user.right = rights.get(this.applicationName);

View File

@ -26,7 +26,7 @@ public class MySecurityContext implements SecurityContext {
return this.contextPrincipale; return this.contextPrincipale;
} }
public Object getRightOfRoleInGroup(final String group, final String role) { public PartRight getRightOfRoleInGroup(final String group, final String role) {
if (this.contextPrincipale.userByToken != null) { if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.getRight(group, role); return this.contextPrincipale.userByToken.getRight(group, role);
} }
@ -67,21 +67,14 @@ public class MySecurityContext implements SecurityContext {
return false; return false;
} }
// get associated Roles: // get associated Roles:
final Object rightPart = getRightOfRoleInGroup(group, role); final PartRight rightPart = getRightOfRoleInGroup(group, role);
LOGGER.info("detect : {}", rightPart); if (PartRight.READ_WRITE.equals(rightPart)) {
long dataRight = 0;
if (rightPart instanceof final Long rightPartCasted) {
dataRight = rightPartCasted;
} else if (rightPart instanceof final Integer rightPartCasted) {
dataRight = rightPartCasted;
}
if (dataRight == PartRight.READ_WRITE.getValue()) {
return true; return true;
} }
if (!needRead && needWrite && dataRight == PartRight.WRITE.getValue()) { if (!needRead && needWrite && PartRight.WRITE.equals(rightPart)) {
return true; return true;
} }
if (needRead && !needWrite && dataRight == PartRight.READ.getValue()) { if (needRead && !needWrite && PartRight.READ.equals(rightPart)) {
return true; return true;
} }
return false; return false;

View File

@ -27,4 +27,27 @@ public enum PartRight {
} }
throw new IllegalArgumentException("PartRight: Unknown value: " + value); throw new IllegalArgumentException("PartRight: Unknown value: " + value);
} }
public static PartRight fromValue(final long value) {
for (final PartRight element : values()) {
if (element.getValue() == value) {
return element;
}
}
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
}
public static PartRight fromString(final String value) {
if (value == null) {
throw new IllegalArgumentException("La chaîne ne peut pas être nulle");
}
return switch (value.toUpperCase()) {
case "NONE" -> NONE;
case "READ" -> READ;
case "WRITE" -> WRITE;
case "READ_WRITE" -> READ_WRITE;
default -> throw new IllegalArgumentException("Valeur inconnue pour PartRight : " + value);
};
}
} }

View File

@ -0,0 +1,61 @@
package org.kar.archidata.filter;
import java.util.Map;
public class RightSafeCaster {
@SuppressWarnings("unchecked")
public static Map<String, Map<String, PartRight>> safeCastAndTransform(final Object obj) {
if (!(obj instanceof Map)) {
throw new IllegalArgumentException("L'objet n'est pas un Map");
}
final Map<?, ?> outerMap = (Map<?, ?>) obj;
// Résultat final après vérification et transformation
final Map<String, Map<String, PartRight>> resultMap = new java.util.HashMap<>();
for (final Map.Entry<?, ?> outerEntry : outerMap.entrySet()) {
if (!(outerEntry.getKey() instanceof String)) {
throw new IllegalArgumentException("Une clé du Map externe n'est pas de type String");
}
if (!(outerEntry.getValue() instanceof Map)) {
throw new IllegalArgumentException("Une valeur du Map externe n'est pas un Map");
}
final String outerKey = (String) outerEntry.getKey();
final Map<?, ?> innerMap = (Map<?, ?>) outerEntry.getValue();
final Map<String, PartRight> transformedInnerMap = new java.util.HashMap<>();
for (final Map.Entry<?, ?> innerEntry : innerMap.entrySet()) {
if (!(innerEntry.getKey() instanceof String)) {
throw new IllegalArgumentException("Une clé du Map interne n'est pas de type String");
}
final String innerKey = (String) innerEntry.getKey();
final Object value = innerEntry.getValue();
PartRight partRight;
if (value instanceof PartRight) {
partRight = (PartRight) value;
} else if (value instanceof final Integer valueCasted) {
partRight = PartRight.fromValue(valueCasted);
} else if (value instanceof final Long valueCasted) {
partRight = PartRight.fromValue(valueCasted);
} else if (value instanceof final String valueCasted) {
partRight = PartRight.fromString(valueCasted);
} else {
throw new IllegalArgumentException("The Map Value is neither PartRight nor String nor Integer");
}
transformedInnerMap.put(innerKey, partRight);
}
resultMap.put(outerKey, transformedInnerMap);
}
return resultMap;
}
}

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,12 +1,14 @@
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;
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.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
@Table(name = "data") @Table(name = "data")
@DataIfNotExists @DataIfNotExists
@ -14,11 +16,16 @@ import jakarta.persistence.Table;
public class Data extends OIDGenericDataSoftDelete { 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)
@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)
@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

@ -48,8 +48,10 @@ public class User extends GenericDataSoftDelete {
@DefaultValue("'0'") @DefaultValue("'0'")
@Column(nullable = false) @Column(nullable = false)
public boolean blocked = false; @Nullable
public Boolean blocked = false;
@Column(length = 512) @Column(length = 512)
@Size(max = 512)
public String blockedReason; public String blockedReason;
@Schema(description = "List of Id of the specific covers") @Schema(description = "List of Id of the specific covers")

View File

@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.kar.archidata.filter.PartRight;
public class UserByToken { public class UserByToken {
public static final int TYPE_USER = -1; public static final int TYPE_USER = -1;
public static final int TYPE_APPLICATION = -2; public static final int TYPE_APPLICATION = -2;
@ -11,10 +13,11 @@ public class UserByToken {
public Integer type = null; public Integer type = null;
public Long id = null; public Long id = null;
public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID // For application, this is the id of the application, and of user token, this is the USERID
public Long parentId = null;
public String name = null; public String name = null;
// Right map // Right map
public Map<String, Map<String, Object>> right = new HashMap<>(); public Map<String, Map<String, PartRight>> right = new HashMap<>();
public Set<String> getGroups() { public Set<String> getGroups() {
return this.right.keySet(); return this.right.keySet();
@ -27,11 +30,11 @@ public class UserByToken {
return this.right.containsKey(group); return this.right.containsKey(group);
} }
public Object getRight(final String group, final String key) { public PartRight getRight(final String group, final String key) {
if (!this.right.containsKey(group)) { if (!this.right.containsKey(group)) {
return null; return null;
} }
final Map<String, Object> rightGroup = this.right.get(group); final Map<String, PartRight> rightGroup = this.right.get(group);
if (!rightGroup.containsKey(key)) { if (!rightGroup.containsKey(key)) {
return null; return 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";
@ -100,9 +109,6 @@ public class ConfigBaseVariable {
} }
public static String getDBName() { public static String getDBName() {
if (bdDatabase == null) {
return "unknown";
}
return bdDatabase; return bdDatabase;
} }

View File

@ -198,7 +198,7 @@ public class JWTWrapper {
.claim("login", userLogin).claim("application", application).issuer(isuer).issueTime(now) .claim("login", userLogin).claim("application", application).issuer(isuer).issueTime(now)
.expirationTime(expiration); // Do not ask why we need a "-" here ... this have no meaning .expirationTime(expiration); // Do not ask why we need a "-" here ... this have no meaning
// add right if needed: // add right if needed:
if (rights != null && !rights.isEmpty()) { if (rights != null) {
builder.claim("right", rights); builder.claim("right", rights);
} }
// Prepare JWT with claims set // Prepare JWT with claims set

View File

@ -11,12 +11,13 @@ import java.net.http.HttpResponse;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.kar.archidata.exception.RESTErrorResponseExeption; import org.kar.archidata.exception.RESTErrorResponseException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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,8 +37,8 @@ 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 RESTErrorResponseExeption, 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)
.uri(URI.create(this.baseUrl + urlOffset)); .uri(URI.create(this.baseUrl + urlOffset));
@ -48,71 +49,91 @@ public class RESTApi {
final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
try { try {
final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseExeption.class); RESTErrorResponseException.class);
throw out; throw out;
} catch (final MismatchedInputException ex) { } catch (final MismatchedInputException ex) {
throw new IOException( throw new IOException(
"Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); "Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
//return this.mapper.readValue(httpResponse.body(), new TypeReference<List<T>>() {});
return this.mapper.readValue(httpResponse.body(), return this.mapper.readValue(httpResponse.body(),
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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 RESTErrorResponseExeption, 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 {
@ -122,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 RESTErrorResponseExeption, 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)
@ -138,40 +162,48 @@ public class RESTApi {
} else { } else {
requestBuilding = requestBuilding.header("Content-Type", "application/json"); requestBuilding = requestBuilding.header("Content-Type", "application/json");
} }
LOGGER.trace("publish body: {}", body);
final HttpRequest request = requestBuilding.method(model, BodyPublishers.ofString(body)).build(); final HttpRequest request = requestBuilding.method(model, BodyPublishers.ofString(body)).build();
final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
LOGGER.trace("Receive Error: {}", httpResponse.body()); LOGGER.trace("Receive Error: {}", httpResponse.body());
try { try {
final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseExeption.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 RESTErrorResponseExeption, IOException, InterruptedException { final Map<String, Object> data) throws RESTErrorResponseException, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient(); final HttpClient client = HttpClient.newHttpClient();
String body = null; String body = null;
Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1) Builder requestBuilding = HttpRequest.newBuilder().version(Version.HTTP_1_1)
@ -189,8 +221,8 @@ public class RESTApi {
final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
try { try {
final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseExeption.class); RESTErrorResponseException.class);
throw out; throw out;
} catch (final MismatchedInputException ex) { } catch (final MismatchedInputException ex) {
throw new IOException( throw new IOException(
@ -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);
} }
@ -210,19 +242,19 @@ public class RESTApi {
* Call a DELETE on a REST API * Call a DELETE on a REST API
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
*/ */
public void delete(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public void delete(final String urlOffset) throws RESTErrorResponseException, IOException, InterruptedException {
delete(Void.class, urlOffset); delete(Void.class, urlOffset);
} }
/** /**
* 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 RESTErrorResponseExeption, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return simpleRequest("DELETE", clazz, urlOffset); return simpleRequest("DELETE", clazz, urlOffset);
} }
@ -230,19 +262,19 @@ public class RESTApi {
* Call an ARCHIVE on a REST API * Call an ARCHIVE on a REST API
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
*/ */
public void archive(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public void archive(final String urlOffset) throws RESTErrorResponseException, IOException, InterruptedException {
archive(Void.class, urlOffset); archive(Void.class, urlOffset);
} }
/** /**
* 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 RESTErrorResponseExeption, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return simpleRequest("ARCHIVE", clazz, urlOffset); return simpleRequest("ARCHIVE", clazz, urlOffset);
} }
@ -250,7 +282,7 @@ public class RESTApi {
* Call an RESTORE on a REST API * Call an RESTORE on a REST API
* @param urlOffset Offset to call the API * @param urlOffset Offset to call the API
*/ */
public void restore(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException { public void restore(final String urlOffset) throws RESTErrorResponseException, IOException, InterruptedException {
restore(Void.class, urlOffset); restore(Void.class, urlOffset);
} }
@ -262,20 +294,22 @@ public class RESTApi {
* @return The parsed object received. * @return The parsed object received.
*/ */
public <T> T restore(final Class<T> clazz, final String urlOffset) public <T> T restore(final Class<T> clazz, final String urlOffset)
throws RESTErrorResponseExeption, IOException, InterruptedException { throws RESTErrorResponseException, IOException, InterruptedException {
return simpleRequest("RESTORE", clazz, urlOffset); return simpleRequest("RESTORE", clazz, urlOffset);
} }
/** /**
* 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 RESTErrorResponseExeption, 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));
@ -286,8 +320,8 @@ public class RESTApi {
final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); final HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) { if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
try { try {
final RESTErrorResponseExeption out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseExeption.class); RESTErrorResponseException.class);
throw out; throw out;
} catch (final MismatchedInputException ex) { } catch (final MismatchedInputException ex) {
throw new IOException( throw new IOException(
@ -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,9 +4,9 @@ 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.ARCHIVE; import org.kar.archidata.annotation.apiGenerator.ApiAsyncType;
import org.kar.archidata.annotation.AsyncType; import org.kar.archidata.annotation.method.ARCHIVE;
import org.kar.archidata.annotation.RESTORE; import org.kar.archidata.annotation.method.RESTORE;
import org.kar.archidata.exception.NotFoundException; import org.kar.archidata.exception.NotFoundException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -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,9 +2,9 @@ package test.kar.archidata.apiExtern.resource;
import java.util.List; import java.util.List;
import org.kar.archidata.annotation.ARCHIVE; import org.kar.archidata.annotation.apiGenerator.ApiAsyncType;
import org.kar.archidata.annotation.AsyncType; import org.kar.archidata.annotation.method.ARCHIVE;
import org.kar.archidata.annotation.RESTORE; import org.kar.archidata.annotation.method.RESTORE;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -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,240 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerDecimalMax {
@Test
public void testDecimalMaxIncludeInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxIncludeInteger = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeInteger = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeInteger = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeInteger = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxIncludeIntegerObject = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeIntegerObject = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeIntegerObject = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeIntegerObject = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxIncludeLong = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeLong = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeLong = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeLong = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxIncludeLongObject = 75L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeLongObject = 74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeLongObject = 76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeLongObject = 100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
// can not be tested
//data.testDecimalMaxIncludeFloat = 75.56f;
//Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeFloat = 75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeFloat = 75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeFloat = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxIncludeFloatObject = 75.56f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeFloatObject = 75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeFloatObject = 75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeFloatObject = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
// can not be tested
//data.testDecimalMaxIncludeDouble = 75.56d;
//Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeDouble = 75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeDouble = 75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeDouble = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxIncludeDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
//data.testDecimalMaxIncludeDoubleObject = 75.56d;
//Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeDoubleObject = 75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxIncludeDoubleObject = 75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxIncludeDoubleObject = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
// exclude
@Test
public void testDecimalMaxExcludeInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeInteger = 75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeInteger = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeInteger = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeInteger = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeIntegerObject = 75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeIntegerObject = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeIntegerObject = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeIntegerObject = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeLong = 75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeLong = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeLong = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeLong = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeLongObject = 75L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeLongObject = 74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeLongObject = 76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeLongObject = 100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeFloat = 75.56f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeFloat = 75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeFloat = 75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeFloat = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeFloatObject = 75.56f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeFloatObject = 75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeFloatObject = 75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeFloatObject = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeDouble = 75.56d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeDouble = 75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeDouble = 75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeDouble = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMaxExcludeDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMaxExcludeDoubleObject = 75.56d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeDoubleObject = 75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMaxExcludeDoubleObject = 75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMaxExcludeDoubleObject = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,240 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerDecimalMin {
@Test
public void testDecimalMinIncludeInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeInteger = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeInteger = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeInteger = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeInteger = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeIntegerObject = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeIntegerObject = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeIntegerObject = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeIntegerObject = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeLong = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeLong = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeLong = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeLong = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeLongObject = -75L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeLongObject = -74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeLongObject = -76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeLongObject = -100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeFloat = -75.56f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeFloat = -75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeFloat = -75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeFloat = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinIncludeFloatObject = -75.56f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeFloatObject = -75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeFloatObject = -75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeFloatObject = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
// can not be tested
//data.testDecimalMinIncludeDouble = -75.56d;
//Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeDouble = -75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeDouble = -75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeDouble = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinIncludeDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
// can not be tested
//data.testDecimalMinIncludeDoubleObject = -75.56d;
//Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeDoubleObject = -75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinIncludeDoubleObject = -75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinIncludeDoubleObject = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
// exclude
@Test
public void testDecimalMinExcludeInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeInteger = -75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeInteger = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeInteger = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeInteger = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeIntegerObject = -75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeIntegerObject = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeIntegerObject = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeIntegerObject = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeLong = -75;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeLong = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeLong = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeLong = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeLongObject = -75L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeLongObject = -74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeLongObject = -76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeLongObject = -100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeFloat = -75.56f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeFloat = -75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeFloat = -75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeFloat = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeFloatObject = -75.56f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeFloatObject = -75.5599f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeFloatObject = -75.5601f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeFloatObject = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeDouble = -75.56d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeDouble = -75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeDouble = -75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeDouble = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testDecimalMinExcludeDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testDecimalMinExcludeDoubleObject = -75.56d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeDoubleObject = -75.5599d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testDecimalMinExcludeDoubleObject = -75.5601d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testDecimalMinExcludeDoubleObject = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,30 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerEMail {
@Test
public void testEMail() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testEMail = "s@s.ds";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testEMail = "yuio.sdf@sqdf.com";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testEMail = "s@s.s";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testEMail = "sq@qsd";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testEMail = "sqsdfsdf";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testEMail = "56465456";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,123 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerMax {
@Test
public void testMaxInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxInteger = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxInteger = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxInteger = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxInteger = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxIntegerObject = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxIntegerObject = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxIntegerObject = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxIntegerObject = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxLong = 75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxLong = 74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxLong = 76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxLong = 100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxLongObject = 75L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxLongObject = 74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxLongObject = 76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxLongObject = 100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxFloat = 75f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxFloat = 74.99f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxFloat = 75.01f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxFloat = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxFloatObject = 75f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxFloatObject = 74.99f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxFloatObject = 75.01f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxFloatObject = 100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxDouble = 75d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxDouble = 74.99d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxDouble = 75.01d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxDouble = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMaxDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMaxDoubleObject = 75d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxDoubleObject = 74.99d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMaxDoubleObject = 75.01d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMaxDoubleObject = 100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,123 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerMin {
@Test
public void testMinInteger() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinInteger = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinInteger = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinInteger = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinInteger = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinIntegerObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinIntegerObject = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinIntegerObject = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinIntegerObject = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinIntegerObject = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinLong() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinLong = -75;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinLong = -74;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinLong = -76;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinLong = -100;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinLongObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinLongObject = -75L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinLongObject = -74L;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinLongObject = -76L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinLongObject = -100L;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinFloat() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinFloat = -75f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinFloat = -74.99f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinFloat = -75.01f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinFloat = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinFloatObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinFloatObject = -75f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinFloatObject = -74.99f;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinFloatObject = -75.01f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinFloatObject = -100f;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinDouble() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinDouble = -75d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinDouble = -74.99d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinDouble = -75.01d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinDouble = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
@Test
public void testMinDoubleObject() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testMinDoubleObject = -75d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinDoubleObject = -74.99d;
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testMinDoubleObject = -75.01d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testMinDoubleObject = -100d;
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,26 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerPattern {
@Test
public void testPattern() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testPattern = "0";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testPattern = "1234567890";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testPattern = "q";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testPattern = "qsdf4653";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,26 @@
package test.kar.archidata.checker;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.exception.InputException;
import test.kar.archidata.checker.model.JpaBaseModel;
import test.kar.archidata.checker.model.JpaBaseModel.JpaBaseModelChecker;
public class TestJPACheckerSize {
@Test
public void testSize() throws Exception {
final JpaBaseModelChecker checker = new JpaBaseModelChecker();
final JpaBaseModel data = new JpaBaseModel();
data.testSize = "000";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testSize = "00000000";
Assertions.assertDoesNotThrow(() -> checker.check(data));
data.testSize = "00";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
data.testSize = "000000000";
Assertions.assertThrows(InputException.class, () -> checker.check(data));
}
}

View File

@ -0,0 +1,132 @@
package test.kar.archidata.checker.model;
import org.kar.archidata.checker.CheckJPA;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class JpaBaseModel {
// Simple checker declaration
public static class JpaBaseModelChecker extends CheckJPA<JpaBaseModel> {
public JpaBaseModelChecker() {
super(JpaBaseModel.class);
}
}
// Simple data to verify if the checker is active
@Size(min = 3, max = 8)
public String testSize;
@Pattern(regexp = "^[0-9]+$")
public String testPattern;
@Email
public String testEMail;
@Min(-75)
public int testMinInteger;
@Min(-75)
public Integer testMinIntegerObject;
@Min(-75)
public long testMinLong;
@Min(-75)
public Long testMinLongObject;
@Min(-75)
public float testMinFloat;
@Min(-75)
public Float testMinFloatObject;
@Min(-75)
public double testMinDouble;
@Min(-75)
public Double testMinDoubleObject;
@Max(75)
public int testMaxInteger;
@Max(75)
public Integer testMaxIntegerObject;
@Max(75)
public long testMaxLong;
@Max(75)
public Long testMaxLongObject;
@Max(75)
public float testMaxFloat;
@Max(75)
public Float testMaxFloatObject;
@Max(75)
public double testMaxDouble;
@Max(75)
public Double testMaxDoubleObject;
@DecimalMin("-75")
public int testDecimalMinIncludeInteger;
@DecimalMin("-75")
public Integer testDecimalMinIncludeIntegerObject;
@DecimalMin("-75")
public long testDecimalMinIncludeLong;
@DecimalMin("-75")
public Long testDecimalMinIncludeLongObject;
@DecimalMin("-75.56")
public float testDecimalMinIncludeFloat;
@DecimalMin("-75.56")
public Float testDecimalMinIncludeFloatObject;
@DecimalMin("-75.56")
public double testDecimalMinIncludeDouble;
@DecimalMin("-75.56")
public Double testDecimalMinIncludeDoubleObject;
@DecimalMax("75")
public int testDecimalMaxIncludeInteger;
@DecimalMax("75")
public Integer testDecimalMaxIncludeIntegerObject;
@DecimalMax("75")
public long testDecimalMaxIncludeLong;
@DecimalMax("75")
public Long testDecimalMaxIncludeLongObject;
@DecimalMax("75.56")
public float testDecimalMaxIncludeFloat;
@DecimalMax("75.56")
public Float testDecimalMaxIncludeFloatObject;
@DecimalMax("75.56")
public double testDecimalMaxIncludeDouble;
@DecimalMax("75.56")
public Double testDecimalMaxIncludeDoubleObject;
@DecimalMin(value = "-75", inclusive = false)
public int testDecimalMinExcludeInteger;
@DecimalMin(value = "-75", inclusive = false)
public Integer testDecimalMinExcludeIntegerObject;
@DecimalMin(value = "-75", inclusive = false)
public long testDecimalMinExcludeLong;
@DecimalMin(value = "-75", inclusive = false)
public Long testDecimalMinExcludeLongObject;
@DecimalMin(value = "-75.56", inclusive = false)
public float testDecimalMinExcludeFloat;
@DecimalMin(value = "-75.56", inclusive = false)
public Float testDecimalMinExcludeFloatObject;
@DecimalMin(value = "-75.56", inclusive = false)
public double testDecimalMinExcludeDouble;
@DecimalMin(value = "-75.56", inclusive = false)
public Double testDecimalMinExcludeDoubleObject;
@DecimalMax(value = "75", inclusive = false)
public int testDecimalMaxExcludeInteger;
@DecimalMax(value = "75", inclusive = false)
public Integer testDecimalMaxExcludeIntegerObject;
@DecimalMax(value = "75", inclusive = false)
public long testDecimalMaxExcludeLong;
@DecimalMax(value = "75", inclusive = false)
public Long testDecimalMaxExcludeLongObject;
@DecimalMax(value = "75.56", inclusive = false)
public float testDecimalMaxExcludeFloat;
@DecimalMax(value = "75.56", inclusive = false)
public Float testDecimalMaxExcludeFloatObject;
@DecimalMax(value = "75.56", inclusive = false)
public double testDecimalMaxExcludeDouble;
@DecimalMax(value = "75.56", inclusive = false)
public Double testDecimalMaxExcludeDoubleObject;
}

View File

@ -1,6 +1,6 @@
package test.kar.archidata.dataAccess.model; package test.kar.archidata.dataAccess.model;
import org.kar.archidata.dataAccess.options.CheckJPA; import org.kar.archidata.checker.CheckJPA;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;

View File

@ -1,7 +1,8 @@
package test.kar.archidata.dataAccess.model; package test.kar.archidata.dataAccess.model;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA; import org.kar.archidata.annotation.checker.Checker;
import org.kar.archidata.checker.CheckJPA;
public class DataWithSubJson { public class DataWithSubJson {
// Simple checker declaration // Simple checker declaration
@ -11,6 +12,7 @@ public class DataWithSubJson {
} }
} }
@DataJson(checker = DataInJson.DataInJsonChecker.class) @DataJson()
@Checker(DataInJson.DataInJsonChecker.class)
public DataInJson dataSerialized; public DataInJson dataSerialized;
} }

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

Some files were not shown because too many files have changed in this diff Show More