Compare commits

...

129 Commits

Author SHA1 Message Date
b9eb17e5c6 [RELEASE] Release v0.19.0 2024-12-12 09:16:17 +01:00
6d05b3444c [FEAT] update role management 2024-12-12 08:46:54 +01:00
dependabot[bot]
7b5e034ac2 [DEV-OPS] (dependabot) Bump actions/setup-java from 3 to 4
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 15:25:10 +01:00
dependabot[bot]
b4554a8bdb [DEV-OPS] (dependabot) Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 15:25:07 +01:00
dependabot[bot]
ae84d1c6c8 [DEV-OPS] (dependabot) Bump advanced-security/maven-dependency-submission-action
Bumps [advanced-security/maven-dependency-submission-action](https://github.com/advanced-security/maven-dependency-submission-action) from 2.0.0 to 4.1.1.
- [Release notes](https://github.com/advanced-security/maven-dependency-submission-action/releases)
- [Commits](571e99aab1...4f64ddab9d)

---
updated-dependencies:
- dependency-name: advanced-security/maven-dependency-submission-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-08 15:25:03 +01:00
239763cf48 [DEV] update dev tag version 2024-12-08 15:20:42 +01:00
754c422be0 [RELEASE] new version 0.18.0 2024-12-08 15:20:23 +01:00
091ac4babd [FEAT,API] (Token) upgrade tocken management to permit to control more conplex token 2024-12-07 16:52:44 +01:00
dccb6b80d5 [FIX] check null pointer in condition for SQL 2024-12-07 16:52:42 +01:00
5633604d13 [FIX] Json field parsing 2024-12-07 16:52:39 +01:00
96cb8a6e16 [FEAT] add control on User JPA 2024-12-07 16:52:36 +01:00
ebe88e4a8d [FEAT] (test-tool) set public some test API to be more permisive 2024-12-07 16:52:32 +01:00
c82ab9f27f [FEAT] add Email generic contraint check 2024-12-07 16:52:27 +01:00
f914462460 [FEAT] add workflow 2024-11-06 17:35:03 +01:00
9da5f589db [FEAT] auto add assignee 2024-11-06 16:01:36 +01:00
a0a35efeaf [VERSION] update dev tag version 2024-11-05 09:02:29 +01:00
abf1ddcf24 [RELEASE] Release v0.17.0 2024-11-05 09:02:25 +01:00
3bbbea87fa [FEAT] add List<String> modifiedValue in all JPA checker to permit to know wich field are mododified 2024-10-27 08:23:43 +01:00
25a163d4fa [VERSION] update dev tag version 2024-10-24 08:31:03 +02:00
c9b9d38efe [RELEASE] Release v0.16.0 2024-10-24 08:30:59 +02:00
cd3a6a1d8b [FIX] missing ARCHIVE & RESTORE in OPTION and correct the typescript request 2024-10-23 16:32:00 +02:00
Edouard DUPIN
5c1b7cd193 [FIX] add missing ARCHIVE and RESTORE in the rest-tool.ts 2024-10-23 10:59:12 +02:00
Edouard DUPIN
9ed09d4fed [FIX] name of the version 2024-10-23 10:50:17 +02:00
Edouard DUPIN
33665d47b8 [FIX] correction of the mis ordering in the many many request, force order by uuid 2024-10-23 10:47:08 +02:00
Edouard DUPIN
b907d2212a [FEAT] add ARCHIVE and RESTORE 2024-10-22 16:53:54 +02:00
a0f4680271 [DEV] update dev tag version 2024-09-20 19:47:44 +02:00
d9e118afaa [RELEASE] new version 0.15.0 2024-09-20 19:46:56 +02:00
9f43ebc782 [FIX] some fixes 2024-09-20 19:45:11 +02:00
8b831522dc [DEV] fix the get of the data 2024-09-20 19:02:22 +02:00
4f5d55bb01 [DEV] update dev tag version 2024-09-15 15:56:06 +02:00
9cbeee66c9 [RELEASE] new version 0.14.2 2024-09-15 15:55:00 +02:00
d25be53b77 [FIX] dot generation 2024-09-15 15:52:10 +02:00
d36f9c005a [DEV] update dev tag version 2024-09-15 15:46:37 +02:00
9a6d712d7a [RELEASE] new version 0.14.0 2024-09-15 15:45:57 +02:00
55275e4f26 [FEAT] update dependencies 2024-09-15 15:44:10 +02:00
f7de0e1db0 [FEAT] add optional for covers 2024-09-15 01:14:09 +02:00
032728f05d [FIX] correct Many to Many (normal way) anf first value field 2024-09-14 15:42:21 +02:00
bfe722f074 [FIX] corect the export if integer as an object 2024-09-14 10:20:43 +02:00
fddf41bea0 [FEAT] add some logs 2024-09-14 10:20:18 +02:00
3fa48fc839 [STYLE] fix style 2024-09-14 10:20:07 +02:00
de08bcfab5 [FEAT] someting is wrong 2024-09-12 00:24:37 +02:00
37f1362c3c [FEAT] start working on reverse @ManyToMany 2024-08-15 11:45:48 +02:00
e2ee68cc03 [FIX] export of LongWrite that does not exist 2024-08-15 11:45:17 +02:00
f05527ce01 [FIX] some annotation throws 2024-08-15 11:44:57 +02:00
38503fac8e [DEV] update development version 2024-06-25 15:48:53 +02:00
ab7259f726 [RELEASE] create version v0.13.0 2024-06-25 15:47:53 +02:00
cdb4581799 [FEAT] add link of List<UUID> that is decorate with @ManyToOne, or @ManyToMany or @OneToMany 2024-06-20 00:10:22 +02:00
7e81bfef28 [FEAT] add generation of dot files 2024-06-20 00:10:22 +02:00
84525fd7aa [FIX] cover tools 2024-06-13 08:27:33 +02:00
d4eb9c2a5f [FIX] correct the ID in uuid for uuid primary-key 2024-06-12 20:08:26 +02:00
15688f93e5 [FIX] version number 2024-06-12 00:55:51 +02:00
906216f237 [FIX] throw when uploading data 2024-06-12 00:54:42 +02:00
b479414bc2 [FIX] callbacks elements 2024-06-12 00:54:22 +02:00
2bc68321e3 [DEV] develoopment version 2024-06-12 00:53:29 +02:00
d77a5518ff [RELEASE] create version v0.12.1 2024-06-11 23:59:59 +02:00
5f9d13d315 [FEAT] remove in upload cover the filename 2024-06-11 23:48:43 +02:00
Yoann Fleury
0b57d8571c
fix: plural for callbacks 2024-06-11 12:41:31 +02:00
483c41914a [RELEASE] create version v0.12.0 2024-06-10 22:26:57 +02:00
a1f56050bf [FEAT] support the min and Max for number and string 2024-06-08 13:48:34 +02:00
a41e837f21 [FIX] import "Write" when no write availlable. 2024-06-08 11:55:51 +02:00
5496855698 [FEAT] add a remove warning 2024-06-08 11:43:29 +02:00
c9cb0d043a [API] remove @SQLWhere 2024-06-08 11:43:00 +02:00
09cfcfc578 [FEAT] generate a full Zod object for write mode.
- Add @NoWriteSpecificMode to permit to remove specific object write model
  - refactor Zod Write model
  - Add .nullable() in write Optional element

Residual bug element use in APi that is mark as no write
2024-06-08 11:42:38 +02:00
e4c56a4da5 [RELEASE] create version v0.11.0 2024-06-02 21:30:58 +02:00
22614aee98 [DEPENDENCY] update dependency 2024-06-02 21:30:36 +02:00
5bbcf63c42 [FIX] close some statements 2024-06-02 21:28:29 +02:00
1c02d333a3 [FIX] remove a warnning of spotbug 2024-06-02 21:28:12 +02:00
1c41ea4273 [FEAT] add a simple tool to generate random string (usefull in test) 2024-06-02 21:27:53 +02:00
274767d89b [FEAT] add some Logs when fail 2024-06-02 21:27:20 +02:00
4236dc38bd [FIX] correct many data-access element that does not close elements 2024-06-02 21:23:57 +02:00
7d4b246d4a [FEAT] add dependency of spotbug to have better code 2024-06-02 21:23:26 +02:00
f6f256c006 [FIX] Correct Exception types in the DataJson Add-on 2024-06-02 13:11:25 +02:00
d46b84c741 [FEAT] support DELETE return Data 2024-06-02 13:10:38 +02:00
3e6b9bf77c [FIX] Correct RESTApi.gets retreive data list in a correct way 2024-06-02 13:10:18 +02:00
007003394a [FEAT] add FailException adding exception context to permit better display of error in the future 2024-06-02 13:09:30 +02:00
54d4c420f9 [FEAT] DataAccess reduce log levels 2024-06-02 13:08:48 +02:00
0a307f3f6e [FIX] correct annotation tool throws 2024-06-02 13:08:21 +02:00
d968a2c48f [DOC] add spotbug documentation 2024-06-02 13:07:51 +02:00
c43e283b57 [FIX] socme checks 2024-06-01 19:49:36 +02:00
6af6f91166 [FIX] SpotBug finding some errors 2024-06-01 16:48:59 +02:00
c94f488747 [FIX] better compatibility with @OneToMany 2024-06-01 13:53:48 +02:00
c44b726cc1 [FEAT] add test for @OneToMany that fail 2024-06-01 13:10:28 +02:00
c412daa1ca [TEST] simplify test model 2024-06-01 13:09:53 +02:00
aa700f9dc5 [FEAT] add a throw in an impossible class call 2024-06-01 13:09:20 +02:00
a1791cf61d [TEST] LOG in TRACE only the library 2024-06-01 13:08:45 +02:00
9c9da21bdb [STYLE] error commit 2024-06-01 13:08:15 +02:00
dc6eeac008 [FIX] fix @ManyToOne (add UUID test) 2024-06-01 13:07:58 +02:00
f3a9ebf5e1 [STYLE] set all the typescript generation in a sub-folder 2024-06-01 13:01:32 +02:00
1fe3cc3523 [FIX] Correct the fail retur API to transmit the error from backend when compatible and wrat it when error occured 2024-05-31 21:50:50 +02:00
91849094cd [DEV] Correct all catcher to be named normalized 2024-05-31 19:51:24 +02:00
7b72b08fc0 [FEAT] permit a better support of @OneToMany 2024-05-29 20:16:48 +02:00
ebf1b4b76a [RELEASE] create version v0.10.3 2024-05-29 10:40:06 +02:00
f6aff28488 [FIX] ser cover nullable 2024-05-29 00:01:28 +02:00
6ebb9eb395 [FIX] remove accept type when void return 2024-05-29 00:01:04 +02:00
17664f1c2f [FIX] missing delete in links 2024-05-29 00:00:08 +02:00
1373760498 [FIX] correct the declaration of Object 2024-05-28 22:22:24 +02:00
8ffa392b2a [FEAT] create sub-directories 2024-05-28 22:21:05 +02:00
6104b68a02 [FIX] correct input order. 2024-05-28 22:07:41 +02:00
3c85af5af9 [FEAT] add a contructor to simplify a checker API 2024-05-28 18:04:54 +02:00
38d1fa9241 [DEV] simple rework the Check JPA 2024-05-28 18:04:31 +02:00
0e498c6a26 [RELEASE] create version v0.10.2 2024-05-27 17:00:52 +02:00
187ffba188 [FEAT] manage @nullable contrainst on list and Map too. 2024-05-27 17:00:31 +02:00
2a2b6b8f3b [RELEASE] create version v0.10.1 2024-05-24 12:33:16 +02:00
73770b03c3 [FIX] wrong type when set the @AsyncModel 2024-05-24 12:32:16 +02:00
5d8d4d9d2a [RELEASE] create version v0.10.0 2024-05-24 00:27:50 +02:00
745b0cb39b [FIX] correct API generation 2024-05-24 00:23:11 +02:00
9bad883866 [FEAT] add capability to support null queryOption to simplify API 2024-05-23 23:03:54 +02:00
f1c3b88a00 [DEV] add checker option to permit to inject some property in the checker 2024-05-23 00:08:56 +02:00
8c30336bdb [FIX] Correct Rest tool interface for stric mode of Typescript 2024-05-22 17:27:36 +02:00
6f36f98cd6 [FIX] correct the JPA display of minimum error 2024-05-22 16:29:49 +02:00
c843d7a64b [FIX] correct the size of ErrorRespose and remove needed of UUID 2024-05-22 16:29:21 +02:00
7059e687db [RELEASE] create version v0.9.0 2024-05-21 23:54:32 +02:00
bfb6498782 [FIX] remove dead test 2024-05-21 23:54:01 +02:00
94abf842e7 [DEV] update dependency 2024-05-21 23:54:01 +02:00
300ce8eab6 [FIX] correct test 2024-05-21 23:54:01 +02:00
c33a73a567 [STYLE] format code 2024-05-21 23:54:01 +02:00
0597ba0df3 [FEAT remove old API and add type in enum int model 2024-05-21 23:53:57 +02:00
4dabfcf7d7 [DEV] full rework of Typescript generator (operational) 2024-05-21 23:53:31 +02:00
4f7d2d711f [DEV] first step to generation of API 2024-05-21 23:53:31 +02:00
9ac3a95060 [FEAT] model generation is ready 2024-05-21 23:53:31 +02:00
dc022abd2d [FEAT] continue refacto of the model generator (base model generated, not the API, mission some control like optional, and limit size 2024-05-21 23:53:26 +02:00
7a36580cce [FEAT] continue refacto of the generation code 2024-05-21 23:52:53 +02:00
6031e6e557 [FEAT] continue integration 2024-05-21 23:52:53 +02:00
6d6fbf93ca [DEV] refacto the external rest api generator 2024-05-21 23:52:53 +02:00
e1f1335e89 [DEV] update version dev 2024-05-20 13:19:59 +02:00
e914daf71b [RELEASE] release v0.8.11 2024-05-20 13:19:31 +02:00
d859c7c278 [FIX] update any lines 2024-05-20 13:18:43 +02:00
420ea94b42 [DEV] update development tag 2024-05-20 09:59:21 +02:00
604c76103f [RELEASE] create version v0.8.10 2024-05-20 09:55:54 +02:00
117 changed files with 7388 additions and 2518 deletions

View File

@ -25,21 +25,24 @@
<attribute name="optional" value="true"/> <attribute name="optional" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21">
<attributes> <attributes>
<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"/>
</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"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/> <classpathentry kind="output" path="target/classes"/>
</classpath> </classpath>

16
.github/workflows/assign-pr-author.yml vendored Normal file
View File

@ -0,0 +1,16 @@
---
name: "Assign PR Author as Assignee"
on:
pull_request:
types:
- opened
jobs:
assign-pr-author-as-assignee:
runs-on: ubuntu-latest
steps:
- name: "Assign Author as Assignee"
uses: itsOliverBott/assign-pr-author-as-assignee@latest
with:
token: ${{ secrets.GITHUB_TOKEN }}

33
.github/workflows/check-title.yml vendored Normal file
View File

@ -0,0 +1,33 @@
---
name: "Check PR title"
on:
pull_request:
types:
- opened
- edited
- synchronize
- ready_for_review
- reopened
jobs:
check-title:
runs-on: ubuntu-latest
steps:
- name: "Check title"
uses: Slashgear/action-check-pr-title@v4.3.0
with:
regexp: "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-]+\\))? [A-Za-z0-9 ,.'\\-!]+$"
helpMessage: |
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}]
* [DEV-OPS] Update automatic build system, method to deliver application/packages, ...
* [DOC] Update or add some documentation.
* [FEAT] Develop a new feature
* [FIX] When fixing issue
* [FIX-CI] When the CI fail to build and we apply a correction to set it work again.
* [STYLE] Update of the style tools/checker, or add/remove rules.
Examples:
[FEAT] My beautiful feature
[API,FIX] Change API to fix typo
[FIX] (module) Correct part of ...

View File

@ -10,19 +10,17 @@ name: Java CI with Maven
on: on:
push: push:
branches: [ "develop" ] branches:
- develop
pull_request: pull_request:
branches: [ "develop" ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '21' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
@ -34,4 +32,4 @@ jobs:
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph - name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 uses: advanced-security/maven-dependency-submission-action@4f64ddab9d742a4806eeb588d238e4c311a8397d

View File

@ -1,4 +1,14 @@
#!/bin/bash #!/bin/bash
version_file="version.txt"
mvn versions:set -DnewVersion=$(cat version.txt) # update the Maven version number
mvn versions:set -DnewVersion=$(sed 's/dev/SNAPSHOT/g' $version_file)
if grep -q "DEV" "$version_file"; then
# update all versions release of dependency
mvn versions:use-latest-releases
# update our manage dependency as snapshoot
mvn versions:use-latest-versions -Dincludes=kangaroo-and-rabbit
else
# update our manage dependency as release (must be done before)
mvn versions:use-latest-releases -Dincludes=kangaroo-and-rabbit
fi

View File

@ -10,6 +10,11 @@
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand>
<name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name> <name>org.eclipse.m2e.core.maven2Builder</name>
<arguments> <arguments>
@ -19,6 +24,7 @@
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature> <nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>
</natures> </natures>
<filteredResources> <filteredResources>
<filter> <filter>

View File

@ -93,7 +93,12 @@ Enable the pre-commit checker
> **_Note_**: You can change the code in `.git/hooks/pre-commit` by replacing `formatter:verify` with `formatter:format` to auto format the code @ every commit > **_Note_**: You can change the code in `.git/hooks/pre-commit` by replacing `formatter:verify` with `formatter:format` to auto format the code @ every commit
Run Spot-bug:
------------
```bash
mvn spotbugs:check
```
Add Gitea in the dependency for the registry: Add Gitea in the dependency for the registry:
============================================= =============================================

102
pom.xml
View File

@ -3,8 +3,9 @@
<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.8.9</version> <version>0.19.0</version>
<properties> <properties>
<java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version> <maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
@ -53,6 +54,30 @@
<version>2.1.0-alpha1</version> <version>2.1.0-alpha1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Decode webP images -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.11.0</version>
</dependency>
<!-- Decode JPEG image -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.11.0</version>
</dependency>
<!-- Encode file in webp -->
<dependency>
<groupId>com.github.gotson</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.2.2</version>
</dependency>
<!-- Detect type of a file with mime type -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.0.0-BETA2</version>
</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>
<groupId>org.glassfish.jersey.media</groupId> <groupId>org.glassfish.jersey.media</groupId>
@ -95,11 +120,13 @@
<artifactId>istack-commons-runtime</artifactId> <artifactId>istack-commons-runtime</artifactId>
<version>${istack.version}</version> <version>${istack.version}</version>
</dependency> </dependency>
<!-- continu to be needed ??? -->
<dependency> <dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId> <groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId> <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Serialize and un-serialize request in JSON-->
<dependency> <dependency>
<groupId>org.glassfish.jersey.media</groupId> <groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId> <artifactId>jersey-media-json-jackson</artifactId>
@ -107,52 +134,59 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.17.1</version> <version>2.18.0-rc1</version>
</dependency> </dependency>
<!-- 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.17.1</version> <version>2.18.0-rc1</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.17.1</version> <version>2.18.0-rc1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0-M2</version> <version>6.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Interface for My-sql & sqlite DB --> <!-- Interface for My-sql & sqlite DB -->
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version> <version>9.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
<version>3.40.0.0</version> <version>3.46.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.39</version> <version>9.41.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.persistence</groupId> <groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId> <artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0-M2</version> <version>3.2.0</version>
</dependency> </dependency>
<!-- Swagger dependencies --> <!-- Swagger dependencies -->
<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.21</version> <version>2.2.23</version>
</dependency>
<!-- spotbug tooling -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.8.6</version>
<scope>compile</scope>
</dependency> </dependency>
<!-- <!--
************************************************************ ************************************************************
@ -162,27 +196,26 @@
<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-M1</version> <version>5.11.0</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-M1</version> <version>5.11.0</version>
<scope>test</scope> <scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven.plugins</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>3.3.1</version> <version>2.24.1</version>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<build> <build>
<sourceDirectory>src</sourceDirectory> <sourceDirectory>src</sourceDirectory>
<resources> <resources>
@ -268,7 +301,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.3.1</version>
<configuration> <configuration>
<configLocation>CheckStyle.xml</configLocation> <configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput> <consoleOutput>true</consoleOutput>
@ -280,7 +313,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.23.0</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding> <lineEnding>LF</lineEnding>
@ -304,6 +337,23 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.5.0</version>
<configuration>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
<!--<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.12.0</version>
</plugin>
</plugins>
-->
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
<!-- Generate Java-docs As Part Of Project Reports --> <!-- Generate Java-docs As Part Of Project Reports -->

View File

@ -0,0 +1,5 @@
<FindBugsFilter>
<Match>
<Bug category="SECURITY"/>
</Match>
</FindBugsFilter>

View File

@ -0,0 +1,2 @@
<FindBugsFilter>
</FindBugsFilter>

View File

@ -4,7 +4,7 @@ import org.kar.archidata.db.DBConfig;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
public class GlobalConfiguration { public class GlobalConfiguration {
public static DBConfig dbConfig = null; public static final DBConfig dbConfig;
static { static {
dbConfig = new DBConfig(ConfigBaseVariable.getDBType(), ConfigBaseVariable.getDBHost(), dbConfig = new DBConfig(ConfigBaseVariable.getDBType(), ConfigBaseVariable.getDBHost(),

View File

@ -0,0 +1,19 @@
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;
import jakarta.ws.rs.HttpMethod;
/**
* Indicates that the annotated method responds to HTTP ARCHIVE requests.
*
* @author Edouard DUPIN
* @see HttpMethod
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("ARCHIVE")
public @interface ARCHIVE {}

View File

@ -7,16 +7,21 @@ import java.util.List;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.OverrideTableName; import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
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;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
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;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -27,7 +32,7 @@ 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 String getTableName(final Class<?> clazz, final QueryOptions options) throws Exception { public static String getTableName(final Class<?> clazz, final QueryOptions options) throws DataAccessException {
if (options != null) { if (options != null) {
final List<OverrideTableName> data = options.get(OverrideTableName.class); final List<OverrideTableName> data = options.get(OverrideTableName.class);
if (data.size() == 1) { if (data.size() == 1) {
@ -37,14 +42,15 @@ public class AnnotationTools {
return AnnotationTools.getTableName(clazz); return AnnotationTools.getTableName(clazz);
} }
public static String getTableName(final Class<?> element) throws Exception { public static String getTableName(final Class<?> element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class);
if (annotation.length == 0) { if (annotation.length == 0) {
// when no annotation is detected, then the table name is the class name // when no annotation is detected, then the table name is the class name
return element.getSimpleName(); return element.getSimpleName();
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Table on " + element.getClass().getCanonicalName()); throw new DataAccessException(
"Must not have more than 1 element @Table on " + element.getClass().getCanonicalName());
} }
final String tmp = ((Table) annotation[0]).name(); final String tmp = ((Table) annotation[0]).name();
if (tmp == null) { if (tmp == null) {
@ -53,160 +59,191 @@ public class AnnotationTools {
return tmp; return tmp;
} }
public static boolean getSchemaReadOnly(final Field element) throws Exception { public static boolean getSchemaReadOnly(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
} }
return ((Schema) annotation[0]).readOnly(); return ((Schema) annotation[0]).readOnly();
} }
public static String getSchemaExample(final Class<?> element) throws Exception { public static String getSchemaExample(final Class<?> element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
} }
return ((Schema) annotation[0]).example(); return ((Schema) annotation[0]).example();
} }
public static String getSchemaDescription(final Class<?> element) throws Exception { 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) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
} }
return ((Schema) annotation[0]).description(); return ((Schema) annotation[0]).description();
} }
public static String getSchemaDescription(final Field element) throws Exception { public static String getSchemaDescription(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
} }
return ((Schema) annotation[0]).description(); return ((Schema) annotation[0]).description();
} }
public static String getComment(final Field element) throws Exception { public static String getComment(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataComment.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataComment.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return getSchemaDescription(element); return getSchemaDescription(element);
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @DataComment on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @DataComment on " + element.getClass().getCanonicalName());
} }
return ((DataComment) annotation[0]).value(); return ((DataComment) annotation[0]).value();
} }
public static String getDefault(final Field element) throws Exception { public static String getDefault(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DefaultValue.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(DefaultValue.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @DataDefault on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @DataDefault on " + element.getClass().getCanonicalName());
} }
return ((DefaultValue) annotation[0]).value(); return ((DefaultValue) annotation[0]).value();
} }
public static ManyToOne getManyToOne(final Field element) throws Exception { public static ManyToOne getManyToOne(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName());
}
return (ManyToOne) annotation[0]; return (ManyToOne) annotation[0];
} }
public static DataJson getDataJson(final Field element) throws Exception { public static ManyToMany getManyToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToMany.class);
if (annotation.length == 0) {
return null;
}
return (ManyToMany) annotation[0];
}
public static OneToMany getOneToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(OneToMany.class);
if (annotation.length == 0) {
return null;
}
return (OneToMany) annotation[0];
}
public static DataJson getDataJson(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName());
} }
return (DataJson) annotation[0]; return (DataJson) annotation[0];
} }
public static Long getConstraintsMax(final Field element) throws Exception { public static Long getConstraintsMax(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName()); throw new DataAccessException(
"Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
} }
return ((Max) annotation[0]).value(); return ((Max) annotation[0]).value();
} }
public static Long getConstraintsMin(final Field element) throws Exception { public static Long getConstraintsMin(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName()); throw new DataAccessException(
"Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
} }
return ((Min) annotation[0]).value(); return ((Min) annotation[0]).value();
} }
public static int getLimitSize(final Field element) throws Exception { public static int getLimitSize(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return 255; return 255;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
} }
final int length = ((Column) annotation[0]).length(); final int length = ((Column) annotation[0]).length();
return length <= 0 ? 0 : length; return length <= 0 ? 0 : length;
} }
public static Size getConstraintsSize(final Field element) throws Exception { public static Size getConstraintsSize(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Size.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Size.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName()); throw new DataAccessException(
"Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
} }
return (Size) annotation[0]; return (Size) annotation[0];
} }
public static String getConstraintsPattern(final Field element) throws Exception { public static String getConstraintsPattern(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Pattern.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Pattern.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Pattern on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Pattern on " + element.getClass().getCanonicalName());
} }
return ((Pattern) annotation[0]).regexp(); return ((Pattern) annotation[0]).regexp();
} }
public static boolean getConstraintsEmail(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(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) {
try { try {
final Annotation[] anns = field.getAnnotations(); final Annotation[] anns = field.getAnnotations();
@ -230,15 +267,11 @@ public class AnnotationTools {
return false; return false;
} }
public static String getFieldName(final Field element) throws Exception { public static String getFieldName(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return element.getName(); return element.getName();
} }
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
final String name = ((Column) annotation[0]).name(); final String name = ((Column) annotation[0]).name();
if (name.isBlank()) { if (name.isBlank()) {
return element.getName(); return element.getName();
@ -246,31 +279,39 @@ public class AnnotationTools {
return name; return name;
} }
public static boolean getColumnNotNull(final Field element) throws Exception { public static boolean getColumnNotNull(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
} }
return !((Column) annotation[0]).nullable(); return !((Column) annotation[0]).nullable();
} }
public static boolean getConstraintsNotNull(final Field element) throws Exception { public static boolean getNullable(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Nullable.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static boolean getConstraintsNotNull(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(NotNull.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(NotNull.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @NotNull on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @NotNull on " + element.getClass().getCanonicalName());
} }
return true; return true;
} }
public static Field getPrimaryKeyField(final Class<?> clazz) throws Exception { public static Field getPrimaryKeyField(final Class<?> clazz) throws DataAccessException {
for (final Field field : clazz.getFields()) { for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
@ -283,7 +324,7 @@ public class AnnotationTools {
return null; return null;
} }
public static boolean isPrimaryKey(final Field element) throws Exception { public static boolean isPrimaryKey(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Id.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Id.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
@ -291,51 +332,51 @@ public class AnnotationTools {
return true; return true;
} }
public static boolean isUnique(final Field element) throws Exception { public static boolean isUnique(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return false; return false;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
} }
return ((Column) annotation[0]).unique(); return ((Column) annotation[0]).unique();
} }
public static GenerationType getStrategy(final Field element) throws Exception { public static GenerationType getStrategy(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(GeneratedValue.class); final Annotation[] annotation = element.getDeclaredAnnotationsByType(GeneratedValue.class);
if (annotation.length == 0) { if (annotation.length == 0) {
return null; return null;
} }
if (annotation.length > 1) { if (annotation.length > 1) {
throw new Exception( throw new DataAccessException(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName()); "Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
} }
return ((GeneratedValue) annotation[0]).strategy(); return ((GeneratedValue) annotation[0]).strategy();
} }
public static boolean isDeletedField(final Field element) throws Exception { public static boolean isDeletedField(final Field element) throws DataAccessException {
return element.getDeclaredAnnotationsByType(DataDeleted.class).length != 0; return element.getDeclaredAnnotationsByType(DataDeleted.class).length != 0;
} }
public static boolean isCreatedAtField(final Field element) throws Exception { public static boolean isCreatedAtField(final Field element) throws DataAccessException {
return element.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0; return element.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
} }
public static boolean isUpdateAtField(final Field element) throws Exception { public static boolean isUpdateAtField(final Field element) throws DataAccessException {
return element.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0; return element.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
} }
public static boolean isdefaultNotRead(final Field element) throws Exception { public static boolean isdefaultNotRead(final Field element) throws DataAccessException {
return element.getDeclaredAnnotationsByType(DataNotRead.class).length != 0; return element.getDeclaredAnnotationsByType(DataNotRead.class).length != 0;
} }
public static boolean isIdField(final Field element) throws Exception { public static boolean isIdField(final Field element) throws DataAccessException {
return element.getDeclaredAnnotationsByType(Id.class).length != 0; return element.getDeclaredAnnotationsByType(Id.class).length != 0;
} }
public static String getDeletedFieldName(final Class<?> clazz) throws Exception { public static String getDeletedFieldName(final Class<?> clazz) throws DataAccessException {
try { try {
for (final Field elem : clazz.getFields()) { for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
@ -352,7 +393,7 @@ public class AnnotationTools {
return null; return null;
} }
public static String getUpdatedFieldName(final Class<?> clazz) throws Exception { public static String getUpdatedFieldName(final Class<?> clazz) throws DataAccessException {
try { try {
for (final Field elem : clazz.getFields()) { for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
@ -386,15 +427,16 @@ public class AnnotationTools {
return null; return null;
} }
public static List<String> getFieldsNames(final Class<?> clazz) throws Exception { public static List<String> getFieldsNames(final Class<?> clazz) throws DataAccessException {
return getFieldsNamesFilter(clazz, false); return getFieldsNamesFilter(clazz, false);
} }
public static List<String> getAllFieldsNames(final Class<?> clazz) throws Exception { public static List<String> getAllFieldsNames(final Class<?> clazz) throws DataAccessException {
return getFieldsNamesFilter(clazz, true); return getFieldsNamesFilter(clazz, true);
} }
private static List<String> getFieldsNamesFilter(final Class<?> clazz, final boolean full) throws Exception { private static List<String> getFieldsNamesFilter(final Class<?> clazz, final boolean full)
throws DataAccessException {
final List<String> out = new ArrayList<>(); final List<String> out = new ArrayList<>();
for (final Field field : clazz.getFields()) { for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
@ -409,12 +451,12 @@ public class AnnotationTools {
return out; return out;
} }
public static boolean isGenericField(final Field elem) throws Exception { public static boolean isGenericField(final Field elem) throws DataAccessException {
return AnnotationTools.isPrimaryKey(elem) || AnnotationTools.isCreatedAtField(elem) return AnnotationTools.isPrimaryKey(elem) || AnnotationTools.isCreatedAtField(elem)
|| AnnotationTools.isUpdateAtField(elem) || AnnotationTools.isDeletedField(elem); || AnnotationTools.isUpdateAtField(elem) || AnnotationTools.isDeletedField(elem);
} }
public static Field getFieldOfId(final Class<?> clazz) throws Exception { public static Field getFieldOfId(final Class<?> clazz) throws DataAccessException {
for (final Field field : clazz.getFields()) { for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
@ -427,7 +469,7 @@ public class AnnotationTools {
return null; return null;
} }
public static Field getFieldNamed(final Class<?> clazz, final String name) throws Exception { public static Field getFieldNamed(final Class<?> clazz, final String name) throws DataAccessException {
for (final Field field : clazz.getFields()) { for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {

View File

@ -5,8 +5,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target({ ElementType.TYPE }) @Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface SQLWhere { public @interface FormDataOptional {
String clause();
} }

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,19 @@
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;
import jakarta.ws.rs.HttpMethod;
/**
* Indicates that the annotated method responds to HTTP RESTORE requests.
*
* @author Edouard DUPIN
* @see HttpMethod
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("RESTORE")
public @interface RESTORE {}

View File

@ -5,6 +5,7 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -26,6 +27,7 @@ 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;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.filter.GenericContext; import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
@ -191,7 +193,8 @@ public class DataResource {
LOGGER.info("Move done"); LOGGER.info("Move done");
} }
public static String saveTemporaryFile(final InputStream uploadedInputStream, final long idData) { public static String saveTemporaryFile(final InputStream uploadedInputStream, final long idData)
throws FailException {
return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData)); return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData));
} }
@ -208,35 +211,35 @@ public class DataResource {
} }
// save uploaded file to a defined location on the server // save uploaded file to a defined location on the server
static String saveFile(final InputStream uploadedInputStream, final String serverLocation) { static String saveFile(final InputStream uploadedInputStream, final String serverLocation) throws FailException {
String out = ""; String out = "";
try { MessageDigest md = null;
OutputStream outpuStream = new FileOutputStream(new File(serverLocation)); try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
int read = 0; md = MessageDigest.getInstance("SHA-512");
final byte[] bytes = new byte[CHUNK_SIZE_IN];
final MessageDigest md = MessageDigest.getInstance("SHA-512");
outpuStream = new FileOutputStream(new File(serverLocation));
while ((read = uploadedInputStream.read(bytes)) != -1) {
// logger.info("write {}", read);
md.update(bytes, 0, read);
outpuStream.write(bytes, 0, read);
}
LOGGER.info("Flush input stream ... {}", serverLocation);
System.out.flush();
outpuStream.flush();
outpuStream.close();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
uploadedInputStream.close();
} catch (final IOException ex) { } catch (final IOException ex) {
LOGGER.info("Can not write in temporary file ... "); throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
ex.printStackTrace();
} catch (final NoSuchAlgorithmException ex) { } catch (final NoSuchAlgorithmException ex) {
LOGGER.info("Can not find sha512 algorithms"); throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not find sha512 algorithms", ex);
ex.printStackTrace(); }
if (md != null) {
try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
int read = 0;
final byte[] bytes = new byte[CHUNK_SIZE_IN];
while ((read = uploadedInputStream.read(bytes)) != -1) {
// logger.info("write {}", read);
md.update(bytes, 0, read);
outpuStream.write(bytes, 0, read);
}
LOGGER.info("Flush input stream ... {}", serverLocation);
outpuStream.flush();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
uploadedInputStream.close();
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
}
} }
return out; return out;
} }
@ -267,7 +270,7 @@ public class DataResource {
public void uploadFile( public void uploadFile(
@Context final SecurityContext sc, @Context final SecurityContext sc,
@FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) { @FormDataParam("file") final FormDataContentDisposition fileMetaData) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.info("==================================================="); LOGGER.info("===================================================");
LOGGER.info("== DATA uploadFile {}", (gc == null ? "null" : gc.userByToken)); LOGGER.info("== DATA uploadFile {}", (gc == null ? "null" : gc.userByToken));
@ -277,8 +280,9 @@ public class DataResource {
final String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++; final String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++;
try { try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator); createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (final IOException e) { } catch (final IOException ex) {
e.printStackTrace(); throw new FailException(Response.Status.INTERNAL_SERVER_ERROR,
"Impossible to create the folder in the server", ex);
} }
saveFile(fileInputStream, filePath); saveFile(fileInputStream, filePath);
} }
@ -293,16 +297,23 @@ public class DataResource {
@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, @HeaderParam("Range") final String range,
@PathParam("uuid") final UUID uuid) throws Exception { @PathParam("uuid") final UUID uuid) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("==================================================="); // logger.info("===================================================");
LOGGER.info("== DATA retrieveDataId ? id={} user={}", uuid, (gc == null ? "null" : gc.userByToken)); LOGGER.info("== DATA retrieveDataId ? id={} user={}", uuid, (gc == null ? "null" : gc.userByToken));
// logger.info("==================================================="); // logger.info("===================================================");
final Data value = getSmall(uuid); final Data value = getSmall(uuid);
if (value == null) { if (value == null) {
Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build(); LOGGER.warn("Request data that does not exist : {}", uuid);
return Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
}
try {
LOGGER.warn("Generate stream : {}", uuid);
return buildStream(getFileData(uuid), range,
value.mimeType == null ? "application/octet-stream" : value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
} }
return buildStream(getFileData(uuid), range, value.mimeType);
} }
@GET @GET
@ -316,11 +327,11 @@ public class DataResource {
@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, @HeaderParam("Range") final String range,
@PathParam("uuid") final UUID uuid) throws Exception { @PathParam("uuid") final UUID uuid) throws FailException {
// GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("==================================================="); LOGGER.info("===================================================");
// logger.info("== DATA retrieveDataThumbnailId ? {}", (gc==null?"null":gc.user)); LOGGER.info("== DATA retrieveDataThumbnailId ? {}", (gc == null ? "null" : gc.userByToken));
// logger.info("==================================================="); LOGGER.info("===================================================");
final Data value = getSmall(uuid); final Data value = getSmall(uuid);
if (value == null) { if (value == null) {
return Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build(); return Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
@ -335,32 +346,49 @@ public class DataResource {
// || value.mimeType.contentEquals("image/webp") // || value.mimeType.contentEquals("image/webp")
) { ) {
// reads input image // reads input image
final BufferedImage inputImage = ImageIO.read(inputFile); BufferedImage inputImage;
try {
inputImage = ImageIO.read(inputFile);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to READ the image", ex);
}
LOGGER.info("input size image: {}x{} type={}", inputImage.getWidth(), inputImage.getHeight(),
inputImage.getType());
final int scaledWidth = 250; final int scaledWidth = 250;
final int scaledHeight = (int) ((float) inputImage.getHeight() / (float) inputImage.getWidth() final int scaledHeight = (int) ((float) inputImage.getHeight() / (float) inputImage.getWidth()
* scaledWidth); * scaledWidth);
// creates output image // creates output image
final BufferedImage outputImage = new BufferedImage(scaledWidth, scaledHeight, inputImage.getType()); final BufferedImage outputImage = new BufferedImage(scaledWidth, scaledHeight, inputImage.getType());
// scales the input image to the output image // scales the input image to the output image
final Graphics2D g2d = outputImage.createGraphics(); final Graphics2D g2d = outputImage.createGraphics();
LOGGER.info("output size image: {}x{}", scaledWidth, scaledHeight);
g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null); g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose(); g2d.dispose();
for (final String data : ImageIO.getWriterFormatNames()) {
LOGGER.info("availlable format: {}", data);
}
// create the output stream: // create the output stream:
final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try { try {
// TODO: check how to remove buffer file !!! here, it is not needed at all... // TODO: check how to remove buffer file !!! here, it is not needed at all...
ImageIO.write(outputImage, "JPG", baos); //ImageIO.write(outputImage, "JPEG", baos);
//ImageIO.write(outputImage, "png", baos);
ImageIO.write(outputImage, "WebP", baos);
} catch (final IOException e) { } catch (final IOException e) {
e.printStackTrace(); e.printStackTrace();
return Response.status(500).entity("Internal Error: resize fail: " + e.getMessage()).type("text/plain") return Response.status(500).entity("Internal Error: resize fail: " + e.getMessage()).type("text/plain")
.build(); .build();
} }
final byte[] imageData = baos.toByteArray(); final byte[] imageData = baos.toByteArray();
LOGGER.info("output length {}", imageData.length);
// Response.ok(new ByteArrayInputStream(imageData)).build(); // Response.ok(new ByteArrayInputStream(imageData)).build();
final Response.ResponseBuilder out = Response.ok(imageData).header(HttpHeaders.CONTENT_LENGTH, final Response.ResponseBuilder out = Response.ok(imageData).header(HttpHeaders.CONTENT_LENGTH,
imageData.length); imageData.length);
out.type("image/jpeg"); //out.type("image/jpeg");
out.type("image/webp");
//out.type("image/png");
// TODO: move this in a decorator !!! // TODO: move this in a decorator !!!
final CacheControl cc = new CacheControl(); final CacheControl cc = new CacheControl();
cc.setMaxAge(3600); cc.setMaxAge(3600);
@ -368,7 +396,11 @@ public class DataResource {
out.cacheControl(cc); out.cacheControl(cc);
return out.build(); return out.build();
} }
return buildStream(filePathName, range, value.mimeType); try {
return buildStream(filePathName, range, value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
}
} }
@GET @GET
@ -389,17 +421,20 @@ public class DataResource {
// logger.info("==================================================="); // logger.info("===================================================");
final Data value = getSmall(uuid); final Data value = getSmall(uuid);
if (value == null) { if (value == null) {
Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build(); return Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
} }
return buildStream(getFileData(uuid), range, value.mimeType); return buildStream(getFileData(uuid), range,
value.mimeType == null ? "application/octet-stream" : value.mimeType);
} }
/** Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541 /** Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541
* *
* @param range range header * @param range range header
* @return Streaming output * @return Streaming output
* @throws FileNotFoundException
* @throws Exception IOException if an error occurs in streaming. */ * @throws Exception IOException if an error occurs in streaming. */
private Response buildStream(final String filename, final String range, final String mimeType) throws Exception { private Response buildStream(final String filename, final String range, final String mimeType)
throws FailException {
final File file = new File(filename); final File file = new File(filename);
// logger.info("request range : {}", range); // logger.info("request range : {}", range);
// range not requested : Firefox does not send range headers // range not requested : Firefox does not send range headers
@ -445,20 +480,26 @@ public class DataResource {
to = file.length() - 1; to = file.length() - 1;
} }
final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length()); final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length());
// logger.info("responseRange: {}", responseRange); // LOGGER.info("responseRange: {}", responseRange);
final RandomAccessFile raf = new RandomAccessFile(file, "r"); try {
raf.seek(from); final RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(from);
final long len = to - from + 1; final long len = to - from + 1;
final MediaStreamer streamer = new MediaStreamer(len, raf); final MediaStreamer streamer = new MediaStreamer(len, raf);
final Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT) final Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT)
.header("Accept-Ranges", "bytes").header("Content-Range", responseRange) .header("Accept-Ranges", "bytes").header("Content-Range", responseRange)
.header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth()) .header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth())
.header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified())); .header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
if (mimeType != null) { if (mimeType != null) {
out.type(mimeType); out.type(mimeType);
}
return out.build();
} catch (final FileNotFoundException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to find the required file.", ex);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to access to the required file.", ex);
} }
return out.build();
} }
public static void undelete(final Long id) throws Exception { public static void undelete(final Long id) throws Exception {

View File

@ -58,5 +58,4 @@ public class MediaStreamer implements StreamingOutput {
public long getLenth() { public long getLenth() {
return this.length; return this.length;
} }
} }

View File

@ -0,0 +1,43 @@
package org.kar.archidata.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
@Path("/proxy")
//@Produces(MediaType.APPLICATION_JSON)
public class ProxyResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyResource.class);
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getImageFromUrl(@QueryParam("url") final String url) {
if (url == null || url.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity("URL manquante").build();
}
final Client client = ClientBuilder.newClient();
try {
final WebTarget target = client.target(url);
final Response response = target.request().get();
if (response.getStatus() != 200) {
return Response.status(Status.BAD_GATEWAY).entity("Can not get the image : " + response.getStatus())
.build();
}
return Response.ok(response.readEntity(byte[].class)).header("Access-Control-Allow-Origin", "*")
.header("Content-Type", response.getHeaderString("Content-Type")).build();
} catch (final Exception e) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("SERVER internal error : " + e.getMessage())
.build();
}
}
}

View File

@ -21,8 +21,8 @@ public class ExceptionCatcher implements ExceptionMapper<Exception> {
} }
private RestErrorResponse build(final Exception exception) { private RestErrorResponse build(final Exception exception) {
return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch Unknown Exception", return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR,
exception.getMessage()); "Catch Unknown Exception: " + exception.getClass().getCanonicalName(), exception.getMessage());
} }
} }

View File

@ -13,11 +13,12 @@ public class FailExceptionCatcher implements ExceptionMapper<FailException> {
@Override @Override
public Response toResponse(final FailException exception) { public Response toResponse(final FailException exception) {
LOGGER.warn("Catch FailException:"); 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 UUID={}", ret.uuid);
// Not display backtrace ==> this may be a normal case ... if (exception.exception != null) {
// exception.printStackTrace(); exception.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

@ -0,0 +1,24 @@
package org.kar.archidata.catcher;
import org.glassfish.jersey.server.ResourceConfig;
public class GenericCatcher {
/**
* Add All the the generic catcher to standardize returns.
* @param rc Resource exception model.
*/
public static void addAll(final ResourceConfig rc) {
// Generic Json parsing error
rc.register(JacksonExceptionCatcher.class);
// Catch jakarta generic errors
rc.register(WebApplicationExceptionCatcher.class);
// Archidata exceptions
rc.register(InputExceptionCatcher.class);
rc.register(SystemExceptionCatcher.class);
rc.register(FailExceptionCatcher.class);
// generic Exception catcher
rc.register(ExceptionCatcher.class);
}
}

View File

@ -3,17 +3,17 @@ package org.kar.archidata.catcher;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JacksonException;
import jakarta.ws.rs.core.MediaType; 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;
public class JacksonCatcher implements ExceptionMapper<JsonProcessingException> { public class JacksonExceptionCatcher implements ExceptionMapper<JacksonException> {
private static final Logger LOGGER = LoggerFactory.getLogger(JacksonCatcher.class); private static final Logger LOGGER = LoggerFactory.getLogger(JacksonExceptionCatcher.class);
@Override @Override
public Response toResponse(final JsonProcessingException 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 UUID={}", ret.uuid);

View File

@ -3,16 +3,29 @@ package org.kar.archidata.catcher;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
import org.kar.archidata.annotation.NoWriteSpecificMode;
import org.kar.archidata.tools.UuidUtils; import org.kar.archidata.tools.UuidUtils;
import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@NoWriteSpecificMode
public class RestErrorResponse { public class RestErrorResponse {
public UUID uuid = UuidUtils.nextUUID(); public UUID uuid = UuidUtils.nextUUID();
@NotNull
@Column(length = 0)
public String name; // Mandatory for TS generic error public String name; // Mandatory for TS generic error
@NotNull
@Column(length = 0)
public String message; // Mandatory for TS generic error public String message; // Mandatory for TS generic error
@NotNull
@Column(length = 0)
public String time; public String time;
@NotNull
final public int status; final public int status;
@NotNull
@Column(length = 0)
final public String statusMessage; final public String statusMessage;
public RestErrorResponse(final Response.Status status, final String time, final String error, public RestErrorResponse(final Response.Status status, final String time, final String error,

View File

@ -3,23 +3,24 @@ package org.kar.archidata.catcher;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType; 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;
public class FailException404API implements ExceptionMapper<ClientErrorException> { public class WebApplicationExceptionCatcher implements ExceptionMapper<WebApplicationException> {
private static final Logger LOGGER = LoggerFactory.getLogger(FailException404API.class); private static final Logger LOGGER = LoggerFactory.getLogger(WebApplicationExceptionCatcher.class);
@Override @Override
public Response toResponse(final ClientErrorException 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 UUID={}", ret.uuid);
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();
} }
private RestErrorResponse build(final ClientErrorException exception) { private RestErrorResponse build(final WebApplicationException exception) {
exception.printStackTrace();
return new RestErrorResponse(exception.getResponse().getStatusInfo().toEnum(), "Catch system exception", return new RestErrorResponse(exception.getResponse().getStatusInfo().toEnum(), "Catch system exception",
exception.getMessage()); exception.getMessage());
} }

View File

@ -25,7 +25,7 @@ import org.kar.archidata.annotation.UpdateTimestamp;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.addOn.AddOnManyToOne; import org.kar.archidata.dataAccess.addOn.AddOnManyToOne;
import org.kar.archidata.dataAccess.addOn.AddOnSQLTableExternalForeinKeyAsList; import org.kar.archidata.dataAccess.addOn.AddOnOneToMany;
import org.kar.archidata.dataAccess.options.CheckFunction; import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.DBInterfaceOption; import org.kar.archidata.dataAccess.options.DBInterfaceOption;
@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.InternalServerErrorException;
@ -64,7 +65,7 @@ public class DataAccess {
static { static {
addOn.add(new AddOnManyToMany()); addOn.add(new AddOnManyToMany());
addOn.add(new AddOnManyToOne()); addOn.add(new AddOnManyToOne());
addOn.add(new AddOnSQLTableExternalForeinKeyAsList()); addOn.add(new AddOnOneToMany());
addOn.add(new AddOnDataJson()); addOn.add(new AddOnDataJson());
} }
@ -250,6 +251,14 @@ public class DataAccess {
return out; return out;
} }
public static UUID getListOfRawUUID(final ResultSet rs, final int iii) throws SQLException, DataAccessException {
final byte[] elem = rs.getBytes(iii);
if (rs.wasNull()) {
return null;
}
return UuidUtils.asUuid(elem);
}
protected static <T> void setValuedb( protected static <T> void setValuedb(
final Class<?> type, final Class<?> type,
final T data, final T data,
@ -789,6 +798,7 @@ public class DataAccess {
return out; return out;
} }
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public static <T> T insert(final T data, final QueryOption... option) throws Exception { public static <T> T insert(final T data, final QueryOption... option) throws Exception {
final Class<?> clazz = data.getClass(); final Class<?> clazz = data.getClass();
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
@ -796,7 +806,7 @@ public class DataAccess {
// External checker of data: // External checker of data:
final List<CheckFunction> checks = options.get(CheckFunction.class); final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) { for (final CheckFunction check : checks) {
check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz)); check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz), options);
} }
final DBEntry entry = DBInterfaceOption.getAutoEntry(options); final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
@ -895,7 +905,7 @@ public class DataAccess {
for (final OrderBy order : orders) { for (final OrderBy order : orders) {
order.generateQuery(query, tableName); order.generateQuery(query, tableName);
} }
LOGGER.warn("generate the query: '{}'", query.toString()); LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request: // prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS); Statement.RETURN_GENERATED_KEYS);
@ -961,13 +971,12 @@ public class DataAccess {
// uniqueSQLUUID = generatedKeys.getObject(1, UUID.class); // uniqueSQLUUID = generatedKeys.getObject(1, UUID.class);
/* final Object obj = generatedKeys.getObject(1); final BigInteger bigint = (BigInteger) generatedKeys.getObject(1); uniqueSQLUUID = UuidUtils.asUuid(bigint); final UUID /* final Object obj = generatedKeys.getObject(1); final BigInteger bigint = (BigInteger) generatedKeys.getObject(1); uniqueSQLUUID = UuidUtils.asUuid(bigint); final UUID
* generatedUUID = (UUID) generatedKeys.getObject(1); System.out.println("UUID généré: " + generatedUUID); */ * generatedUUID = (UUID) generatedKeys.getObject(1); System.out.println("UUID généré: " + generatedUUID); */
final Object obj = generatedKeys.getObject(1); //final Object obj = generatedKeys.getObject(1);
final byte[] tmpid = generatedKeys.getBytes(1); final byte[] tmpid = generatedKeys.getBytes(1);
uniqueSQLUUID = UuidUtils.asUuid(tmpid); uniqueSQLUUID = UuidUtils.asUuid(tmpid);
} else { } else {
uniqueSQLID = generatedKeys.getLong(1); uniqueSQLID = generatedKeys.getLong(1);
} }
} else { } else {
throw new SQLException("Creating node failed, no ID obtained (1)."); throw new SQLException("Creating node failed, no ID obtained (1).");
} }
@ -977,6 +986,7 @@ public class DataAccess {
throw new SQLException("Creating node failed, no ID obtained (2)."); throw new SQLException("Creating node failed, no ID obtained (2).");
} }
} }
ps.close();
if (primaryKeyField != null) { if (primaryKeyField != null) {
if (primaryKeyField.getType() == Long.class) { if (primaryKeyField.getType() == Long.class) {
primaryKeyField.set(data, uniqueSQLID); primaryKeyField.set(data, uniqueSQLID);
@ -1020,7 +1030,7 @@ public class DataAccess {
} }
public static <ID_TYPE> QueryCondition getTableIdCondition(final Class<?> clazz, final ID_TYPE idKey) public static <ID_TYPE> QueryCondition getTableIdCondition(final Class<?> clazz, final ID_TYPE idKey)
throws Exception { throws DataAccessException {
// Find the ID field type .... // Find the ID field type ....
final Field idField = AnnotationTools.getIdField(clazz); final Field idField = AnnotationTools.getIdField(clazz);
if (idField == null) { if (idField == null) {
@ -1107,10 +1117,14 @@ public class DataAccess {
return updateWhere(data, options); return updateWhere(data, options);
} }
public static <T> int updateWhere(final T data, final QueryOptions options) throws Exception { @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public static <T> int updateWhere(final T data, QueryOptions options) throws Exception {
final Class<?> clazz = data.getClass(); final Class<?> clazz = data.getClass();
if (options == null) {
options = new QueryOptions();
}
final Condition condition = conditionFusionOrEmpty(options, true); final Condition condition = conditionFusionOrEmpty(options, true);
final List<FilterValue> filters = options.get(FilterValue.class); final List<FilterValue> filters = options != null ? options.get(FilterValue.class) : new ArrayList<>();
if (filters.size() != 1) { if (filters.size() != 1) {
throw new DataAccessException("request a gets without/or with more 1 filter of values"); throw new DataAccessException("request a gets without/or with more 1 filter of values");
} }
@ -1119,7 +1133,7 @@ public class DataAccess {
if (options != null) { if (options != null) {
final List<CheckFunction> checks = options.get(CheckFunction.class); final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) { for (final CheckFunction check : checks) {
check.getChecker().check("", data, filter.getValues()); check.getChecker().check("", data, filter.getValues(), options);
} }
} }
final List<LazyGetter> asyncActions = new ArrayList<>(); final List<LazyGetter> asyncActions = new ArrayList<>();
@ -1185,39 +1199,41 @@ public class DataAccess {
if (!firstField) { if (!firstField) {
LOGGER.debug("generate update query: '{}'", query.toString()); LOGGER.debug("generate update query: '{}'", query.toString());
// prepare the request: // prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS); Statement.RETURN_GENERATED_KEYS)) {
final CountInOut iii = new CountInOut(1); final CountInOut iii = new CountInOut(1);
for (final Field field : clazz.getFields()) { for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it .. // static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) { if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue; continue;
} }
final String name = AnnotationTools.getFieldName(field); final String name = AnnotationTools.getFieldName(field);
if (!filter.getValues().contains(name)) { if (!filter.getValues().contains(name)) {
continue; continue;
} else if (AnnotationTools.isGenericField(field)) { } else if (AnnotationTools.isGenericField(field)) {
continue; continue;
} }
final DataAccessAddOn addOn = findAddOnforField(field); final DataAccessAddOn addOn = findAddOnforField(field);
if (addOn != null && !addOn.canInsert(field)) { if (addOn != null && !addOn.canInsert(field)) {
continue; continue;
} }
if (addOn == null) { if (addOn == null) {
final Class<?> type = field.getType(); final Class<?> type = field.getType();
if (!type.isPrimitive()) { if (!type.isPrimitive()) {
final Object tmp = field.get(data); final Object tmp = field.get(data);
if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) { if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) {
continue; continue;
} }
}
setValuedb(type, data, iii, field, ps);
} else {
addOn.insertData(ps, field, data, iii);
} }
setValuedb(type, data, iii, field, ps);
} else {
addOn.insertData(ps, field, data, iii);
} }
condition.injectQuery(ps, iii);
final int out = ps.executeUpdate();
return out;
} }
condition.injectQuery(ps, iii);
return ps.executeUpdate();
} }
} catch (final SQLException ex) { } catch (final SQLException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -1230,6 +1246,10 @@ public class DataAccess {
public static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii) public static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii)
throws Exception { throws Exception {
if (value == null) {
ps.setNull(iii.value, Types.INTEGER);
return;
}
if (value instanceof final UUID tmp) { if (value instanceof final UUID tmp) {
final byte[] dataByte = UuidUtils.asBytes(tmp); final byte[] dataByte = UuidUtils.asBytes(tmp);
ps.setBytes(iii.value, dataByte); ps.setBytes(iii.value, dataByte);
@ -1281,20 +1301,22 @@ public class DataAccess {
throws SQLException, IOException { throws SQLException, IOException {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
final DBEntry entry = DBInterfaceOption.getAutoEntry(options); final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
final Statement stmt = entry.connection.createStatement(); LOGGER.info("Query : '{}'", query);
return stmt.executeUpdate(query); try (final Statement stmt = entry.connection.createStatement()) {
return stmt.executeUpdate(query);
}
} }
public static boolean executeQuery(final String query, final QueryOption... option) public static boolean executeQuery(final String query, final QueryOption... option)
throws SQLException, IOException { throws SQLException, IOException {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
final DBEntry entry = DBInterfaceOption.getAutoEntry(options); final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
final Statement stmt = entry.connection.createStatement(); try (final Statement stmt = entry.connection.createStatement()) {
return stmt.execute(query); return stmt.execute(query);
}
} }
public static <T> T getWhere(final Class<T> clazz, final QueryOption... option) throws Exception { public static <T> T getWhere(final Class<T> clazz, final QueryOptions options) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Limit(1)); options.add(new Limit(1));
final List<T> values = getsWhere(clazz, options); final List<T> values = getsWhere(clazz, options);
if (values.size() == 0) { if (values.size() == 0) {
@ -1303,6 +1325,11 @@ public class DataAccess {
return values.get(0); return values.get(0);
} }
public static <T> T getWhere(final Class<T> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return getWhere(clazz, options);
}
public static void generateSelectField(// public static void generateSelectField(//
final StringBuilder querySelect, // final StringBuilder querySelect, //
final StringBuilder query, // final StringBuilder query, //
@ -1353,6 +1380,9 @@ public class DataAccess {
public static Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty) public static Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty)
throws DataAccessException { throws DataAccessException {
if (options == null) {
return new Condition();
}
final List<Condition> conditions = options.get(Condition.class); final List<Condition> conditions = options.get(Condition.class);
if (conditions.size() == 0) { if (conditions.size() == 0) {
if (throwIfEmpty) { if (throwIfEmpty) {
@ -1375,14 +1405,13 @@ public class DataAccess {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> List<T> getsWhere(final Class<T> clazz, final QueryOptions options) throws Exception { public static <T> List<T> getsWhere(final Class<T> clazz, final QueryOptions options)
throws DataAccessException, IOException {
final Condition condition = conditionFusionOrEmpty(options, false); final Condition condition = conditionFusionOrEmpty(options, false);
final List<LazyGetter> lazyCall = new ArrayList<>(); final List<LazyGetter> lazyCall = new ArrayList<>();
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
final List<T> outs = new ArrayList<>(); final List<T> outs = new ArrayList<>();
// real add in the BDD: try (final DBEntry entry = DBInterfaceOption.getAutoEntry(options)) {
try {
final CountInOut count = new CountInOut(); final CountInOut count = new CountInOut();
final StringBuilder querySelect = new StringBuilder(); final StringBuilder querySelect = new StringBuilder();
StringBuilder query = new StringBuilder(); StringBuilder query = new StringBuilder();
@ -1410,35 +1439,35 @@ public class DataAccess {
} else if (limits.size() > 1) { } else if (limits.size() > 1) {
throw new DataAccessException("Request with multiple 'limit'..."); throw new DataAccessException("Request with multiple 'limit'...");
} }
LOGGER.warn("generate the query: '{}'", query.toString()); LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request: // prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS); Statement.RETURN_GENERATED_KEYS)) {
final CountInOut iii = new CountInOut(1); final CountInOut iii = new CountInOut(1);
condition.injectQuery(ps, iii); condition.injectQuery(ps, iii);
if (limits.size() == 1) { if (limits.size() == 1) {
limits.get(0).injectQuery(ps, iii); limits.get(0).injectQuery(ps, iii);
} }
// execute the request // execute the request
final ResultSet rs = ps.executeQuery(); final ResultSet rs = ps.executeQuery();
while (rs.next()) { while (rs.next()) {
count.value = 1; count.value = 1;
final CountInOut countNotNull = new CountInOut(0); final CountInOut countNotNull = new CountInOut(0);
final Object data = createObjectFromSQLRequest(rs, clazz, count, countNotNull, options, lazyCall); final Object data = createObjectFromSQLRequest(rs, clazz, count, countNotNull, options, lazyCall);
final T out = (T) data; final T out = (T) data;
outs.add(out); outs.add(out);
} }
LOGGER.info("Async calls: {}", lazyCall.size()); LOGGER.info("Async calls: {}", lazyCall.size());
for (final LazyGetter elem : lazyCall) { for (final LazyGetter elem : lazyCall) {
elem.doRequest(); elem.doRequest();
}
} }
} catch (final SQLException ex) { } catch (final SQLException ex) {
ex.printStackTrace(); ex.printStackTrace();
throw ex; throw new DataAccessException("Catch a SQL Exception: " + ex.getMessage());
} catch (final Exception ex) { } catch (final Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} finally { throw new DataAccessException("Catch an Exception: " + ex.getMessage());
entry.close();
} }
return outs; return outs;
} }
@ -1484,12 +1513,19 @@ public class DataAccess {
return data; return data;
} }
public static <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id) throws Exception { public static <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
return DataAccess.countWhere(clazz, new Condition(getTableIdCondition(clazz, id))); throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id)));
return DataAccess.countWhere(clazz, options);
} }
public static long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception { public static long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
return countWhere(clazz, options);
}
public static long countWhere(final Class<?> clazz, final QueryOptions options) throws Exception {
final Condition condition = conditionFusionOrEmpty(options, false); final Condition condition = conditionFusionOrEmpty(options, false);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
DBEntry entry = DBInterfaceOption.getAutoEntry(options); DBEntry entry = DBInterfaceOption.getAutoEntry(options);
@ -1508,7 +1544,7 @@ public class DataAccess {
} else if (limits.size() > 1) { } else if (limits.size() > 1) {
throw new DataAccessException("Request with multiple 'limit'..."); throw new DataAccessException("Request with multiple 'limit'...");
} }
LOGGER.warn("generate the query: '{}'", query.toString()); LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request: // prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS); Statement.RETURN_GENERATED_KEYS);
@ -1549,10 +1585,6 @@ public class DataAccess {
return getsWhere(clazz, option); return getsWhere(clazz, option);
} }
public static <ID_TYPE> int delete(final Class<?> clazz, final ID_TYPE id) throws Exception {
return delete(clazz, id, null);
}
/** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before). /** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before).
* @param <ID_TYPE> Type of the reference @Id * @param <ID_TYPE> Type of the reference @Id
* @param clazz Data model that might remove element * @param clazz Data model that might remove element
@ -1649,18 +1681,18 @@ public class DataAccess {
} }
} }
public static <ID_TYPE> int unsetDelete(final Class<?> clazz, final ID_TYPE id) throws Exception { public static <ID_TYPE> int unsetDelete(final Class<?> clazz, final ID_TYPE id) throws DataAccessException {
return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id))); return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id)));
} }
public static <ID_TYPE> int unsetDelete(final Class<?> clazz, final ID_TYPE id, final QueryOption... option) public static <ID_TYPE> int unsetDelete(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws Exception { throws DataAccessException {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id))); options.add(new Condition(getTableIdCondition(clazz, id)));
return unsetDeleteWhere(clazz, options.getAllArray()); return unsetDeleteWhere(clazz, options.getAllArray());
} }
public static int unsetDeleteWhere(final Class<?> clazz, final QueryOption... option) throws Exception { public static int unsetDeleteWhere(final Class<?> clazz, final QueryOption... option) throws DataAccessException {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
final Condition condition = conditionFusionOrEmpty(options, true); final Condition condition = conditionFusionOrEmpty(options, true);
final String tableName = AnnotationTools.getTableName(clazz, options); final String tableName = AnnotationTools.getTableName(clazz, options);
@ -1668,7 +1700,12 @@ public class DataAccess {
if (deletedFieldName == null) { if (deletedFieldName == null) {
throw new DataAccessException("The class " + clazz.getCanonicalName() + " has no deleted field"); throw new DataAccessException("The class " + clazz.getCanonicalName() + " has no deleted field");
} }
final DBEntry entry = DBInterfaceOption.getAutoEntry(options); DBEntry entry;
try {
entry = DBInterfaceOption.getAutoEntry(options);
} catch (final IOException ex) {
throw new DataAccessException("Fail to connect the DB: " + ex.getMessage());
}
final StringBuilder query = new StringBuilder(); final StringBuilder query = new StringBuilder();
query.append("UPDATE `"); query.append("UPDATE `");
query.append(tableName); query.append(tableName);
@ -1678,13 +1715,14 @@ public class DataAccess {
// need to disable the deleted false because the model must be unselected to be updated. // need to disable the deleted false because the model must be unselected to be updated.
options.add(QueryOptions.ACCESS_DELETED_ITEMS); options.add(QueryOptions.ACCESS_DELETED_ITEMS);
condition.whereAppendQuery(query, tableName, options, deletedFieldName); condition.whereAppendQuery(query, tableName, options, deletedFieldName);
try { try (final PreparedStatement ps = entry.connection.prepareStatement(query.toString())) {
final PreparedStatement ps = entry.connection.prepareStatement(query.toString());
final CountInOut iii = new CountInOut(1); final CountInOut iii = new CountInOut(1);
condition.injectQuery(ps, iii); condition.injectQuery(ps, iii);
return ps.executeUpdate(); return ps.executeUpdate();
} finally { } catch (final SQLException ex) {
entry.close(); throw new DataAccessException("Catch SQL error:" + ex.getMessage());
} catch (final Exception ex) {
throw new DataAccessException("Fail to excute the SQL query:" + ex.getMessage());
} }
} }
@ -1801,7 +1839,7 @@ public class DataAccess {
} else if (limits.size() > 1) { } else if (limits.size() > 1) {
throw new DataAccessException("Request with multiple 'limit'..."); throw new DataAccessException("Request with multiple 'limit'...");
} }
LOGGER.warn("generate the query: '{}'", query.toString()); LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request: // prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS); Statement.RETURN_GENERATED_KEYS);
@ -1867,4 +1905,4 @@ public class DataAccess {
} }
return outs; return outs;
} }
} }

View File

@ -27,7 +27,7 @@ import jakarta.persistence.GenerationType;
public class DataFactory { public class DataFactory {
static final Logger LOGGER = LoggerFactory.getLogger(DataFactory.class); static final Logger LOGGER = LoggerFactory.getLogger(DataFactory.class);
public static String convertTypeInSQL(final Class<?> type, final String fieldName) throws Exception { public static String convertTypeInSQL(final Class<?> type, final String fieldName) throws DataAccessException {
if (!"sqlite".equals(ConfigBaseVariable.getDBType())) { if (!"sqlite".equals(ConfigBaseVariable.getDBType())) {
if (type == UUID.class) { if (type == UUID.class) {
return "binary(16)"; return "binary(16)";
@ -165,7 +165,7 @@ public class DataFactory {
final String comment = AnnotationTools.getComment(elem); final String comment = AnnotationTools.getComment(elem);
final String defaultValue = AnnotationTools.getDefault(elem); final String defaultValue = AnnotationTools.getDefault(elem);
if (fieldId == 0) { if (mainTableBuilder.toString().length() == 0) {
mainTableBuilder.append("\n\t\t`"); mainTableBuilder.append("\n\t\t`");
} else { } else {
mainTableBuilder.append(",\n\t\t`"); mainTableBuilder.append(",\n\t\t`");
@ -404,6 +404,7 @@ public class DataFactory {
} }
final boolean dataInThisObject = tmpOut.toString().length() > 0; final boolean dataInThisObject = tmpOut.toString().length() > 0;
if (dataInThisObject) { if (dataInThisObject) {
LOGGER.info("Previous Object : '{}'", reverseOut.toString());
final boolean dataInPreviousObject = reverseOut.toString().length() > 0; final boolean dataInPreviousObject = reverseOut.toString().length() > 0;
if (dataInPreviousObject) { if (dataInPreviousObject) {
tmpOut.append(", "); tmpOut.append(", ");

View File

@ -1,708 +0,0 @@
package org.kar.archidata.dataAccess;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.dataAccess.DataFactoryZod.ClassElement;
import org.kar.archidata.dataAccess.DataFactoryZod.GeneratedTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
public class DataFactoryTsApi {
static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryTsApi.class);
record APIModel(
String data,
String className) {}
/** Request the generation of the TypeScript file for the "Zod" export model
* @param classs List of class used in the model
* @throws Exception */
public static List<String> createApi(
final List<Class<?>> classs,
final GeneratedTypes previous,
final String pathPackage) throws Exception {
final List<String> apis = new ArrayList<>();
final String globalheader = """
/**
* API of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
ModelResponseHttp,
RESTCallbacks,
RESTConfig,
RESTRequestJson,
RESTRequestJsonArray,
RESTRequestVoid
} from "./rest-tools"
import {""";
for (final Class<?> clazz : classs) {
final Set<Class<?>> includeModel = new HashSet<>();
final Set<Class<?>> includeCheckerModel = new HashSet<>();
final APIModel api = createSingleApi(clazz, includeModel, includeCheckerModel, previous);
final StringBuilder generatedData = new StringBuilder();
generatedData.append(globalheader);
final List<String> includedElements = new ArrayList<>();
for (final Class<?> elem : includeModel) {
if (elem == null) {
continue;
}
final ClassElement classElement = DataFactoryZod.createTable(elem, previous);
if (classElement.nativeType) {
continue;
}
includedElements.add(classElement.tsTypeName);
}
Collections.sort(includedElements);
for (final String elem : includedElements) {
generatedData.append("\n ");
generatedData.append(elem);
generatedData.append(",");
}
for (final Class<?> elem : includeCheckerModel) {
if (elem == null) {
continue;
}
final ClassElement classElement = DataFactoryZod.createTable(elem, previous);
if (classElement.nativeType) {
continue;
}
generatedData.append("\n ");
generatedData.append(classElement.tsCheckType);
generatedData.append(",");
}
generatedData.append("\n} from \"./model\"\n");
generatedData.append(api.data());
String fileName = api.className();
fileName = fileName.replaceAll("([A-Z])", "-$1").toLowerCase();
fileName = fileName.replaceAll("^\\-*", "");
apis.add(fileName);
final FileWriter myWriter = new FileWriter(pathPackage + File.separator + fileName + ".ts");
myWriter.write(generatedData.toString());
myWriter.close();
}
return apis;
}
public static String apiAnnotationGetPath(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class);
if (annotation.length == 0) {
return null;
}
return ((Path) annotation[0]).value();
}
public static List<String> apiAnnotationProduces(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Produces) annotation[0]).value());
}
public static List<String> apiAnnotationProduces(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Produces) annotation[0]).value());
}
public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static List<String> apiAnnotationProduces(final Class<?> clazz, final Method method) throws Exception {
final List<String> data = apiAnnotationProduces(method);
if (data != null) {
return data;
}
return apiAnnotationProduces(clazz);
}
public static String apiAnnotationGetOperationDescription(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Operation.class);
if (annotation.length == 0) {
return null;
}
return ((Operation) annotation[0]).description();
}
public static String apiAnnotationGetPath(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class);
if (annotation.length == 0) {
return null;
}
return ((Path) annotation[0]).value();
}
public static String apiAnnotationGetTypeRequest(final Method element) throws Exception {
if (element.getDeclaredAnnotationsByType(GET.class).length == 1) {
return "GET";
}
if (element.getDeclaredAnnotationsByType(POST.class).length == 1) {
return "POST";
}
if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) {
return "PUT";
}
if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) {
return "PATCH";
}
if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) {
return "DELETE";
}
return null;
}
public static String apiAnnotationGetPathParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(PathParam.class);
if (annotation.length == 0) {
return null;
}
return ((PathParam) annotation[0]).value();
}
public static String apiAnnotationGetQueryParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(QueryParam.class);
if (annotation.length == 0) {
return null;
}
return ((QueryParam) annotation[0]).value();
}
public static String apiAnnotationGetFormDataParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataParam.class);
if (annotation.length == 0) {
return null;
}
return ((FormDataParam) annotation[0]).value();
}
public static Class<?>[] apiAnnotationGetAsyncType(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
}
public static Class<?>[] apiAnnotationGetAsyncType(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
}
public static List<String> apiAnnotationGetConsumes(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Consumes) annotation[0]).value());
}
public static List<String> apiAnnotationGetConsumes(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Consumes) annotation[0]).value());
}
public static List<String> apiAnnotationGetConsumes(final Class<?> clazz, final Method method) throws Exception {
final List<String> data = apiAnnotationGetConsumes(method);
if (data != null) {
return data;
}
return apiAnnotationGetConsumes(clazz);
}
public static boolean apiAnnotationIsContext(final Parameter element) throws Exception {
return element.getDeclaredAnnotationsByType(Context.class).length != 0;
}
public static String convertInTypeScriptType(final List<ClassElement> tmp, final boolean isList) {
String out = "";
for (final ClassElement elem : tmp) {
if (out.length() != 0) {
out += " | ";
}
out += elem.tsTypeName;
if (isList) {
out += "[]";
}
}
return out;
}
public static String convertInTypeScriptCheckType(final List<ClassElement> tmp) {
String out = "";
for (final ClassElement elem : tmp) {
if (out.length() != 0) {
out += " | ";
}
out += elem.tsCheckType;
}
return out;
}
record OrderedElement(
String methodName,
Method method) {}
public static APIModel createSingleApi(
final Class<?> clazz,
final Set<Class<?>> includeModel,
final Set<Class<?>> includeCheckerModel,
final GeneratedTypes previous) throws Exception {
final StringBuilder builder = new StringBuilder();
// the basic path has no specific elements...
final String basicPath = apiAnnotationGetPath(clazz);
final String classSimpleName = clazz.getSimpleName();
builder.append("export namespace ");
builder.append(classSimpleName);
builder.append(" {\n");
LOGGER.info("Parse Class for path: {} => {}", classSimpleName, basicPath);
final List<OrderedElement> orderedElements = new ArrayList<>();
for (final Method method : clazz.getDeclaredMethods()) {
final String methodName = method.getName();
orderedElements.add(new OrderedElement(methodName, method));
}
final Comparator<OrderedElement> comparator = Comparator.comparing(OrderedElement::methodName);
Collections.sort(orderedElements, comparator);
for (final OrderedElement orderedElement : orderedElements) {
final Method method = orderedElement.method();
final String methodName = orderedElement.methodName();
final String methodPath = apiAnnotationGetPath(method);
final String methodType = apiAnnotationGetTypeRequest(method);
if (methodType == null) {
LOGGER.error(" [{}] {} => {}/{} ==> No methode type @PATH, @GET ...", methodType, methodName,
basicPath, methodPath);
continue;
}
final String methodDescription = apiAnnotationGetOperationDescription(method);
final List<String> consumes = apiAnnotationGetConsumes(clazz, method);
List<String> produces = apiAnnotationProduces(clazz, method);
LOGGER.trace(" [{}] {} => {}/{}", methodType, methodName, basicPath, methodPath);
if (methodDescription != null) {
LOGGER.trace(" description: {}", methodDescription);
}
final boolean needGenerateProgress = apiAnnotationTypeScriptProgress(method);
Class<?>[] returnTypeModel = apiAnnotationGetAsyncType(method);
boolean isUnmanagedReturnType = false;
boolean returnModelIsArray = false;
List<ClassElement> tmpReturn;
if (returnTypeModel == null) {
Class<?> returnTypeModelRaw = method.getReturnType();
LOGGER.info("Get type: {}", returnTypeModelRaw);
if (returnTypeModelRaw == Response.class) {
LOGGER.info("Get type: {}", returnTypeModelRaw);
}
if (returnTypeModelRaw == Response.class || returnTypeModelRaw == void.class
|| returnTypeModelRaw == Void.class) {
if (returnTypeModelRaw == Response.class) {
isUnmanagedReturnType = true;
}
returnTypeModel = new Class<?>[] { Void.class };
tmpReturn = new ArrayList<>();
produces = null;
} else if (returnTypeModelRaw == Map.class) {
LOGGER.warn("Not manage the Map Model ... set any");
returnTypeModel = new Class<?>[] { Map.class };
tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous);
} else if (returnTypeModelRaw == List.class) {
final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType();
returnTypeModelRaw = (Class<?>) listType.getActualTypeArguments()[0];
returnModelIsArray = true;
returnTypeModel = new Class<?>[] { returnTypeModelRaw };
tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous);
} else {
returnTypeModel = new Class<?>[] { returnTypeModelRaw };
tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous);
}
} else if (returnTypeModel.length >= 0 && (returnTypeModel[0] == Response.class
|| returnTypeModel[0] == Void.class || returnTypeModel[0] == void.class)) {
if (returnTypeModel[0] == Response.class) {
isUnmanagedReturnType = true;
}
returnTypeModel = new Class<?>[] { Void.class };
tmpReturn = new ArrayList<>();
produces = null;
} else if (returnTypeModel.length > 0 && returnTypeModel[0] == Map.class) {
LOGGER.warn("Not manage the Map Model ...");
returnTypeModel = new Class<?>[] { Map.class };
tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous);
} else {
tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous);
}
for (final ClassElement elem : tmpReturn) {
includeModel.add(elem.model[0]);
includeCheckerModel.add(elem.model[0]);
}
LOGGER.trace(" return: {}", tmpReturn.size());
for (final ClassElement elem : tmpReturn) {
LOGGER.trace(" - {}", elem.tsTypeName);
}
final Map<String, String> queryParams = new HashMap<>();
final Map<String, String> pathParams = new HashMap<>();
final Map<String, String> formDataParams = new HashMap<>();
final List<String> emptyElement = new ArrayList<>();
// LOGGER.info(" Parameters:");
for (final Parameter parameter : method.getParameters()) {
// Security context are internal parameter (not available from API)
if (apiAnnotationIsContext(parameter)) {
continue;
}
final Class<?> parameterType = parameter.getType();
String parameterTypeString;
final Class<?>[] asyncType = apiAnnotationGetAsyncType(parameter);
if (parameterType == List.class) {
if (asyncType == null) {
LOGGER.warn("Detect List param ==> not managed type ==> any[] !!!");
parameterTypeString = "any[]";
} else {
final List<ClassElement> tmp = DataFactoryZod.createTables(asyncType, previous);
for (final ClassElement elem : tmp) {
includeModel.add(elem.model[0]);
}
parameterTypeString = convertInTypeScriptType(tmp, true);
}
} else if (asyncType == null) {
final ClassElement tmp = DataFactoryZod.createTable(parameterType, previous);
includeModel.add(tmp.model[0]);
parameterTypeString = tmp.tsTypeName;
} else {
final List<ClassElement> tmp = DataFactoryZod.createTables(asyncType, previous);
for (final ClassElement elem : tmp) {
includeModel.add(elem.model[0]);
}
parameterTypeString = convertInTypeScriptType(tmp, true);
}
final String pathParam = apiAnnotationGetPathParam(parameter);
final String queryParam = apiAnnotationGetQueryParam(parameter);
final String formDataParam = apiAnnotationGetFormDataParam(parameter);
if (queryParam != null) {
queryParams.put(queryParam, parameterTypeString);
} else if (pathParam != null) {
pathParams.put(pathParam, parameterTypeString);
} else if (formDataParam != null) {
formDataParams.put(formDataParam, parameterTypeString);
} else if (asyncType != null) {
final List<ClassElement> tmp = DataFactoryZod.createTables(asyncType, previous);
parameterTypeString = "";
for (final ClassElement elem : tmp) {
includeModel.add(elem.model[0]);
if (parameterTypeString.length() != 0) {
parameterTypeString += " | ";
}
parameterTypeString += elem.tsTypeName;
}
emptyElement.add(parameterTypeString);
} else if (parameterType == List.class) {
parameterTypeString = "any[]";
final Class<?> plop = parameterType.arrayType();
LOGGER.info("ArrayType = {}", plop);
emptyElement.add(parameterTypeString);
} else {
final ClassElement tmp = DataFactoryZod.createTable(parameterType, previous);
includeModel.add(tmp.model[0]);
emptyElement.add(tmp.tsTypeName);
}
}
if (!queryParams.isEmpty()) {
LOGGER.trace(" Query parameter:");
for (final Entry<String, String> queryEntry : queryParams.entrySet()) {
LOGGER.trace(" - {}: {}", queryEntry.getKey(), queryEntry.getValue());
}
}
if (!pathParams.isEmpty()) {
LOGGER.trace(" Path parameter:");
for (final Entry<String, String> pathEntry : pathParams.entrySet()) {
LOGGER.trace(" - {}: {}", pathEntry.getKey(), pathEntry.getValue());
}
}
if (emptyElement.size() > 1) {
LOGGER.error(" Fail to parse: Too much element in the model for the data ...");
continue;
} else if (emptyElement.size() == 1 && formDataParams.size() != 0) {
LOGGER.error(" Fail to parse: Incompatible form data & direct data ...");
continue;
} else if (emptyElement.size() == 1) {
LOGGER.trace(" data type: {}", emptyElement.get(0));
}
// ALL is good can generate the Elements
if (methodDescription != null) {
builder.append("\n\t/**\n\t * ");
builder.append(methodDescription);
builder.append("\n\t */");
}
if (isUnmanagedReturnType) {
builder.append(
"\n\t// TODO: unmanaged \"Response\" type: please specify @AsyncType or considered as 'void'.");
}
builder.append("\n\texport function ");
builder.append(methodName);
builder.append("({\n\t\t\trestConfig,");
if (!queryParams.isEmpty()) {
builder.append("\n\t\t\tqueries,");
}
if (!pathParams.isEmpty()) {
builder.append("\n\t\t\tparams,");
}
if (produces != null && produces.size() > 1) {
builder.append("\n\t\t\tproduce,");
}
if (emptyElement.size() == 1 || formDataParams.size() != 0) {
builder.append("\n\t\t\tdata,");
}
if (needGenerateProgress) {
builder.append("\n\t\t\tcallback,");
}
builder.append("\n\t\t}: {");
builder.append("\n\t\trestConfig: RESTConfig,");
if (!queryParams.isEmpty()) {
builder.append("\n\t\tqueries: {");
for (final Entry<String, String> queryEntry : queryParams.entrySet()) {
builder.append("\n\t\t\t");
builder.append(queryEntry.getKey());
builder.append("?: ");
builder.append(queryEntry.getValue());
builder.append(",");
}
builder.append("\n\t\t},");
}
if (!pathParams.isEmpty()) {
builder.append("\n\t\tparams: {");
for (final Entry<String, String> pathEntry : pathParams.entrySet()) {
builder.append("\n\t\t\t");
builder.append(pathEntry.getKey());
builder.append(": ");
builder.append(pathEntry.getValue());
builder.append(",");
}
builder.append("\n\t\t},");
}
if (emptyElement.size() == 1) {
builder.append("\n\t\tdata: ");
builder.append(emptyElement.get(0));
builder.append(",");
} else if (formDataParams.size() != 0) {
builder.append("\n\t\tdata: {");
for (final Entry<String, String> pathEntry : formDataParams.entrySet()) {
builder.append("\n\t\t\t");
builder.append(pathEntry.getKey());
builder.append(": ");
builder.append(pathEntry.getValue());
builder.append(",");
}
builder.append("\n\t\t},");
}
if (produces != null && produces.size() > 1) {
builder.append("\n\t\tproduce: ");
String isFist = null;
for (final String elem : produces) {
String lastElement = null;
if (MediaType.APPLICATION_JSON.equals(elem)) {
lastElement = "HTTPMimeType.JSON";
}
if (MediaType.MULTIPART_FORM_DATA.equals(elem)) {
lastElement = "HTTPMimeType.MULTIPART";
}
if (DataExport.CSV_TYPE.equals(elem)) {
lastElement = "HTTPMimeType.CSV";
}
if (lastElement != null) {
if (isFist == null) {
isFist = lastElement;
} else {
builder.append(" | ");
}
builder.append(lastElement);
} else {
LOGGER.error("Unmanaged model type: {}", elem);
}
}
builder.append(",");
}
if (needGenerateProgress) {
builder.append("\n\t\tcallback?: RESTCallbacks,");
}
builder.append("\n\t}): Promise<");
if (tmpReturn.size() == 0 //
|| tmpReturn.get(0).tsTypeName == null //
|| tmpReturn.get(0).tsTypeName.equals("void")) {
builder.append("void");
} else {
builder.append(convertInTypeScriptType(tmpReturn, returnModelIsArray));
}
builder.append("> {");
if (tmpReturn.size() == 0 //
|| tmpReturn.get(0).tsTypeName == null //
|| tmpReturn.get(0).tsTypeName.equals("void")) {
builder.append("\n\t\treturn RESTRequestVoid({");
} else if (returnModelIsArray) {
builder.append("\n\t\treturn RESTRequestJsonArray({");
} else {
builder.append("\n\t\treturn RESTRequestJson({");
}
builder.append("\n\t\t\trestModel: {");
builder.append("\n\t\t\t\tendPoint: \"");
builder.append(basicPath);
if (methodPath != null) {
builder.append("/");
builder.append(methodPath);
}
builder.append("\",");
builder.append("\n\t\t\t\trequestType: HTTPRequestModel.");
builder.append(methodType);
builder.append(",");
if (consumes != null) {
for (final String elem : consumes) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
builder.append("\n\t\t\t\tcontentType: HTTPMimeType.JSON,");
break;
} else if (MediaType.MULTIPART_FORM_DATA.equals(elem)) {
builder.append("\n\t\t\t\tcontentType: HTTPMimeType.MULTIPART,");
break;
} else if (MediaType.TEXT_PLAIN.equals(elem)) {
builder.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
break;
}
}
} else if ("DELETE".equals(methodType)) {
builder.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
}
if (produces != null) {
if (produces.size() > 1) {
builder.append("\n\t\t\t\taccept: produce,");
} else {
for (final String elem : produces) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
builder.append("\n\t\t\t\taccept: HTTPMimeType.JSON,");
break;
}
}
}
}
builder.append("\n\t\t\t},");
builder.append("\n\t\t\trestConfig,");
if (!pathParams.isEmpty()) {
builder.append("\n\t\t\tparams,");
}
if (!queryParams.isEmpty()) {
builder.append("\n\t\t\tqueries,");
}
if (emptyElement.size() == 1) {
builder.append("\n\t\t\tdata,");
} else if (formDataParams.size() != 0) {
builder.append("\n\t\t\tdata,");
}
if (needGenerateProgress) {
builder.append("\n\t\t\tcallback,");
}
builder.append("\n\t\t}");
if (tmpReturn.size() != 0 && tmpReturn.get(0).tsTypeName != null
&& !tmpReturn.get(0).tsTypeName.equals("void")) {
builder.append(", ");
// TODO: correct this it is really bad ...
builder.append(convertInTypeScriptCheckType(tmpReturn));
}
builder.append(");");
builder.append("\n\t};");
}
builder.append("\n}\n");
return new APIModel(builder.toString(), classSimpleName);
}
public static void generatePackage(
final List<Class<?>> classApi,
final List<Class<?>> classModel,
final String pathPackage) throws Exception {
final GeneratedTypes previous = DataFactoryZod.createBasicType();
DataFactoryZod.createTable(RestErrorResponse.class, previous);
final List<String> listApi = createApi(classApi, previous, pathPackage);
final String packageApi = DataFactoryZod.createTables(new ArrayList<>(classModel), previous);
FileWriter myWriter = new FileWriter(pathPackage + File.separator + "model.ts");
myWriter.write(packageApi.toString());
myWriter.close();
final StringBuilder index = new StringBuilder("""
/**
* Global import of the package
*/
export * from "./model";
""");
for (final String api : listApi) {
index.append("export * from \"./").append(api).append("\";\n");
}
myWriter = new FileWriter(pathPackage + File.separator + "index.ts");
myWriter.write(index.toString());
myWriter.close();
final InputStream ioStream = DataFactoryTsApi.class.getClassLoader().getResourceAsStream("rest-tools.ts");
if (ioStream == null) {
throw new IllegalArgumentException("rest-tools.ts is not found");
}
final BufferedReader buffer = new BufferedReader(new InputStreamReader(ioStream));
myWriter = new FileWriter(pathPackage + File.separator + "rest-tools.ts");
String line;
while ((line = buffer.readLine()) != null) {
myWriter.write(line);
myWriter.write("\n");
}
ioStream.close();
myWriter.close();
return;
}
}

View File

@ -1,471 +0,0 @@
package org.kar.archidata.dataAccess;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.Response;
public class DataFactoryZod {
static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryZod.class);
static public class ClassElement {
public Class<?>[] model;
public String zodName;
public String tsTypeName;
public String tsCheckType;
public String declaration;
public String comment = null;
public boolean isEnum = false;
public boolean nativeType;
public ClassElement(final Class<?> model[], final String zodName, final String tsTypeName,
final String tsCheckType, final String declaration, final boolean nativeType) {
this.model = model;
this.zodName = zodName;
this.tsTypeName = tsTypeName;
this.tsCheckType = tsCheckType;
this.declaration = declaration;
this.nativeType = nativeType;
}
public ClassElement(final Class<?> model) {
this(new Class<?>[] { model });
}
public ClassElement(final Class<?> model[]) {
this.model = model;
this.zodName = "Zod" + model[0].getSimpleName();
this.tsTypeName = model[0].getSimpleName();
this.tsCheckType = "is" + model[0].getSimpleName();
this.declaration = null;
this.nativeType = false;
}
}
public static class GeneratedTypes {
final List<ClassElement> previousGeneration = new ArrayList<>();
final List<Class<?>> order = new ArrayList<>();
public ClassElement find(final Class<?> clazz) {
for (final ClassElement elem : this.previousGeneration) {
for (final Class<?> elemClass : elem.model) {
if (elemClass == clazz) {
return elem;
}
}
}
return null;
}
public void add(final ClassElement elem) {
this.previousGeneration.add(elem);
}
public void add(final ClassElement elem, final boolean addOrder) {
this.previousGeneration.add(elem);
if (addOrder) {
this.order.add(elem.model[0]);
}
}
public void addOrder(final ClassElement elem) {
this.order.add(elem.model[0]);
}
}
public static ClassElement convertTypeZodEnum(final Class<?> clazz, final GeneratedTypes previous)
throws Exception {
final ClassElement element = new ClassElement(clazz);
previous.add(element);
final Object[] arr = clazz.getEnumConstants();
final StringBuilder out = new StringBuilder();
if (System.getenv("ARCHIDATA_GENERATE_ZOD_ENUM") != null) {
boolean first = true;
out.append("zod.enum([");
for (final Object elem : arr) {
if (!first) {
out.append(",\n\t");
} else {
out.append("\n\t");
first = false;
}
out.append("'");
out.append(elem.toString());
out.append("'");
}
if (first) {
out.append("]}");
} else {
out.append("\n\t])");
}
} else {
element.isEnum = true;
boolean first = true;
out.append("{");
for (final Object elem : arr) {
if (!first) {
out.append(",\n\t");
} else {
out.append("\n\t");
first = false;
}
out.append(elem.toString());
out.append(" = '");
out.append(elem.toString());
out.append("'");
}
if (first) {
out.append("}");
} else {
out.append(",\n\t}");
}
}
element.declaration = out.toString();
previous.addOrder(element);
return element;
}
public static String convertTypeZod(final Class<?> type, final GeneratedTypes previous) throws Exception {
final ClassElement previousType = previous.find(type);
if (previousType != null) {
return previousType.zodName;
}
if (type.isEnum()) {
return convertTypeZodEnum(type, previous).zodName;
}
if (type == List.class) {
throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName()
+ " Unmanaged List of List ... ");
}
final ClassElement elemCreated = createTable(type, previous);
if (elemCreated != null) {
return elemCreated.zodName;
}
throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName());
}
public static String convertTypeZod(final Field field, final GeneratedTypes previous) throws Exception {
final Class<?> type = field.getType();
final ClassElement previousType = previous.find(type);
if (previousType != null) {
return previousType.zodName;
}
if (type.isEnum()) {
return convertTypeZodEnum(type, previous).zodName;
}
if (type == List.class) {
final ParameterizedType listType = (ParameterizedType) field.getGenericType();
final Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];
final String simpleSubType = convertTypeZod(listClass, previous);
return "zod.array(" + simpleSubType + ")";
}
final ClassElement elemCreated = createTable(type, previous);
if (elemCreated != null) {
return elemCreated.zodName;
}
throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName());
}
public static String optionalTypeZod(final Class<?> type) throws Exception {
if (type.isPrimitive()) {
return "";
}
return ".optional()";
}
public static void createTablesSpecificType(
final Field elem,
final int fieldId,
final StringBuilder builder,
final GeneratedTypes previous) throws Exception {
final String name = elem.getName();
final Class<?> classModel = elem.getType();
final int limitSize = AnnotationTools.getLimitSize(elem);
final String comment = AnnotationTools.getComment(elem);
if (fieldId != 0) {
builder.append(",");
}
if (comment != null) {
builder.append("\n\t// ");
builder.append(comment);
}
builder.append("\n\t");
builder.append(name);
builder.append(": ");
builder.append(convertTypeZod(elem, previous));
if (limitSize > 0 && classModel == String.class) {
builder.append(".max(");
builder.append(limitSize);
builder.append(")");
}
if (AnnotationTools.getSchemaReadOnly(elem)) {
builder.append(".readonly()");
}
builder.append(optionalTypeZod(classModel));
}
private static boolean isFieldFromSuperClass(final Class<?> model, final String filedName) {
final Class<?> superClass = model.getSuperclass();
if (superClass == null) {
return false;
}
for (final Field field : superClass.getFields()) {
String name;
try {
name = AnnotationTools.getFieldName(field);
if (filedName.equals(name)) {
return true;
}
} catch (final Exception e) {
// TODO Auto-generated catch block
LOGGER.trace("Catch error field name in parent create data table: {}", e.getMessage());
}
}
return false;
}
public static GeneratedTypes createBasicType() throws Exception {
final GeneratedTypes previous = new GeneratedTypes();
previous.add(new ClassElement(new Class<?>[] { Void.class, void.class }, "void", "void", null, null, true));
// Map is binded to any ==> can not determine this complex model for now
previous.add(new ClassElement(new Class<?>[] { Map.class }, "any", "any", null, null, true));
previous.add(new ClassElement(new Class<?>[] { String.class }, "zod.string()", "string", null, "zod.string()",
true));
previous.add(new ClassElement(new Class<?>[] { InputStream.class, FormDataContentDisposition.class },
"z.instanceof(File)", "File", null, "z.instanceof(File)", true));
previous.add(new ClassElement(new Class<?>[] { Boolean.class, boolean.class }, "zod.boolean()", "boolean", null,
"zod.boolean()", true));
previous.add(new ClassElement(new Class<?>[] { UUID.class }, "ZodUUID", "UUID", "isUUID", "zod.string().uuid()",
false), true);
previous.add(new ClassElement(new Class<?>[] { Long.class, long.class }, "ZodLong", "Long", "isLong",
// "zod.bigint()",
"zod.number()", false), true);
previous.add(new ClassElement(new Class<?>[] { Integer.class, int.class }, "ZodInteger", "Integer", "isInteger",
"zod.number().safe()", false), true);
previous.add(new ClassElement(new Class<?>[] { Double.class, double.class }, "ZodDouble", "Double", "isDouble",
"zod.number()", true), true);
previous.add(new ClassElement(new Class<?>[] { Float.class, float.class }, "ZodFloat", "Float", "isFloat",
"zod.number()", false), true);
previous.add(new ClassElement(new Class<?>[] { Instant.class }, "ZodInstant", "Instant", "isInstant",
"zod.string()", false), true);
previous.add(new ClassElement(new Class<?>[] { Date.class }, "ZodDate", "Date", "isDate",
"zod.string().datetime({ precision: 3 })", false), true);
previous.add(new ClassElement(new Class<?>[] { Timestamp.class }, "ZodTimestamp", "Timestamp", "isTimestamp",
"zod.string().datetime({ precision: 3 })", false), true);
previous.add(new ClassElement(new Class<?>[] { LocalDate.class }, "ZodLocalDate", "LocalDate", "isLocalDate",
"zod.string().date()", false), true);
previous.add(new ClassElement(new Class<?>[] { LocalTime.class }, "ZodLocalTime", "LocalTime", "isLocalTime",
"zod.string().time()", false), true);
return previous;
}
/** Request the generation of the TypeScript file for the "Zod" export model
* @param classs List of class used in the model
* @return A string representing the Server models
* @throws Exception */
public static String createTables(final List<Class<?>> classs) throws Exception {
return createTables(classs, createBasicType());
}
public static String createTables(final List<Class<?>> classs, final GeneratedTypes previous) throws Exception {
for (final Class<?> clazz : classs) {
createTable(clazz, previous);
}
final StringBuilder generatedData = new StringBuilder();
generatedData.append("""
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from \"zod\";
""");
for (final Class<?> elem : previous.order) {
final ClassElement data = previous.find(elem);
if (!data.nativeType) {
if (data.comment != null) {
generatedData.append(data.comment);
}
generatedData.append(createDeclaration(data));
generatedData.append("\n\n");
}
}
LOGGER.info("generated: {}", generatedData.toString());
return generatedData.toString();
}
public static List<ClassElement> createTables(final Class<?>[] classs, final GeneratedTypes previous)
throws Exception {
final List<ClassElement> out = new ArrayList<>();
for (final Class<?> clazz : classs) {
if (clazz == Response.class) {
throw new IOException("Can not generate a Zod element for an unknow type Response");
}
out.add(createTable(clazz, previous));
}
return out;
}
public static ClassElement createTable(final Class<?> clazz, final GeneratedTypes previous) throws Exception {
if (clazz == null) {
return null;
}
if (clazz == Response.class) {
throw new IOException("Can not generate a Zod element for an unknow type Response");
}
final ClassElement alreadyExist = previous.find(clazz);
if (previous.find(clazz) != null) {
return alreadyExist;
}
if (clazz.isPrimitive()) {
return null;
}
if (clazz.isEnum()) {
return convertTypeZodEnum(clazz, previous);
}
// add the current class to prevent multiple creation
final ClassElement curentElementClass = new ClassElement(clazz);
previous.add(curentElementClass);
// Local generation of class:
final StringBuilder internalBuilder = new StringBuilder();
final List<String> alreadyAdded = new ArrayList<>();
LOGGER.trace("parse class: '{}'", clazz.getCanonicalName());
int fieldId = 0;
for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
continue;
}
final String dataName = elem.getName();
if (isFieldFromSuperClass(clazz, dataName)) {
LOGGER.trace(" SKIP: '{}'", elem.getName());
continue;
}
if (alreadyAdded.contains(dataName)) {
LOGGER.trace(" SKIP2: '{}'", elem.getName());
continue;
}
alreadyAdded.add(dataName);
LOGGER.trace(" + '{}'", elem.getName());
if (false && DataAccess.isAddOnField(elem)) {
final DataAccessAddOn addOn = DataAccess.findAddOnforField(elem);
LOGGER.error("Create type for: {} ==> {} (ADD-ON) ==> Not managed now ....",
AnnotationTools.getFieldName(elem), elem.getType());
/* LOGGER.trace("Create type for: {} ==> {} (ADD-ON)", AnnotationTools.getFieldName(elem), elem.getType()); if (addOn != null) { addOn.createTables(tableName, elem, tmpOut,
* preActionList, postActionList, createIfNotExist, createDrop, fieldId); } else { throw new DataAccessException( "Element matked as add-on but add-on does not loaded: table:" +
* tableName + " field name=" + AnnotationTools.getFieldName(elem) + " type=" + elem.getType()); } fieldId++; */
} else {
LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType());
DataFactoryZod.createTablesSpecificType(elem, fieldId, internalBuilder, previous);
fieldId++;
}
}
final String description = AnnotationTools.getSchemaDescription(clazz);
final String example = AnnotationTools.getSchemaExample(clazz);
final StringBuilder generatedCommentedData = new StringBuilder();
if (description != null || example != null) {
generatedCommentedData.append("/**\n");
if (description != null) {
for (final String elem : description.split("\n")) {
generatedCommentedData.append(" * ");
generatedCommentedData.append(elem);
generatedCommentedData.append("\n");
}
}
if (example != null) {
generatedCommentedData.append(" * Example:\n");
generatedCommentedData.append(" * ```\n");
for (final String elem : example.split("\n")) {
generatedCommentedData.append(" * ");
generatedCommentedData.append(elem);
generatedCommentedData.append("\n");
}
generatedCommentedData.append(" * ```\n");
}
generatedCommentedData.append(" */\n");
}
curentElementClass.comment = generatedCommentedData.toString();
final StringBuilder generatedData = new StringBuilder();
final Class<?> parentClass = clazz.getSuperclass();
if (parentClass != null && parentClass != Object.class && parentClass != Record.class) {
final ClassElement parentDeclaration = createTable(parentClass, previous);
generatedData.append(parentDeclaration.zodName);
generatedData.append(".extend({");
} else {
generatedData.append("zod.object({");
}
generatedData.append(internalBuilder.toString());
generatedData.append("\n})");
// Remove the previous to reorder the map ==> parent must be inserted before us.
curentElementClass.declaration = generatedData.toString();
previous.addOrder(curentElementClass);
return curentElementClass;
}
public static String createDeclaration(final ClassElement elem) {
final StringBuilder generatedData = new StringBuilder();
if (elem.isEnum) {
generatedData.append("export enum ");
generatedData.append(elem.tsTypeName);
generatedData.append(" ");
generatedData.append(elem.declaration);
generatedData.append(";");
generatedData.append("\nexport const ");
generatedData.append(elem.zodName);
generatedData.append(" = zod.nativeEnum(");
generatedData.append(elem.tsTypeName);
generatedData.append(");");
} else {
generatedData.append("export const ");
generatedData.append(elem.zodName);
generatedData.append(" = ");
generatedData.append(elem.declaration);
generatedData.append(";");
generatedData.append("\nexport type ");
generatedData.append(elem.tsTypeName);
generatedData.append(" = zod.infer<typeof ");
generatedData.append(elem.zodName);
generatedData.append(">;");
}
// declare generic isXXX:
generatedData.append("\nexport function ");
generatedData.append(elem.tsCheckType);
generatedData.append("(data: any): data is ");
generatedData.append(elem.tsTypeName);
generatedData.append(" {\n\ttry {\n\t\t");
generatedData.append(elem.zodName);
generatedData.append("""
.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data ${e}`);
return false;
}
}
""");
return generatedData.toString();
}
}

View File

@ -1,7 +1,6 @@
package org.kar.archidata.dataAccess; package org.kar.archidata.dataAccess;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.options.AccessDeletedItems; import org.kar.archidata.dataAccess.options.AccessDeletedItems;
@ -25,10 +24,16 @@ public class QueryOptions {
if (elems == null || elems.length == 0) { if (elems == null || elems.length == 0) {
return; return;
} }
Collections.addAll(this.options, elems); for (final QueryOption elem : elems) {
add(elem);
}
} }
public void add(final QueryOption option) { public void add(final QueryOption option) {
if (option == null) {
return;
}
this.options.add(option); this.options.add(option);
} }

View File

@ -4,6 +4,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -22,12 +23,16 @@ import org.kar.archidata.dataAccess.addOn.model.TableCoversLongUUID;
import org.kar.archidata.dataAccess.addOn.model.TableCoversUUIDLong; import org.kar.archidata.dataAccess.addOn.model.TableCoversUUIDLong;
import org.kar.archidata.dataAccess.addOn.model.TableCoversUUIDUUID; import org.kar.archidata.dataAccess.addOn.model.TableCoversUUIDUUID;
import org.kar.archidata.dataAccess.options.OverrideTableName; import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -40,7 +45,7 @@ public class AddOnDataJson implements DataAccessAddOn {
} }
@Override @Override
public String getSQLFieldType(final Field elem) throws Exception { public String getSQLFieldType(final Field elem) throws DataAccessException {
final String fieldName = AnnotationTools.getFieldName(elem); final String fieldName = AnnotationTools.getFieldName(elem);
return DataFactory.convertTypeInSQL(String.class, fieldName); return DataFactory.convertTypeInSQL(String.class, fieldName);
} }
@ -53,7 +58,7 @@ public class AddOnDataJson implements DataAccessAddOn {
@Override @Override
public void insertData(final PreparedStatement ps, final Field field, final Object rootObject, final CountInOut iii) public void insertData(final PreparedStatement ps, final Field field, final Object rootObject, final CountInOut iii)
throws Exception { throws IllegalArgumentException, IllegalAccessException, SQLException, JsonProcessingException {
final Object data = field.get(rootObject); final Object data = field.get(rootObject);
if (data == null) { if (data == null) {
ps.setNull(iii.value, Types.VARCHAR); ps.setNull(iii.value, Types.VARCHAR);
@ -70,7 +75,7 @@ public class AddOnDataJson implements DataAccessAddOn {
} }
@Override @Override
public boolean isInsertAsync(final Field field) throws Exception { public boolean isInsertAsync(final Field field) {
return false; return false;
} }
@ -149,7 +154,10 @@ public class AddOnDataJson implements DataAccessAddOn {
} }
LOGGER.warn("Maybe fail to translate Model in datajson list: List<{}>", listClass.getCanonicalName()); LOGGER.warn("Maybe fail to translate Model in datajson list: List<{}>", listClass.getCanonicalName());
} }
final Object dataParsed = objectMapper.readValue(jsonData, field.getType()); final TypeFactory typeFactory = objectMapper.getTypeFactory();
final JavaType fieldType = typeFactory.constructType(field.getGenericType());
final Object dataParsed = objectMapper.readValue(jsonData, fieldType);
//final Object dataParsed = objectMapper.readValue(jsonData, field.getType());
field.set(data, dataParsed); field.set(data, dataParsed);
} }
} }
@ -186,9 +194,21 @@ public class AddOnDataJson implements DataAccessAddOn {
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName));
} }
/**
* Adds a remoteKey to the covers list of a data entry identified by the given class type and ID.
* If the covers list is null, it initializes it. If the remoteKey already exists in the list,
* the method returns without making any changes.
*
* @param clazz The class type to retrieve the table name from.
* @param id The ID of the data object to fetch.
* @param column The name of the column (currently not used, but may be used for specifying a field name).
* @param remoteKey The UUID to add to the covers list.
* @throws Exception If an error occurs during data retrieval or update.
*/
public static void addLink(final Class<?> clazz, final Long id, final String column, final UUID remoteKey) public static void addLink(final Class<?> clazz, final Long id, final String column, final UUID remoteKey)
throws Exception { throws Exception {
final String tableName = AnnotationTools.getTableName(clazz); final String tableName = AnnotationTools.getTableName(clazz);
// TODO: Get primary key name
final TableCoversLongUUID data = DataAccess.get(TableCoversLongUUID.class, id, final TableCoversLongUUID data = DataAccess.get(TableCoversLongUUID.class, id,
new OverrideTableName(tableName)); new OverrideTableName(tableName));
if (data.covers == null) { if (data.covers == null) {
@ -203,10 +223,21 @@ public class AddOnDataJson implements DataAccessAddOn {
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName));// TODO: ,new OverrideFieldName("covers", column)); DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName));// TODO: ,new OverrideFieldName("covers", column));
} }
public static void addLink(final Class<?> clazz, final UUID id, final String column, final UUID remoteKey) /**
* Adds a remoteKey to the covers list of a data entry identified by the given class type and ID.
* If the covers list is null, it initializes it. If the remoteKey already exists in the list,
* the method returns without making any changes.
*
* @param clazz The class type to retrieve the table name from.
* @param id The ID of the data object to fetch.
* @param column The name of the column (currently not used, but may be used for specifying a field name).
* @param remoteKey The UUID to add to the covers list.
* @throws Exception If an error occurs during data retrieval or update.
*/
public static void addLink(final Class<?> clazz, final UUID uuid, final String column, final UUID remoteKey)
throws Exception { throws Exception {
final String tableName = AnnotationTools.getTableName(clazz); final String tableName = AnnotationTools.getTableName(clazz);
final TableCoversUUIDUUID data = DataAccess.get(TableCoversUUIDUUID.class, id, final TableCoversUUIDUUID data = DataAccess.get(TableCoversUUIDUUID.class, uuid,
new OverrideTableName(tableName)); new OverrideTableName(tableName));
if (data.covers == null) { if (data.covers == null) {
data.covers = new ArrayList<>(); data.covers = new ArrayList<>();
@ -217,13 +248,13 @@ public class AddOnDataJson implements DataAccessAddOn {
} }
} }
data.covers.add(remoteKey); data.covers.add(remoteKey);
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.uuid, List.of("covers"), new OverrideTableName(tableName));
} }
public static void addLink(final Class<?> clazz, final UUID id, final String column, final Long remoteKey) public static void addLink(final Class<?> clazz, final UUID uuid, final String column, final Long remoteKey)
throws Exception { throws Exception {
final String tableName = AnnotationTools.getTableName(clazz); final String tableName = AnnotationTools.getTableName(clazz);
final TableCoversUUIDLong data = DataAccess.get(TableCoversUUIDLong.class, id, final TableCoversUUIDLong data = DataAccess.get(TableCoversUUIDLong.class, uuid,
new OverrideTableName(tableName)); new OverrideTableName(tableName));
if (data.covers == null) { if (data.covers == null) {
data.covers = new ArrayList<>(); data.covers = new ArrayList<>();
@ -234,13 +265,13 @@ public class AddOnDataJson implements DataAccessAddOn {
} }
} }
data.covers.add(remoteKey); data.covers.add(remoteKey);
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.uuid, List.of("covers"), new OverrideTableName(tableName));
} }
public static void removeLink(final Class<?> clazz, final UUID id, final String column, final Long remoteKey) public static void removeLink(final Class<?> clazz, final UUID uuid, final String column, final Long remoteKey)
throws Exception { throws Exception {
final String tableName = AnnotationTools.getTableName(clazz); final String tableName = AnnotationTools.getTableName(clazz);
final TableCoversUUIDLong data = DataAccess.get(TableCoversUUIDLong.class, id, final TableCoversUUIDLong data = DataAccess.get(TableCoversUUIDLong.class, uuid,
new OverrideTableName(tableName)); new OverrideTableName(tableName));
if (data.covers == null) { if (data.covers == null) {
return; return;
@ -253,13 +284,13 @@ public class AddOnDataJson implements DataAccessAddOn {
newList.add(elem); newList.add(elem);
} }
data.covers = newList; data.covers = newList;
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.uuid, List.of("covers"), new OverrideTableName(tableName));
} }
public static void removeLink(final Class<?> clazz, final UUID id, final String column, final UUID remoteKey) public static void removeLink(final Class<?> clazz, final UUID uuid, final String column, final UUID remoteKey)
throws Exception { throws Exception {
final String tableName = AnnotationTools.getTableName(clazz); final String tableName = AnnotationTools.getTableName(clazz);
final TableCoversUUIDUUID data = DataAccess.get(TableCoversUUIDUUID.class, id, final TableCoversUUIDUUID data = DataAccess.get(TableCoversUUIDUUID.class, uuid,
new OverrideTableName(tableName)); new OverrideTableName(tableName));
if (data.covers == null) { if (data.covers == null) {
return; return;
@ -272,7 +303,7 @@ public class AddOnDataJson implements DataAccessAddOn {
newList.add(elem); newList.add(elem);
} }
data.covers = newList; data.covers = newList;
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.uuid, List.of("covers"), new OverrideTableName(tableName));
} }
public static void removeLink(final Class<?> clazz, final Long id, final String column, final Long remoteKey) public static void removeLink(final Class<?> clazz, final Long id, final String column, final Long remoteKey)
@ -312,9 +343,4 @@ public class AddOnDataJson implements DataAccessAddOn {
data.covers = newList; data.covers = newList;
DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName)); DataAccess.update(data, data.id, List.of("covers"), new OverrideTableName(tableName));
} }
/* public static <TYPE> void addLink(final Class<TYPE> clazz, final Object localKey, final String column, final Object remoteKey) throws Exception { final String tableName =
* AnnotationTools.getTableName(clazz); final TYPE data = DataAccess.get(clazz, localKey); // TODO: add filter of the "column" // find the field column: // add the remoteKey in the list: // post
* new data in the DB final String linkTableName = generateLinkTableName(this.tableName, this.column); final LinkTable insertElement = new LinkTable(this.localKey, this.remoteKey);
* DataAccess.insert(insertElement, new OverrideTableName(linkTableName)); } */
} }

View File

@ -108,16 +108,28 @@ public class AddOnManyToMany implements DataAccessAddOn {
@NotNull final String name, @NotNull final String name,
@NotNull final CountInOut count, @NotNull final CountInOut count,
final QueryOptions options) throws Exception { final QueryOptions options) throws Exception {
final String linkTableName = generateLinkTableName(tableName, name); final ManyToMany manyToMany = AnnotationTools.getManyToMany(field);
String linkTableName = generateLinkTableName(tableName, name);
if (manyToMany.mappedBy() != null && manyToMany.mappedBy().length() != 0) {
// TODO: get the remote table name .....
final String remoteTableName = AnnotationTools.getTableName(manyToMany.targetEntity());
linkTableName = generateLinkTableName(remoteTableName, manyToMany.mappedBy());
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0]; .getActualTypeArguments()[0];
final String tmpVariable = "tmp_" + Integer.toString(count.value); final String tmpVariable = "tmp_" + Integer.toString(count.value);
querySelect.append(" (SELECT GROUP_CONCAT("); querySelect.append(" (SELECT GROUP_CONCAT(");
querySelect.append(tmpVariable); querySelect.append(tmpVariable);
querySelect.append(".object2Id "); final boolean mode = manyToMany.mappedBy() == null || manyToMany.mappedBy().length() == 0;
if (mode) {
querySelect.append(".object2Id ");
} else {
querySelect.append(".object1Id ");
}
if ("sqlite".equals(ConfigBaseVariable.getDBType())) { if ("sqlite".equals(ConfigBaseVariable.getDBType())) {
querySelect.append(", "); querySelect.append(", ");
} else { } else {
querySelect.append("ORDER BY uuid ASC ");
querySelect.append("SEPARATOR "); querySelect.append("SEPARATOR ");
} }
querySelect.append("'"); querySelect.append("'");
@ -143,11 +155,19 @@ public class AddOnManyToMany implements DataAccessAddOn {
querySelect.append(" = "); querySelect.append(" = ");
querySelect.append(tmpVariable); querySelect.append(tmpVariable);
querySelect.append("."); querySelect.append(".");
querySelect.append("object1Id "); if (mode) {
querySelect.append("object1Id ");
} else {
querySelect.append("object2Id ");
}
if (!"sqlite".equals(ConfigBaseVariable.getDBType())) { if (!"sqlite".equals(ConfigBaseVariable.getDBType())) {
querySelect.append(" GROUP BY "); querySelect.append(" GROUP BY ");
querySelect.append(tmpVariable); querySelect.append(tmpVariable);
querySelect.append(".object1Id"); if (mode) {
querySelect.append(".object1Id");
} else {
querySelect.append(".object2Id");
}
} }
querySelect.append(") AS "); querySelect.append(") AS ");
querySelect.append(name); querySelect.append(name);
@ -172,6 +192,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 !!
if (objectClass == Long.class || objectClass == UUID.class) { if (objectClass == Long.class || objectClass == UUID.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options); generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options);
} }
@ -508,6 +529,12 @@ public class AddOnManyToMany implements DataAccessAddOn {
final boolean createIfNotExist, final boolean createIfNotExist,
final boolean createDrop, final boolean createDrop,
final int fieldId) throws Exception { final int fieldId) throws Exception {
final ManyToMany manyToMany = AnnotationTools.getManyToMany(field);
if (manyToMany.mappedBy() != null && manyToMany.mappedBy().length() != 0) {
// not the reference model to create base:
return;
}
final String linkTableName = generateLinkTableNameField(tableName, field); final String linkTableName = generateLinkTableNameField(tableName, field);
final QueryOptions options = new QueryOptions(new OverrideTableName(linkTableName)); final QueryOptions options = new QueryOptions(new OverrideTableName(linkTableName));
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()) final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
@ -526,7 +553,6 @@ public class AddOnManyToMany implements DataAccessAddOn {
postActionList.addAll(sqlCommand); postActionList.addAll(sqlCommand);
} }
} else if (primaryType == UUID.class) { } else if (primaryType == UUID.class) {
if (objectClass == Long.class) { if (objectClass == Long.class) {
final List<String> sqlCommand = DataFactory.createTable(LinkTableUUIDLong.class, options); final List<String> sqlCommand = DataFactory.createTable(LinkTableUUIDLong.class, options);
postActionList.addAll(sqlCommand); postActionList.addAll(sqlCommand);

View File

@ -238,7 +238,11 @@ public class AddOnManyToOne implements DataAccessAddOn {
if (dataNew != null && countNotNull.value != 0) { if (dataNew != null && countNotNull.value != 0) {
field.set(data, dataNew); field.set(data, dataNew);
} }
} else { return;
}
final Field remotePrimaryKeyField = AnnotationTools.getFieldOfId(objectClass);
final Class<?> remotePrimaryKeyType = remotePrimaryKeyField.getType();
if (remotePrimaryKeyType == Long.class) {
// here we have the field, the data and the the remote value ==> can create callback that generate the update of the value ... // here we have the field, the data and the the remote value ==> can create callback that generate the update of the value ...
final Long foreignKey = rs.getLong(count.value); final Long foreignKey = rs.getLong(count.value);
count.inc(); count.inc();
@ -254,6 +258,22 @@ public class AddOnManyToOne implements DataAccessAddOn {
}; };
lazyCall.add(lambda); lazyCall.add(lambda);
} }
} else if (remotePrimaryKeyType == UUID.class) {
// here we have the field, the data and the the remote value ==> can create callback that generate the update of the value ...
final UUID foreignKey = DataAccess.getListOfRawUUID(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 = DataAccess.get(decorators.targetEntity(), foreignKey);
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} }
} }
} }

View File

@ -1,28 +1,36 @@
package org.kar.archidata.dataAccess.addOn; package org.kar.archidata.dataAccess.addOn;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.dataAccess.CountInOut; import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataAccessAddOn; import org.kar.archidata.dataAccess.DataAccessAddOn;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.LazyGetter; import org.kar.archidata.dataAccess.LazyGetter;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
public class AddOnOneToMany implements DataAccessAddOn { public class AddOnOneToMany implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class); static final Logger LOGGER = LoggerFactory.getLogger(AddOnOneToMany.class);
static final String SEPARATOR_LONG = "-";
/** Convert the list if external id in a string '-' separated /** Convert the list if external id in a string '-' separated
* @param ids List of value (null are removed) * @param ids List of value (null are removed)
@ -77,15 +85,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
@Override @Override
public void insertData(final PreparedStatement ps, final Field field, final Object rootObject, final CountInOut iii) public void insertData(final PreparedStatement ps, final Field field, final Object rootObject, final CountInOut iii)
throws SQLException, IllegalArgumentException, IllegalAccessException { throws SQLException, IllegalArgumentException, IllegalAccessException {
final Object data = field.get(rootObject); throw new IllegalAccessException("Can not generate an inset of @OneToMany");
iii.inc();
if (data == null) {
ps.setNull(iii.value, Types.BIGINT);
} else {
@SuppressWarnings("unchecked")
final String dataTmp = getStringOfIds((List<Long>) data);
ps.setString(iii.value, dataTmp);
}
} }
@Override @Override
@ -95,14 +95,88 @@ public class AddOnOneToMany implements DataAccessAddOn {
@Override @Override
public boolean isInsertAsync(final Field field) throws Exception { public boolean isInsertAsync(final Field field) throws Exception {
// TODO: can be implemented later...
return false; return false;
} }
@Override @Override
public boolean canRetrieve(final Field field) { public boolean canRetrieve(final Field field) {
if (field.getType() != List.class) {
return false;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) {
return true;
}
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
if (decorators == null) {
return false;
}
if (decorators.targetEntity() == objectClass) {
return true;
}
return false; return false;
} }
public void generateConcatQuery(
@NotNull final String tableName,
@NotNull final String primaryKey,
@NotNull final Field field,
@NotNull final StringBuilder querySelect,
@NotNull final StringBuilder query,
@NotNull final String name,
@NotNull final CountInOut count,
final QueryOptions options,
final Class<?> targetEntity,
final String mappedBy) throws Exception {
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
final String remoteTableName = AnnotationTools.getTableName(targetEntity);
final String remoteTablePrimaryKeyName = AnnotationTools
.getFieldName(AnnotationTools.getPrimaryKeyField(targetEntity));
final String tmpRemoteVariable = "tmp_" + Integer.toString(count.value);
final String remoteDeletedFieldName = AnnotationTools.getDeletedFieldName(targetEntity);
querySelect.append(" (SELECT GROUP_CONCAT(");
querySelect.append(tmpRemoteVariable);
querySelect.append(".");
querySelect.append(remoteTablePrimaryKeyName);
querySelect.append(" ");
if ("sqlite".equals(ConfigBaseVariable.getDBType())) {
querySelect.append(", ");
} else {
querySelect.append("SEPARATOR ");
}
querySelect.append("'");
if (objectClass == Long.class) {
querySelect.append(SEPARATOR_LONG);
}
querySelect.append("') FROM ");
querySelect.append(remoteTableName);
querySelect.append(" ");
querySelect.append(tmpRemoteVariable);
querySelect.append(" WHERE ");
if (remoteDeletedFieldName != null) {
querySelect.append(tmpRemoteVariable);
querySelect.append(".");
querySelect.append(remoteDeletedFieldName);
querySelect.append(" = false");
querySelect.append(" AND ");
}
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(primaryKey);
querySelect.append(" = ");
querySelect.append(tmpRemoteVariable);
querySelect.append(".");
querySelect.append(mappedBy);
querySelect.append(" ");
querySelect.append(") AS ");
querySelect.append(name);
querySelect.append(" ");
}
@Override @Override
public void generateQuery( public void generateQuery(
@NotNull final String tableName, @NotNull final String tableName,
@ -112,12 +186,35 @@ public class AddOnOneToMany implements DataAccessAddOn {
@NotNull final StringBuilder query, @NotNull final StringBuilder query,
@NotNull final String name, @NotNull final String name,
@NotNull final CountInOut count, @NotNull final CountInOut count,
final QueryOptions options) { final QueryOptions options) throws Exception {
querySelect.append(" "); if (field.getType() != List.class) {
querySelect.append(tableName); return;
querySelect.append("."); }
querySelect.append(name); final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
count.inc(); .getActualTypeArguments()[0];
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
if (decorators == null) {
return;
}
// TODO: manage better the eager and lazy !!
if (objectClass == Long.class || objectClass == UUID.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options,
decorators.targetEntity(), decorators.mappedBy());
return;
}
if (objectClass == decorators.targetEntity()) {
if (decorators.fetch() == FetchType.EAGER) {
throw new DataAccessException("EAGER is not supported for list of element...");
} else {
// Force a copy of the primaryKey to permit the async retrieve of the data
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(primaryKey);
querySelect.append(" AS tmp_");
querySelect.append(Integer.toString(count.value));
}
}
} }
@Override @Override
@ -127,16 +224,86 @@ public class AddOnOneToMany implements DataAccessAddOn {
final Object data, final Object data,
final CountInOut count, final CountInOut count,
final QueryOptions options, final QueryOptions options,
final List<LazyGetter> lazyCall) throws SQLException, IllegalArgumentException, IllegalAccessException { final List<LazyGetter> lazyCall) throws Exception {
final Long foreignKey = rs.getLong(count.value); if (field.getType() != List.class) {
count.inc(); LOGGER.error("Can not OneToMany with other than List Model: {}", field.getType().getCanonicalName());
if (!rs.wasNull()) { return;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
if (decorators == null) {
return;
}
if (objectClass == Long.class) {
final List<Long> idList = DataAccess.getListOfIds(rs, count.value, SEPARATOR_LONG);
field.set(data, idList);
count.inc();
return;
} else if (objectClass == UUID.class) {
final List<UUID> idList = DataAccess.getListOfRawUUIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
}
if (objectClass == decorators.targetEntity()) {
field.set(data, foreignKey); Long parentIdTmp = null;
UUID parendUuidTmp = null;
try {
final String modelData = rs.getString(count.value);
parentIdTmp = Long.valueOf(modelData);
count.inc();
} catch (final NumberFormatException ex) {
final List<UUID> idList = DataAccess.getListOfRawUUIDs(rs, count.value);
parendUuidTmp = idList.get(0);
count.inc();
}
final Long parentId = parentIdTmp;
final UUID parendUuid = parendUuidTmp;
final String mappingKey = decorators.mappedBy();
// We get the parent ID ... ==> need to request the list of elements
if (objectClass == Long.class) {
LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList);
return;
} else if (objectClass == UUID.class) {
LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList);
return;
}
if (objectClass == decorators.targetEntity()) {
if (decorators.fetch() == FetchType.EAGER) {
throw new DataAccessException("EAGER is not supported for list of element...");
} else if (parentId != null) {
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
@SuppressWarnings("unchecked")
final Object foreignData = DataAccess.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parentId)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
} else if (parendUuid != null) {
final LazyGetter lambda = () -> {
@SuppressWarnings("unchecked")
final Object foreignData = DataAccess.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendUuid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
}
} }
} }
// TODO : refacto this table to manage a generic table with dynamic name to be serializable with the default system // TODO : refacto this table to manage a generic table with dynamic name to be serialize with the default system
@Override @Override
public void createTables( public void createTables(
final String tableName, final String tableName,
@ -148,7 +315,6 @@ public class AddOnOneToMany implements DataAccessAddOn {
final boolean createIfNotExist, final boolean createIfNotExist,
final boolean createDrop, final boolean createDrop,
final int fieldId) throws Exception { final int fieldId) throws Exception {
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList, // This is a remote field ==> nothing to generate (it is stored in the remote object
postActionList, createIfNotExist, createDrop, fieldId, Long.class);
} }
} }

View File

@ -1,136 +0,0 @@
package org.kar.archidata.dataAccess.addOn;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.addOn.SQLTableExternalForeinKeyAsList;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataAccessAddOn;
import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.LazyGetter;
import org.kar.archidata.dataAccess.QueryOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.validation.constraints.NotNull;
// TODO: maybe deprecated ==> use DataJson instead...
@Deprecated
public class AddOnSQLTableExternalForeinKeyAsList implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
static final String SEPARATOR = "-";
/** Convert the list if external id in a string '-' separated
* @param ids List of value (null are removed)
* @return '-' string separated */
protected static String getStringOfIds(final List<Long> ids) {
final List<Long> tmp = new ArrayList<>(ids);
return tmp.stream().map(String::valueOf).collect(Collectors.joining(SEPARATOR));
}
@Override
public Class<?> getAnnotationClass() {
return SQLTableExternalForeinKeyAsList.class;
}
@Override
public String getSQLFieldType(final Field field) throws Exception {
final String fieldName = AnnotationTools.getFieldName(field);
try {
return DataFactory.convertTypeInSQL(String.class, fieldName);
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Override
public boolean isCompatibleField(final Field field) {
final SQLTableExternalForeinKeyAsList decorators = field
.getDeclaredAnnotation(SQLTableExternalForeinKeyAsList.class);
return decorators != null;
}
@Override
public void insertData(final PreparedStatement ps, final Field field, final Object rootObject, final CountInOut iii)
throws SQLException, IllegalArgumentException, IllegalAccessException {
final Object data = field.get(rootObject);
iii.inc();
if (data == null) {
ps.setNull(iii.value, Types.BIGINT);
} else {
@SuppressWarnings("unchecked")
final String dataTmp = getStringOfIds((List<Long>) data);
ps.setString(iii.value, dataTmp);
}
}
@Override
public boolean canInsert(final Field field) {
return true;
}
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return false;
}
@Override
public boolean canRetrieve(final Field field) {
return true;
}
@Override
public void generateQuery(
@NotNull final String tableName,
@NotNull final String primaryKey,
@NotNull final Field field,
@NotNull final StringBuilder querySelect,
@NotNull final StringBuilder query,
@NotNull final String name,
@NotNull final CountInOut count,
final QueryOptions options) {
count.inc();
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(name);
}
@Override
public void fillFromQuery(
final ResultSet rs,
final Field field,
final Object data,
final CountInOut count,
final QueryOptions options,
final List<LazyGetter> lazyCall) throws SQLException, IllegalArgumentException, IllegalAccessException {
final List<Long> idList = DataAccess.getListOfIds(rs, count.value, SEPARATOR);
field.set(data, idList);
count.inc();
}
@Override
public void createTables(
final String tableName,
final Field primaryField,
final Field field,
final StringBuilder mainTableBuilder,
final List<String> preActionList,
final List<String> postActionList,
final boolean createIfNotExist,
final boolean createDrop,
final int fieldId) throws Exception {
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, String.class);
}
}

View File

@ -3,12 +3,10 @@ package org.kar.archidata.dataAccess.addOn.model;
import java.util.List; import java.util.List;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericData;
import jakarta.persistence.Column;
import jakarta.persistence.Id; import jakarta.persistence.Id;
public class TableCoversLongLong extends GenericData { public class TableCoversLongLong {
public TableCoversLongLong() { public TableCoversLongLong() {
// nothing to do... // nothing to do...
} }
@ -18,11 +16,10 @@ public class TableCoversLongLong extends GenericData {
this.covers = covers; this.covers = covers;
} }
@Column(nullable = false)
@Id @Id
public Long id; public Long id;
@DataJson() @DataJson()
@Column(nullable = false)
public List<Long> covers; public List<Long> covers;
} }

View File

@ -1,29 +1,27 @@
package org.kar.archidata.dataAccess.addOn.model; package org.kar.archidata.dataAccess.addOn.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericData;
import jakarta.persistence.Column;
import jakarta.persistence.Id; import jakarta.persistence.Id;
public class TableCoversLongUUID extends GenericData { public class TableCoversLongUUID {
public TableCoversLongUUID() { public TableCoversLongUUID() {
// nothing to do... // nothing to do...
} }
public TableCoversLongUUID(final Long id, final List<UUID> covers) { public TableCoversLongUUID(final Long id, final List<UUID> covers) {
this.id = id; this.id = id;
this.covers = covers; this.covers = new ArrayList<>(covers);
} }
@Column(nullable = false)
@Id @Id
public Long id; public Long id;
@DataJson() @DataJson()
@Column(nullable = false)
public List<UUID> covers; public List<UUID> covers;
} }

View File

@ -1,29 +1,26 @@
package org.kar.archidata.dataAccess.addOn.model; package org.kar.archidata.dataAccess.addOn.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericData;
import jakarta.persistence.Column;
import jakarta.persistence.Id; import jakarta.persistence.Id;
public class TableCoversUUIDLong extends GenericData { public class TableCoversUUIDLong {
public TableCoversUUIDLong() { public TableCoversUUIDLong() {
// nothing to do... // nothing to do...
} }
public TableCoversUUIDLong(final UUID id, final List<Long> covers) { public TableCoversUUIDLong(final UUID uuid, final List<Long> covers) {
this.id = id; this.uuid = uuid;
this.covers = covers; this.covers = new ArrayList<>(covers);
} }
@Column(nullable = false)
@Id @Id
public UUID id; public UUID uuid;
@DataJson() @DataJson()
@Column(nullable = false)
public List<Long> covers; public List<Long> covers;
} }

View File

@ -4,26 +4,22 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericData;
import jakarta.persistence.Column;
import jakarta.persistence.Id; import jakarta.persistence.Id;
public class TableCoversUUIDUUID extends GenericData { public class TableCoversUUIDUUID {
public TableCoversUUIDUUID() { public TableCoversUUIDUUID() {
// nothing to do... // nothing to do...
} }
public TableCoversUUIDUUID(final UUID id, final List<UUID> covers) { public TableCoversUUIDUUID(final UUID uuid, final List<UUID> covers) {
this.id = id; this.uuid = uuid;
this.covers = covers; this.covers = covers;
} }
@Column(nullable = false)
@Id @Id
public UUID id; public UUID uuid;
@DataJson() @DataJson()
@Column(nullable = false)
public List<UUID> covers; public List<UUID> covers;
} }

View File

@ -3,18 +3,20 @@ package org.kar.archidata.dataAccess.options;
import java.util.List; import java.util.List;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
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 filterValue 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. */
void check(final String baseName, Object data, List<String> filterValue) throws Exception; void check(final String baseName, Object data, List<String> modifiedValue, final QueryOptions options)
throws Exception;
default void checkAll(final String baseName, final Object data) throws Exception { default void checkAll(final String baseName, final Object data, final QueryOptions options) throws Exception {
check(baseName, data, AnnotationTools.getAllFieldsNames(data.getClass())); check(baseName, data, AnnotationTools.getAllFieldsNames(data.getClass()), options);
} }
} }

View File

@ -2,10 +2,16 @@ package org.kar.archidata.dataAccess.options;
import java.util.List; import java.util.List;
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 class CheckFunctionVoid implements CheckFunctionInterface { public class CheckFunctionVoid implements CheckFunctionInterface {
@Override @Override
public void check(final String baseName, Object data, List<String> filterValue) { public void check(
final String baseName,
final Object data,
final List<String> filterValue,
final QueryOptions options) {
} }

View File

@ -16,6 +16,7 @@ import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.exception.DataAccessException; import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.exception.InputException; import org.kar.archidata.exception.InputException;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -34,10 +35,13 @@ public class CheckJPA<T> implements CheckFunctionInterface {
/** 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 CheckInterface<K> { public interface CheckInterface<K> {
/** 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 Base of the name input that is displayed in exception generated.
* @param data The object that might be injected. * @param data The object that might be injected.
* @param filterValue List of fields that might be check. If null, then all column must be checked. * @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. */ * @throws Exception Exception is generate if the data are incorrect. */
void check(final String baseName, final K data) throws Exception; void check(final String baseName, final K data, List<String> modifiedValue, final QueryOptions options)
throws Exception;
} }
protected Map<String, List<CheckInterface<T>>> checking = null; protected Map<String, List<CheckInterface<T>>> checking = null;
@ -66,124 +70,182 @@ public class CheckJPA<T> implements CheckFunctionInterface {
for (final Field field : this.clazz.getFields()) { for (final Field field : this.clazz.getFields()) {
final String fieldName = field.getName(); // AnnotationTools.getFieldName(field); final String fieldName = field.getName(); // AnnotationTools.getFieldName(field);
if (AnnotationTools.isPrimaryKey(field)) { if (AnnotationTools.isPrimaryKey(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
throw new InputException(baseName + fieldName, (
"This is a '@Id' (primaryKey) ==> can not be change"); 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)) { if (AnnotationTools.getConstraintsNotNull(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
if (field.get(data) == null) { (
throw new InputException(baseName + fieldName, "Can not be null"); 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)) { if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
throw new InputException(baseName + fieldName, "It is forbidden to change this field"); (
}); 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(); final Class<?> type = field.getType();
if (type == Long.class || type == long.class) { if (type == Long.class || type == long.class) {
final Long maxValue = AnnotationTools.getConstraintsMax(field); final Long maxValue = AnnotationTools.getConstraintsMax(field);
if (maxValue != null) { if (maxValue != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Long elemTyped = (Long) elem; final QueryOptions options) -> {
if (elemTyped > maxValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); 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); final Long minValue = AnnotationTools.getConstraintsMin(field);
if (minValue != null) { if (minValue != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Long elemTyped = (Long) elem; final QueryOptions options) -> {
if (elemTyped < minValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); 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); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem); final QueryOptions options) -> {
if (count == 0) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"Foreign element does not exist in the DB:" + elem); 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) { } else if (type == Integer.class || type == int.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final int maxValue = maxValueRoot.intValue(); final int maxValue = maxValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Integer elemTyped = (Integer) elem; final QueryOptions options) -> {
if (elemTyped > maxValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); 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); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final int minValue = minValueRoot.intValue(); final int minValue = minValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Integer elemTyped = (Integer) elem; final QueryOptions options) -> {
if (elemTyped < minValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); 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); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem); final QueryOptions options) -> {
if (count == 0) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"Foreign element does not exist in the DB:" + elem); 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) { } else if (type == UUID.class) {
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem); final QueryOptions options) -> {
if (count == 0) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"Foreign element does not exist in the DB:" + elem); 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 == Boolean.class || type == boolean.class) {
@ -191,59 +253,83 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final float maxValue = maxValueRoot.floatValue(); final float maxValue = maxValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Float elemTyped = (Float) elem; final QueryOptions options) -> {
if (elemTyped > maxValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); 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); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final float minValue = minValueRoot.floatValue(); final float minValue = minValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Float elemTyped = (Float) elem; final QueryOptions options) -> {
if (elemTyped < minValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); 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) { } else if (type == Double.class || type == double.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final double maxValue = maxValueRoot.doubleValue(); final double maxValue = maxValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Double elemTyped = (Double) elem; final QueryOptions options) -> {
if (elemTyped > maxValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue); 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); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final double minValue = minValueRoot.doubleValue(); final double minValue = minValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final Double elemTyped = (Double) elem; final QueryOptions options) -> {
if (elemTyped < minValue) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue); 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 == Date.class || type == Timestamp.class) {
@ -254,51 +340,87 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} else if (type == String.class) { } else if (type == String.class) {
final int maxSizeString = AnnotationTools.getLimitSize(field); final int maxSizeString = AnnotationTools.getLimitSize(field);
if (maxSizeString > 0) { if (maxSizeString > 0) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final String elemTyped = (String) elem; final QueryOptions options) -> {
if (elemTyped.length() > maxSizeString) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"Too long size must be <= " + maxSizeString); 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); final Size limitSize = AnnotationTools.getConstraintsSize(field);
if (limitSize != null) { if (limitSize != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final String elemTyped = (String) elem; final QueryOptions options) -> {
if (elemTyped.length() > limitSize.max()) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"Too long size (constraints) must be <= " + limitSize.max()); return;
} }
if (elemTyped.length() < limitSize.min()) { final String elemTyped = (String) elem;
throw new InputException(baseName + fieldName, if (elemTyped.length() > limitSize.max()) {
"Too small size (constraints) must be >= " + 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); final String patternString = AnnotationTools.getConstraintsPattern(field);
if (patternString != null) { if (patternString != null) {
final Pattern pattern = Pattern.compile(patternString); final Pattern pattern = Pattern.compile(patternString);
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object elem = field.get(data); (
if (elem == null) { final String baseName,
return; final T data,
} final List<String> modifiedValue,
final String elemTyped = (String) elem; final QueryOptions options) -> {
if (!pattern.matcher(elemTyped).find()) { final Object elem = field.get(data);
throw new InputException(baseName + fieldName, if (elem == null) {
"does not match the required pattern (constraints) must be '" + patternString 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 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) { } else if (type == JsonValue.class) {
final DataJson jsonAnnotation = AnnotationTools.getDataJson(field); final DataJson jsonAnnotation = AnnotationTools.getDataJson(field);
@ -306,9 +428,14 @@ public class CheckJPA<T> implements CheckFunctionInterface {
// Here if we have an error it crash at start and no new instance after creation... // Here if we have an error it crash at start and no new instance after creation...
final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor() final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor()
.newInstance(); .newInstance();
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
instance.checkAll(baseName + fieldName + ".", field.get(data)); (
}); final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
instance.checkAll(baseName + fieldName + ".", field.get(data), options);
});
} }
} else if (type.isEnum()) { } else if (type.isEnum()) {
// nothing to do. // nothing to do.
@ -316,13 +443,26 @@ public class CheckJPA<T> implements CheckFunctionInterface {
// keep this is last ==> take more time... // keep this is last ==> take more time...
if (AnnotationTools.isUnique(field)) { if (AnnotationTools.isUnique(field)) {
// Create the request ... // Create the request ...
add(fieldName, (final String baseName, final T data) -> { add(fieldName,
final Object other = DataAccess.getWhere(this.clazz, (
new Condition(new QueryCondition(fieldName, "==", field.get(data)))); final String baseName,
if (other != null) { final T data,
throw new InputException(baseName + fieldName, "Name already exist in the DB"); 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");
}
});
} }
} }
@ -333,7 +473,11 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
@Override @Override
public void check(final String baseName, final Object data, final List<String> filterValue) throws Exception { public void check(
final String baseName,
final Object data,
final List<String> modifiedValue,
final QueryOptions options) throws Exception {
if (this.checking == null) { if (this.checking == null) {
initialize(); initialize();
} }
@ -342,19 +486,20 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final T dataCasted = (T) data; final T dataCasted = (T) data;
for (final String filter : filterValue) { for (final String filter : modifiedValue) {
final List<CheckInterface<T>> actions = this.checking.get(filter); final List<CheckInterface<T>> actions = this.checking.get(filter);
if (actions == null) { if (actions == null) {
continue; continue;
} }
for (final CheckInterface<T> action : actions) { for (final CheckInterface<T> action : actions) {
action.check(baseName, dataCasted); action.check(baseName, dataCasted, modifiedValue, options);
} }
} }
checkTyped(dataCasted, filterValue); checkTyped(dataCasted, modifiedValue, options);
} }
public void checkTyped(final T data, final List<String> filterValue) throws Exception { public void checkTyped(final T data, final List<String> modifiedValue, final QueryOptions options)
throws Exception {
// nothing to do ... // nothing to do ...
} }
} }

View File

@ -0,0 +1,25 @@
package org.kar.archidata.dataAccess.options;
import org.kar.archidata.dataAccess.QueryItem;
/** Condition model apply to the check models. */
public class ConditionChecker extends QueryOption {
public final QueryItem condition;
public ConditionChecker(final QueryItem items) {
this.condition = items;
}
public ConditionChecker(final Condition cond) {
this.condition = cond.condition;
}
public ConditionChecker() {
this.condition = null;
}
public Condition toCondition() {
return new Condition(this.condition);
}
}

View File

@ -36,6 +36,9 @@ public class DBInterfaceOption extends QueryOption {
} }
public static DBEntry getAutoEntry(final QueryOptions options) throws IOException { public static DBEntry getAutoEntry(final QueryOptions options) throws IOException {
if (options == null) {
return DBEntry.createInterface(GlobalConfiguration.dbConfig, false);
}
final List<DBInterfaceOption> dbOption = options.get(DBInterfaceOption.class); final List<DBInterfaceOption> dbOption = options.get(DBInterfaceOption.class);
if (dbOption.size() == 0) { if (dbOption.size() == 0) {
final List<DBInterfaceRoot> isRoot = options.get(DBInterfaceRoot.class); final List<DBInterfaceRoot> isRoot = options.get(DBInterfaceRoot.class);

View File

@ -63,6 +63,7 @@ public class DBEntry implements Closeable {
this.connection = DriverManager.getConnection(this.config.getUrl(), this.config.getLogin(), this.connection = DriverManager.getConnection(this.config.getUrl(), this.config.getLogin(),
this.config.getPassword()); this.config.getPassword());
} catch (final SQLException ex) { } catch (final SQLException ex) {
LOGGER.error("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true));
throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true)); throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true));
} }

View File

@ -1,19 +1,34 @@
package org.kar.archidata.exception; package org.kar.archidata.exception;
import org.kar.archidata.api.DataResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
public class FailException extends Exception { public class FailException extends Exception {
private static final Logger LOGGER = LoggerFactory.getLogger(DataResource.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public final Response.Status status; public final Response.Status status;
public final Exception exception;
public FailException(final Response.Status status, final String message) { public FailException(final Response.Status status, final String message) {
super(message); super(message);
this.status = status; this.status = status;
this.exception = null;
}
public FailException(final Response.Status status, final String message, final Exception ex) {
super(message);
this.status = status;
this.exception = ex;
ex.printStackTrace();
LOGGER.error("Generate Fail exception with exceptionData: {}", ex.getMessage());
} }
public FailException(final String message) { public FailException(final String message) {
super(message); super(message);
this.status = Response.Status.BAD_REQUEST; this.status = Response.Status.BAD_REQUEST;
this.exception = null;
} }
} }

View File

@ -0,0 +1,95 @@
package org.kar.archidata.externalRestApi;
import java.util.ArrayList;
import java.util.List;
import org.kar.archidata.externalRestApi.model.ApiGroupModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.model.ModelGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnalyzeApi {
static final Logger LOGGER = LoggerFactory.getLogger(AnalyzeApi.class);
protected final List<ApiGroupModel> apiModels = new ArrayList<>();
protected final ModelGroup modelGroup = new ModelGroup();
public void addAllModel(final List<Class<?>> classes) throws Exception {
this.modelGroup.addAll(classes);
analyzeModels();
}
public void addModel(final Class<?> clazz) throws Exception {
this.modelGroup.add(clazz);
analyzeModels();
}
public void addApi(final Class<?> clazz) throws Exception {
this.apiModels.add(new ApiGroupModel(clazz, this.modelGroup));
analyzeModels();
}
public void addAllApi(final List<Class<?>> classes) throws Exception {
for (final Class<?> clazz : classes) {
this.apiModels.add(new ApiGroupModel(clazz, this.modelGroup));
}
analyzeModels();
}
public List<ApiGroupModel> getAllApi() {
return this.apiModels;
}
public List<ClassModel> getAllModel() {
return this.modelGroup.getModels();
}
private void analyzeModels() throws Exception {
final List<ClassModel> dones = new ArrayList<>();
while (dones.size() < getAllModel().size()) {
final List<ClassModel> copyList = new ArrayList<>(this.modelGroup.getModels());
for (final ClassModel model : copyList) {
if (dones.contains(model)) {
continue;
}
LOGGER.info("Analyze: {}", model);
model.analyze(this.modelGroup);
dones.add(model);
}
}
}
public List<ClassModel> getCompatibleModels(final List<Class<?>> search) {
final List<ClassModel> out = new ArrayList<>();
for (final ClassModel model : getAllModel()) {
if (search.contains(model.getOriginClasses())) {
out.add(model);
}
}
if (out.isEmpty()) {
return null;
}
return out;
}
public void displayAllApi() {
LOGGER.info("List all API:");
for (final ApiGroupModel model : getAllApi()) {
LOGGER.info(" - {}: {}", model.name, model.getClass().getCanonicalName());
}
}
public void displayAllModel() {
LOGGER.info("List all Model:");
for (final ClassModel model : getAllModel()) {
final StringBuilder out = new StringBuilder();
for (final ClassModel classModel : model.getAlls()) {
out.append(classModel.getOriginClasses().getCanonicalName());
out.append(",");
}
LOGGER.info(" - {}", out.toString());
}
}
}

View File

@ -0,0 +1,232 @@
package org.kar.archidata.externalRestApi;
import java.io.FileWriter;
import java.io.InputStream;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.externalRestApi.dot.DotApiGeneration;
import org.kar.archidata.externalRestApi.dot.DotClassElement;
import org.kar.archidata.externalRestApi.dot.DotClassElement.DefinedPosition;
import org.kar.archidata.externalRestApi.dot.DotClassElementGroup;
import org.kar.archidata.externalRestApi.model.ApiGroupModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
public class DotGenerateApi {
public static void generateApi(final AnalyzeApi api, final String pathDotFile) throws Exception {
final List<DotClassElement> localModel = generateApiModel(api);
final DotClassElementGroup dotGroup = new DotClassElementGroup(localModel);
try (final FileWriter myWriter = new FileWriter(pathDotFile)) {
myWriter.write("""
# Architecture auto-generated file
digraph UML_Class_diagram {
#rankdir=NS;
graph [
pad="0.5"
nodesep="1"
#ranksep="2"
label="Rest API server Model"
labelloc="t"
fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif"
]
node [
fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif"
shape=record
style=filled
fillcolor=gray95
]
edge [fontname="FreeMono,Sans-Mono,Helvetica,Arial,sans-serif"]
""");
/*
myWriter.write("""
subgraph REST_API {
style=filled;
color=lightgrey;
label="REST API";
rankdir=LR;
""");
*/
for (final ApiGroupModel element : api.apiModels) {
final String tmp = DotApiGeneration.generateApiFile(element, dotGroup);
myWriter.write(tmp);
myWriter.write("\n");
}
// create an invisible link to force all element to be link together:
if (false) {
String previous = null;
for (final ApiGroupModel element : api.apiModels) {
if (previous == null) {
previous = element.name;
continue;
}
myWriter.write("\t{ ");
myWriter.write(previous);
myWriter.write(":s -> ");
previous = element.name;
myWriter.write(previous);
myWriter.write(":n [style=invis]}\n");
}
}
/*
myWriter.write("""
}
""");
myWriter.write("""
subgraph Models {
style=filled;
color=lightgrey;
label="Models";
rankdir=NS;
""");
*/
// Generates all MODEL files
for (final DotClassElement element : localModel) {
final String tmp = element.generateFile(dotGroup);
myWriter.write(tmp);
myWriter.write("\n");
}
/*
myWriter.write("""
}
""");
*/
myWriter.write("""
}
""");
}
}
private static List<DotClassElement> generateApiModel(final AnalyzeApi api) throws Exception {
// First step is to add all specific basic elements the wrap correctly the model
final List<DotClassElement> dotModels = new ArrayList<>();
List<ClassModel> models = api.getCompatibleModels(List.of(Void.class, void.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "void", "void", null, null, DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(Object.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Object", "object", null, "Object", DefinedPosition.NATIVE));
}
// Map is binded to any ==> can not determine this complex model for now
models = api.getCompatibleModels(List.of(Map.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Object", "any", null, null, DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(String.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "String", "string", null, "String", DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(
List.of(InputStream.class, FormDataContentDisposition.class, ContentDisposition.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "File", "File", null, "File", DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(Boolean.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Boolean", "boolean", null, "Boolean", DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(boolean.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "boolean", "boolean", null, "boolean", DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(UUID.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "UUID", "UUID", "isUUID", "UUID", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(long.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "long", "Long", "isLong", "long", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Long.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Long", "Long", "isLong", "Long", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(short.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "short", "Short", "isShort", "short", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Short.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Short", "Short", "isShort", "Short", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(int.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "int", "Integer", "isInteger", "int", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Integer.class));
if (models != null) {
dotModels.add(
new DotClassElement(models, "Integer", "Integer", "isInteger", "Integer", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(double.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Double", "Double", "isDouble", "double", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Double.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Double", "Double", "isDouble", "Double", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(float.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "float", "Float", "isFloat", "float", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Float.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Float", "Float", "isFloat", "Float", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Instant.class));
if (models != null) {
dotModels.add(
new DotClassElement(models, "Instant", "Instant", "isInstant", "Instant", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Date.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Date", "IsoDate", "isIsoDate", "Date", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Timestamp.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "Timestamp", "Timestamp", "isTimestamp", "Timestamp",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(LocalDate.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "LocalDate", "LocalDate", "isLocalDate", "LocalDate",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(LocalTime.class));
if (models != null) {
dotModels.add(new DotClassElement(models, "LocalTime", "LocalTime", "isLocalTime", "LocalTime",
DefinedPosition.BASIC));
}
// needed for Rest interface
api.addModel(RestErrorResponse.class);
for (final ClassModel model : api.getAllModel()) {
boolean alreadyExist = false;
for (final DotClassElement elem : dotModels) {
if (elem.isCompatible(model)) {
alreadyExist = true;
break;
}
}
if (alreadyExist) {
continue;
}
dotModels.add(new DotClassElement(model));
}
return dotModels;
}
}

View File

@ -0,0 +1,8 @@
package org.kar.archidata.externalRestApi;
public class PythonGenerateApi {
public static void generateApi(final AnalyzeApi api) {
}
}

View File

@ -0,0 +1,242 @@
package org.kar.archidata.externalRestApi;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.externalRestApi.model.ApiGroupModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.typescript.TsApiGeneration;
import org.kar.archidata.externalRestApi.typescript.TsClassElement;
import org.kar.archidata.externalRestApi.typescript.TsClassElement.DefinedPosition;
import org.kar.archidata.externalRestApi.typescript.TsClassElementGroup;
public class TsGenerateApi {
/**
* Generate a full API tree for Typescript in a specific folder.
* This generate a folder containing a full API with "model" folder and "api" folder.
* The generation depend of Zod and can be strict compile.
* @param api Data model to generate the api
* @param pathPackage Path to store the api.
*/
public static void generateApi(final AnalyzeApi api, final String pathPackage) throws Exception {
final List<TsClassElement> localModel = generateApiModel(api);
final TsClassElementGroup tsGroup = new TsClassElementGroup(localModel);
// Generates all MODEL files
for (final TsClassElement element : localModel) {
element.generateFile(pathPackage, tsGroup);
}
// Generate index of model files
createModelIndex(pathPackage, tsGroup);
for (final ApiGroupModel element : api.apiModels) {
TsApiGeneration.generateApiFile(element, pathPackage, tsGroup);
}
// Generate index of model files
createResourceIndex(pathPackage, api.apiModels);
createIndex(pathPackage);
copyResourceFile("rest-tools.ts", pathPackage + File.separator + "rest-tools.ts");
}
private static void createIndex(final String pathPackage) throws IOException {
final String out = """
/**
* Interface of the server (auto-generated code)
*/
export * from \"./model\";
export * from \"./api\";
export * from \"./rest-tools\";
""";
final FileWriter myWriter = new FileWriter(pathPackage + File.separator + "index.ts");
myWriter.write(out);
myWriter.close();
}
private static void createResourceIndex(final String pathPackage, final List<ApiGroupModel> apiModels)
throws IOException {
final StringBuilder out = new StringBuilder("""
/**
* Interface of the server (auto-generated code)
*/
""");
final List<String> files = new ArrayList<>();
for (final ApiGroupModel elem : apiModels) {
files.add(TsClassElement.determineFileName(elem.name));
}
Collections.sort(files);
for (final String elem : files) {
out.append("export * from \"./");
out.append(elem);
out.append("\"\n");
}
final FileWriter myWriter = new FileWriter(pathPackage + File.separator + "api" + File.separator + "index.ts");
myWriter.write(out.toString());
myWriter.close();
}
private static void createModelIndex(final String pathPackage, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder("""
/**
* Interface of the server (auto-generated code)
*/
""");
final List<String> files = new ArrayList<>();
for (final TsClassElement elem : tsGroup.getTsElements()) {
if (elem.nativeType == DefinedPosition.NATIVE) {
continue;
}
files.add(elem.fileName);
}
Collections.sort(files);
for (final String elem : files) {
out.append("export * from \"./");
out.append(elem);
out.append("\"\n");
}
final FileWriter myWriter = new FileWriter(
pathPackage + File.separator + "model" + File.separator + "index.ts");
myWriter.write(out.toString());
myWriter.close();
}
private static List<TsClassElement> generateApiModel(final AnalyzeApi api) throws Exception {
// needed for Rest interface
api.addModel(RestErrorResponse.class);
// First step is to add all specific basic elements the wrap correctly the model
final List<TsClassElement> tsModels = new ArrayList<>();
List<ClassModel> models = api.getCompatibleModels(List.of(Void.class, void.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "void", "void", null, null, DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(Object.class));
if (models != null) {
tsModels.add(
new TsClassElement(models, "zod.any()", "object", null, "zod.object()", DefinedPosition.NATIVE));
}
// Map is binded to any ==> can not determine this complex model for now
models = api.getCompatibleModels(List.of(Map.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "zod.any()", "any", null, null, DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(String.class));
if (models != null) {
tsModels.add(
new TsClassElement(models, "zod.string()", "string", null, "zod.string()", DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(
List.of(InputStream.class, FormDataContentDisposition.class, ContentDisposition.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "z.instanceof(File)", "File", null, "z.instanceof(File)",
DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(Boolean.class, boolean.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "zod.boolean()", "boolean", null, "zod.boolean()",
DefinedPosition.NATIVE));
}
models = api.getCompatibleModels(List.of(UUID.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodUUID", "UUID", "isUUID", "zod.string().uuid()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Long.class, long.class));
if (models != null) {
tsModels.add(
new TsClassElement(models, "ZodLong", "Long", "isLong", "zod.number()", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Short.class, short.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodShort", "Short", "isShort", "zod.number().safe()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Integer.class, int.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodInteger", "Integer", "isInteger", "zod.number().safe()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Double.class, double.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodDouble", "Double", "isDouble", "zod.number()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Float.class, float.class));
if (models != null) {
tsModels.add(
new TsClassElement(models, "ZodFloat", "Float", "isFloat", "zod.number()", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Instant.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodInstant", "Instant", "isInstant", "zod.string()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Date.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodIsoDate", "IsoDate", "isIsoDate",
"zod.string().datetime({ precision: 3 })", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(Timestamp.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodTimestamp", "Timestamp", "isTimestamp",
"zod.string().datetime({ precision: 3 })", DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(LocalDate.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodLocalDate", "LocalDate", "isLocalDate", "zod.string().date()",
DefinedPosition.BASIC));
}
models = api.getCompatibleModels(List.of(LocalTime.class));
if (models != null) {
tsModels.add(new TsClassElement(models, "ZodLocalTime", "LocalTime", "isLocalTime", "zod.string().time()",
DefinedPosition.BASIC));
}
for (final ClassModel model : api.getAllModel()) {
boolean alreadyExist = false;
for (final TsClassElement elem : tsModels) {
if (elem.isCompatible(model)) {
alreadyExist = true;
break;
}
}
if (alreadyExist) {
continue;
}
tsModels.add(new TsClassElement(model));
}
return tsModels;
}
public static void copyResourceFile(final String name, final String destinationPath) throws IOException {
final InputStream ioStream = TsGenerateApi.class.getClassLoader().getResourceAsStream(name);
if (ioStream == null) {
throw new IllegalArgumentException("rest-tools.ts is not found");
}
final BufferedReader buffer = new BufferedReader(new InputStreamReader(ioStream));
final FileWriter myWriter = new FileWriter(destinationPath);
String line;
while ((line = buffer.readLine()) != null) {
myWriter.write(line);
myWriter.write("\n");
}
ioStream.close();
myWriter.close();
}
}

View File

@ -0,0 +1,415 @@
package org.kar.archidata.externalRestApi.dot;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.kar.archidata.externalRestApi.dot.DotClassElement.DefinedPosition;
import org.kar.archidata.externalRestApi.model.ApiGroupModel;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.kar.archidata.externalRestApi.model.ApiModel.OptionalClassModel;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DotApiGeneration {
static final Logger LOGGER = LoggerFactory.getLogger(DotApiGeneration.class);
public static String generateClassEnumModelTypescript(
final ClassEnumModel model,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
imports.add(model);
final DotClassElement dotModel = dotGroup.find(model);
return dotModel.dotTypeName;
}
public static String generateClassObjectModelTypescript(
final ClassObjectModel model,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
final DotClassElement dotModel = dotGroup.find(model);
if (dotModel.nativeType != DefinedPosition.NATIVE) {
imports.add(model);
}
if (dotModel.nativeType != DefinedPosition.NORMAL) {
return dotModel.dotTypeName;
}
return dotModel.dotTypeName;
}
public static String generateClassMapModelTypescript(
final ClassMapModel model,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
final StringBuilder out = new StringBuilder();
out.append("Map&lt;");
out.append(generateClassModelTypescript(model.keyModel, dotGroup, imports));
out.append(", ");
out.append(generateClassModelTypescript(model.valueModel, dotGroup, imports));
out.append("&gt;");
return out.toString();
}
public static String generateClassListModelTypescript(
final ClassListModel model,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
final StringBuilder out = new StringBuilder();
out.append("List&lt;");
out.append(generateClassModelTypescript(model.valueModel, dotGroup, imports));
out.append("&gt;");
return out.toString();
}
public static String generateClassModelTypescript(
final ClassModel model,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
if (model instanceof final ClassObjectModel objectModel) {
return generateClassObjectModelTypescript(objectModel, dotGroup, imports);
}
if (model instanceof final ClassListModel listModel) {
return generateClassListModelTypescript(listModel, dotGroup, imports);
}
if (model instanceof final ClassMapModel mapModel) {
return generateClassMapModelTypescript(mapModel, dotGroup, imports);
}
if (model instanceof final ClassEnumModel enumModel) {
return generateClassEnumModelTypescript(enumModel, dotGroup, imports);
}
throw new IOException("Impossible model:" + model);
}
public static String generateClassModelsTypescript(
final List<ClassModel> models,
final DotClassElementGroup dotGroup) throws IOException {
if (models.size() == 0) {
return "void";
}
final StringBuilder out = new StringBuilder();
if (models.size() > 1) {
out.append("Union&lt;");
}
boolean isFirst = true;
for (final ClassModel model : models) {
if (isFirst) {
isFirst = false;
} else {
out.append(", ");
}
final String data = DotClassElement.generateClassModelTypescript(model, dotGroup);
out.append(data);
}
if (models.size() > 1) {
out.append("&gt;");
}
return out.toString();
}
public static List<String> generateClassModelsLinks(
final List<ClassModel> models,
final DotClassElementGroup dotGroup) throws IOException {
if (models.size() == 0) {
return null;
}
final List<String> out = new ArrayList<>();
final boolean isFirst = true;
for (final ClassModel model : models) {
final String data = DotClassElement.generateClassModelTypescriptLink(model, dotGroup);
if (data != null) {
out.add(data);
}
}
return out;
}
public static String capitalizeFirstLetter(final String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
public static String generateApiFile(final ApiGroupModel element, final DotClassElementGroup dotGroup)
throws IOException {
final StringBuilder data = new StringBuilder();
final StringBuilder outLinks = new StringBuilder();
data.append("""
%s [
shape=plain
label=<<table color="#FF3333" border="2" cellborder="1" cellspacing="0" cellpadding="4">
<tr>
<td><b>%s</b><br/>(REST)</td>
</tr>
<tr>
<td>
<table border="0" cellborder="0" cellspacing="0" >
""".formatted(element.name, element.name));
final Set<ClassModel> imports = new HashSet<>();
final Set<ClassModel> zodImports = new HashSet<>();
final Set<ClassModel> isImports = new HashSet<>();
final Set<ClassModel> writeImports = new HashSet<>();
final Set<String> toolImports = new HashSet<>();
for (final ApiModel interfaceElement : element.interfaces) {
final List<String> consumes = interfaceElement.consumes;
final List<String> produces = interfaceElement.produces;
final boolean needGenerateProgress = interfaceElement.needGenerateProgress;
/*
if (returnComplexModel != null) {
data.append(returnComplexModel.replaceAll("(?m)^", "\t"));
for (final ClassModel elem : interfaceElement.returnTypes) {
zodImports.addAll(elem.getDependencyGroupModels());
}
}
*/
data.append("\t\t\t\t\t<tr><td align=\"left\" port=\"");
data.append(interfaceElement.name);
data.append("\"><b> + ");
data.append(interfaceElement.name);
data.append("(");
boolean hasParam = false;
/*
if (!interfaceElement.queries.isEmpty()) {
data.append("\n\t\tqueries: {");
for (final Entry<String, List<ClassModel>> queryEntry : interfaceElement.queries.entrySet()) {
data.append("\n\t\t\t");
data.append(queryEntry.getKey());
data.append("?: ");
data.append(generateClassModelsTypescript(queryEntry.getValue(), dotGroup, imports, false));
data.append(",");
}
data.append("\n\t\t},");
}
*/
/* fonctionnel mais trop de donnée
if (!interfaceElement.parameters.isEmpty()) {
//data.append("params: {");
for (final Entry<String, List<ClassModel>> paramEntry : interfaceElement.parameters.entrySet()) {
data.append("");
data.append(paramEntry.getKey());
data.append(": ");
data.append(generateClassModelsTypescript(paramEntry.getValue(), dotGroup, imports, false));
data.append(",");
}
//data.append("},");
}
*/
if (interfaceElement.unnamedElement.size() == 1) {
if (hasParam) {
data.append(", ");
}
hasParam = true;
data.append("data: ");
data.append(
generateClassModelTypescript(interfaceElement.unnamedElement.get(0), dotGroup, writeImports));
} else if (interfaceElement.multiPartParameters.size() != 0) {
if (hasParam) {
data.append(", ");
}
hasParam = true;
boolean hasParam2 = false;
data.append("data: {");
for (final Entry<String, OptionalClassModel> pathEntry : interfaceElement.multiPartParameters
.entrySet()) {
if (hasParam2) {
data.append(", ");
}
hasParam2 = true;
data.append(pathEntry.getKey());
data.append(": ");
data.append(generateClassModelsTypescript(pathEntry.getValue().model(), dotGroup));
}
data.append("}");
}
data.append("): ");
/*
String tmp = DotClassElement.generateLocalModel(
final String ModelName,
final List<ClassModel> models,
final DotClassElementGroup dotGroup)
public static String generateClassModelsTypescript(
final List<ClassModel> models,
final DotClassElementGroup dotGroup,
final Set<ClassModel> imports) throws IOException {
*/
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, dotGroup);
data.append(returnType);
final List<String> returnLinks = generateClassModelsLinks(interfaceElement.returnTypes, dotGroup);
for (final String link : returnLinks) {
outLinks.append("\t");
outLinks.append(element.name);
outLinks.append(":");
outLinks.append(interfaceElement.name);
outLinks.append(":e -> ");
outLinks.append(link);
outLinks.append(":NAME:w\n");
}
data.append("</b>");
//data.append("<br align=\"left\"/>&nbsp;&nbsp;&nbsp;&nbsp;");
data.append("</td></tr>\n\t\t\t\t\t\t\t<tr><td align=\"left\"> ");
/*
data.append("\n\t\t\trestModel: {");
data.append("\n\t\t\t\tendPoint: \"");
*/
data.append(interfaceElement.restEndPoint);
/*
data.append("\",");
data.append("\n\t\t\t\trequestType: HTTPRequestModel.");
toolImports.add("HTTPRequestModel");
data.append(interfaceElement.restTypeRequest);
data.append(",");
if (consumes != null) {
for (final String elem : consumes) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.JSON,");
toolImports.add("HTTPMimeType");
break;
} else if (MediaType.MULTIPART_FORM_DATA.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.MULTIPART,");
toolImports.add("HTTPMimeType");
break;
} else if (MediaType.TEXT_PLAIN.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
toolImports.add("HTTPMimeType");
break;
}
}
} else if (RestTypeRequest.DELETE.equals(interfaceElement.restTypeRequest)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
toolImports.add("HTTPMimeType");
}
if (produces != null) {
if (produces.size() > 1) {
data.append("\n\t\t\t\taccept: produce,");
} else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, dotGroup,
imports, false);
if (!"void".equals(returnType)) {
for (final String elem : produces) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
data.append("\n\t\t\t\taccept: HTTPMimeType.JSON,");
toolImports.add("HTTPMimeType");
break;
}
}
}
}
}
data.append("\n\t\t\t},");
data.append("\n\t\t\trestConfig,");
if (!interfaceElement.parameters.isEmpty()) {
data.append("\n\t\t\tparams,");
}
if (!interfaceElement.queries.isEmpty()) {
data.append("\n\t\t\tqueries,");
}
if (interfaceElement.unnamedElement.size() == 1) {
data.append("\n\t\t\tdata,");
} else if (interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\t\tdata,");
}
if (needGenerateProgress) {
data.append("\n\t\t\tcallback,");
}
data.append("\n\t\t}");
if (returnComplexModel != null) {
data.append(", is");
data.append(returnModelNameIfComplex);
} else {
final DotClassElement retType = dotGroup.find(interfaceElement.returnTypes.get(0));
if (retType.dotCheckType != null) {
data.append(", ");
data.append(retType.dotCheckType);
imports.add(interfaceElement.returnTypes.get(0));
}
}
*/
data.append("</td></tr>\n");
}
/*
data.append("\n}\n");
final StringBuilder out = new StringBuilder();
final List<String> toolImportsList = new ArrayList<>(toolImports);
Collections.sort(toolImportsList);
if (toolImportsList.size() != 0) {
out.append("import {");
for (final String elem : toolImportsList) {
out.append("\n\t");
out.append(elem);
out.append(",");
}
out.append("\n} from \"../rest-tools\";\n\n");
}
if (zodImports.size() != 0) {
out.append("import { z as zod } from \"zod\"\n");
}
final Set<String> finalImportSet = new TreeSet<>();
for (final ClassModel model : imports) {
final DotClassElement dotModel = dotGroup.find(model);
if (dotModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
finalImportSet.add(dotModel.dotTypeName);
}
for (final ClassModel model : isImports) {
final DotClassElement dotModel = dotGroup.find(model);
if (dotModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
if (dotModel.dotCheckType != null) {
finalImportSet.add(dotModel.dotCheckType);
}
}
for (final ClassModel model : zodImports) {
final DotClassElement dotModel = dotGroup.find(model);
if (dotModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
finalImportSet.add("Zod" + dotModel.dotTypeName);
}
for (final ClassModel model : writeImports) {
final DotClassElement dotModel = dotGroup.find(model);
if (dotModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
finalImportSet.add(dotModel.dotTypeName + "Write");
}
if (finalImportSet.size() != 0) {
out.append("import {");
for (final String elem : finalImportSet) {
out.append("\n\t");
out.append(elem);
out.append(",");
}
out.append("\n} from \"../model\";\n\n");
}
out.append(data.toString());
*/
data.append("""
</table>
</td>
</tr>
</table>>
]
""");
data.append(outLinks.toString());
return data.toString();
}
}

View File

@ -0,0 +1,485 @@
package org.kar.archidata.externalRestApi.dot;
import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel.FieldProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DotClassElement {
static final Logger LOGGER = LoggerFactory.getLogger(DotClassElement.class);
public enum DefinedPosition {
NATIVE, // Native element of dot language.
BASIC, // basic wrapping for JAVA type.
NORMAL // Normal Object to interpret.
}
public List<ClassModel> models;
public String zodName;
public String dotTypeName;
public String dotCheckType;
public String declaration;
public String fileName = null;
public String comment = null;
public DefinedPosition nativeType = DefinedPosition.NORMAL;
public static String determineFileName(final String className) {
return className.replaceAll("([a-z])([A-Z])", "$1-$2").replaceAll("([A-Z])([A-Z][a-z])", "$1-$2").toLowerCase();
}
public DotClassElement(final List<ClassModel> model, final String zodName, final String dotTypeName,
final String dotCheckType, final String declaration, final DefinedPosition nativeType) {
this.models = model;
this.zodName = zodName;
this.dotTypeName = dotTypeName;
this.declaration = declaration;
this.nativeType = nativeType;
}
public DotClassElement(final ClassModel model) {
this.models = List.of(model);
this.dotTypeName = model.getOriginClasses().getSimpleName();
this.declaration = null;
}
public boolean isCompatible(final ClassModel model) {
return this.models.contains(model);
}
public String generateEnum(final ClassEnumModel model, final DotClassElementGroup dotGroup) throws IOException {
final StringBuilder out = new StringBuilder();
out.append("""
%s [
shape=plain
label=<<table color="#33FF33" border="2" cellborder="1" cellspacing="0" cellpadding="4">
<tr>
<td port="NAME"><b>%s</b><br/>(ENUM)</td>
</tr>
<tr>
<td>
<table border="0" cellborder="0" cellspacing="0" >
""".formatted(this.dotTypeName, this.dotTypeName));
final boolean first = true;
for (final Entry<String, Object> elem : model.getListOfValues().entrySet()) {
out.append("\t\t\t\t\t\t<tr><td align=\"left\"><b> + ");
out.append(elem.getKey());
out.append("</b> = ");
if (elem.getValue() instanceof final Integer value) {
out.append(value);
} else {
out.append("'");
out.append(elem.getValue());
out.append("'");
}
out.append("</td></tr>\n");
}
out.append("""
</table>
</td>
</tr>
</table>>
]
""");
return out.toString();
}
public String generateImporDot(final List<ClassModel> depModels, final DotClassElementGroup dotGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
for (final ClassModel depModel : depModels) {
final DotClassElement dotModel = dotGroup.find(depModel);
if (dotModel.nativeType != DefinedPosition.NATIVE) {
out.append("import {");
out.append(dotModel.zodName);
out.append("} from \"./");
out.append(dotModel.fileName);
out.append("\";\n");
}
}
return out.toString();
}
private Object generateComment(final ClassObjectModel model) {
final StringBuilder out = new StringBuilder();
if (model.getDescription() != null || model.getExample() != null) {
out.append("/**\n");
if (model.getDescription() != null) {
for (final String elem : model.getDescription().split("\n")) {
out.append(" * ");
out.append(elem);
out.append("\n");
}
}
if (model.getExample() != null) {
out.append(" * Example:\n");
out.append(" * ```\n");
for (final String elem : model.getExample().split("\n")) {
out.append(" * ");
out.append(elem);
out.append("\n");
}
out.append(" * ```\n");
}
out.append(" */\n");
}
return out.toString();
}
public String optionalTypeZod(final FieldProperty field) {
// Common checking element (apply to List, Map, ...)
if (field.nullable()) {
return ".optional()";
}
if (field.notNull()) {
return "";
}
// Other object:
if (field.model().getOriginClasses() == null || field.model().getOriginClasses().isPrimitive()) {
return "";
}
if (field.columnNotNull()) {
return "";
}
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() {
final StringBuilder out = new StringBuilder();
return out.toString();
}
public String convertHtml(final String data) {
return data.replace("<", "&lt;").replace(">", "&gt;");
}
public static String generateClassModelTypescript(final ClassModel model, final DotClassElementGroup dotGroup)
throws IOException {
if (model instanceof ClassEnumModel) {
final DotClassElement dotFieldModel = dotGroup.find(model);
return dotFieldModel.dotTypeName;
} else if (model instanceof ClassObjectModel) {
final DotClassElement dotFieldModel = dotGroup.find(model);
return dotFieldModel.dotTypeName;
} else if (model instanceof final ClassListModel fieldListModel) {
return generateDotList(fieldListModel, dotGroup);
} else if (model instanceof final ClassMapModel fieldMapModel) {
return generateDotMap(fieldMapModel, dotGroup);
}
throw new IOException("Impossible model:" + model);
}
public static String generateClassModelTypescriptLink(final ClassModel model, final DotClassElementGroup dotGroup)
throws IOException {
if (model instanceof ClassEnumModel) {
final DotClassElement dotFieldModel = dotGroup.find(model);
return dotFieldModel.dotTypeName;
} else if (model instanceof ClassObjectModel) {
final DotClassElement dotFieldModel = dotGroup.find(model);
if (dotFieldModel.nativeType == DefinedPosition.NORMAL) {
return dotFieldModel.dotTypeName;
}
} else if (model instanceof final ClassListModel fieldListModel) {
final String className = generateDotListClassName(fieldListModel, dotGroup);
if (className != null) {
return className;
}
} else if (model instanceof final ClassMapModel fieldMapModel) {
final String className = generateDotMapClassName(fieldMapModel, dotGroup);
if (className != null) {
return className;
}
}
return null;
}
public String generateObject(final ClassObjectModel model, final DotClassElementGroup dotGroup) throws IOException {
final StringBuilder out = new StringBuilder();
final StringBuilder outLinks = new StringBuilder();
out.append("""
%s [
shape=plain
ranksep="2"
label=<<table color="#000000" border="2" cellborder="1" cellspacing="0" cellpadding="4">
<tr>
<td port="NAME"><b>%s</b></td>
</tr>
<tr>
<td>
<table border="0" cellborder="0" cellspacing="0" >
""".formatted(this.dotTypeName, this.dotTypeName));
String inheritence = null;
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final DotClassElement dotParentModel = dotGroup.find(parentClass);
inheritence = dotParentModel.dotTypeName;
}
if (model.getFields().size() == 0) {
out.append("\t\t\t\t\t\t<tr><td> <i>(empty)</i> </td></tr>");
}
for (final FieldProperty field : model.getFields()) {
final ClassModel fieldModel = field.model();
if (field.comment() != null) {
out.append("\t\t\t\t\t\t<tr><td align=\"left\"><i> // ");
out.append(convertHtml(field.comment()));
out.append("</i></td></tr>\n");
}
out.append("\t\t\t\t\t\t<tr><td align=\"left\" port=\"");
out.append(field.name());
out.append("\"><b> + ");
out.append(field.name());
out.append("</b>: ");
out.append(generateClassModelTypescript(fieldModel, dotGroup));
final String remoteType = generateClassModelTypescriptLink(fieldModel, dotGroup);
if (remoteType != null) {
outLinks.append("\t");
outLinks.append(this.dotTypeName);
outLinks.append(":");
outLinks.append(field.name());
outLinks.append(":e -> ");
outLinks.append(remoteType);
outLinks.append(":NAME:w\n");
} else if (field.linkClass() != null) {
final String remoteLinkType = generateClassModelTypescriptLink(field.linkClass(), dotGroup);
if (remoteLinkType != null) {
outLinks.append("\t");
outLinks.append(this.dotTypeName);
outLinks.append(":");
outLinks.append(field.name());
outLinks.append(":e -> ");
outLinks.append(remoteLinkType);
outLinks.append(":NAME:w\n");
}
}
out.append("</td></tr>\n");
}
out.append("""
</table>
</td>
</tr>
</table>>
]
""");
if (inheritence != null) {
out.append("\tedge [dir=back arrowtail=empty arrowsize=2]\n");
out.append("\t");
out.append(inheritence);
// heritage stop link on the "s" South
out.append(":s -> ");
out.append(this.dotTypeName);
// heritage start link on the "n" North
out.append(":n\n");
}
if (!outLinks.isEmpty()) {
out.append("\tedge [dir=back arrowtail=diamond arrowsize=2]\n");
//out.append("\tedge [arrowhead=diamond arrowsize=2]\n");
out.append(outLinks.toString());
}
return out.toString();
}
private static String generateDotMap(final ClassMapModel model, final DotClassElementGroup dotGroup) {
final StringBuilder out = new StringBuilder();
out.append("Map&lt;");
if (model.keyModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateDotList(fieldListModel, dotGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateDotMap(fieldMapModel, dotGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateDotObject(fieldObjectModel, dotGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassEnumModel fieldEnumModel) {
final String tmp = generateDotEnum(fieldEnumModel, dotGroup);
out.append(tmp);
}
out.append(", ");
if (model.valueModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateDotList(fieldListModel, dotGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateDotMap(fieldMapModel, dotGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateDotObject(fieldObjectModel, dotGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassEnumModel fieldEnumModel) {
final String tmp = generateDotEnum(fieldEnumModel, dotGroup);
out.append(tmp);
}
out.append("&gt;");
return out.toString();
}
private static String generateDotEnum(final ClassEnumModel model, final DotClassElementGroup dotGroup) {
final DotClassElement dotParentModel = dotGroup.find(model);
return dotParentModel.dotTypeName;
}
private static String generateDotObject(final ClassObjectModel model, final DotClassElementGroup dotGroup) {
final DotClassElement dotParentModel = dotGroup.find(model);
return dotParentModel.dotTypeName;
}
private static String generateDotObjectClassName(
final ClassObjectModel model,
final DotClassElementGroup dotGroup) {
final DotClassElement dotParentModel = dotGroup.find(model);
if (dotParentModel.nativeType == DefinedPosition.NORMAL) {
return dotParentModel.dotTypeName;
}
return null;
}
private static String generateDotListClassName(final ClassListModel model, final DotClassElementGroup dotGroup) {
if (model.valueModel instanceof final ClassListModel fieldListModel) {
return generateDotListClassName(fieldListModel, dotGroup);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
return generateDotMapClassName(fieldMapModel, dotGroup);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
return generateDotObjectClassName(fieldObjectModel, dotGroup);
}
return null;
}
private static String generateDotMapClassName(final ClassMapModel model, final DotClassElementGroup dotGroup) {
if (model.valueModel instanceof final ClassListModel fieldListModel) {
return generateDotListClassName(fieldListModel, dotGroup);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
return generateDotMapClassName(fieldMapModel, dotGroup);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
return generateDotObjectClassName(fieldObjectModel, dotGroup);
} else if (model.valueModel instanceof final ClassEnumModel fieldEnumModel) {
return generateDotEnum(fieldEnumModel, dotGroup);
}
return null;
}
private static String generateDotList(final ClassListModel model, final DotClassElementGroup dotGroup) {
final StringBuilder out = new StringBuilder();
out.append("List&lt;");
if (model.valueModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateDotList(fieldListModel, dotGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateDotMap(fieldMapModel, dotGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateDotObject(fieldObjectModel, dotGroup);
out.append(tmp);
}
out.append("&gt;");
return out.toString();
}
public String generateFile(final DotClassElementGroup dotGroup) throws IOException {
if (this.nativeType == DefinedPosition.NATIVE) {
return "";
}
final ClassModel model = this.models.get(0);
String data = "";
if (this.nativeType == DefinedPosition.BASIC && model instanceof ClassObjectModel) {
// nothing to do___ data = generateBaseObject();
} else if (model instanceof final ClassEnumModel modelEnum) {
data = generateEnum(modelEnum, dotGroup);
} else if (model instanceof final ClassObjectModel modelObject) {
data = generateObject(modelObject, dotGroup);
}
return data;
}
private static String generateLocalModelBase(final ClassModel model, final DotClassElementGroup dotGroup)
throws IOException {
if (model instanceof final ClassObjectModel objectModel) {
return generateDotObject(objectModel, dotGroup);
}
if (model instanceof final ClassEnumModel enumModel) {
return generateDotEnum(enumModel, dotGroup);
}
if (model instanceof final ClassListModel listModel) {
return generateDotList(listModel, dotGroup);
}
if (model instanceof final ClassMapModel mapModel) {
return generateDotMap(mapModel, dotGroup);
}
return "";
}
public static String generateLocalModel(
final String ModelName,
final List<ClassModel> models,
final DotClassElementGroup dotGroup) throws IOException {
if (models.size() == 1) {
if (models.get(0) instanceof ClassObjectModel) {
return null;
}
if (models.get(0) instanceof ClassEnumModel) {
return null;
}
}
final StringBuilder out = new StringBuilder();
if (models.size() == 1) {
out.append(generateLocalModelBase(models.get(0), dotGroup));
} else {
out.append("Union&lt;");
for (final ClassModel model : models) {
out.append("\t");
out.append(generateLocalModelBase(models.get(0), dotGroup));
out.append(",\n");
}
out.append("&gt;");
}
return out.toString();
}
}

View File

@ -0,0 +1,27 @@
package org.kar.archidata.externalRestApi.dot;
import java.util.List;
import org.kar.archidata.externalRestApi.model.ClassModel;
public class DotClassElementGroup {
private final List<DotClassElement> dotElements;
public List<DotClassElement> getDotElements() {
return this.dotElements;
}
public DotClassElementGroup(final List<DotClassElement> tsElements) {
this.dotElements = tsElements;
}
public DotClassElement find(final ClassModel model) {
for (final DotClassElement elem : this.dotElements) {
if (elem.isCompatible(model)) {
return elem;
}
}
return null;
}
}

View File

@ -0,0 +1,72 @@
package org.kar.archidata.externalRestApi.model;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Temporary class that permit to order the list of API.
record OrderedElement(
String methodName,
Method method) {}
public class ApiGroupModel {
static final Logger LOGGER = LoggerFactory.getLogger(ApiGroupModel.class);
// Name of the REST end-point name
public String restEndPoint;
// Name of the Class
public String name;
// Origin class reference
public Class<?> originClass;
// List of all API
public List<ApiModel> interfaces = new ArrayList<>();
public ApiModel getInterfaceNamed(final String name) {
for (final ApiModel model : this.interfaces) {
if (name.equals(model.name)) {
return model;
}
}
return null;
}
public ApiGroupModel(final Class<?> clazz, final ModelGroup previousModel) throws Exception {
this.originClass = clazz;
// the basic path has no specific elements...
this.restEndPoint = ApiTool.apiAnnotationGetPath(clazz);
if (this.restEndPoint == null) {
this.restEndPoint = "";
}
this.name = clazz.getSimpleName();
final List<String> consumes = ApiTool.apiAnnotationGetConsumes(clazz);
final List<String> produces = ApiTool.apiAnnotationProduces(clazz);
// Get all method to order it. This permit to stabilize the generation.
// JAVA has dynamic allocation of member order, then we need to order it?.
final List<OrderedElement> orderedElements = new ArrayList<>();
for (final Method method : clazz.getDeclaredMethods()) {
final String methodName = method.getName();
orderedElements.add(new OrderedElement(methodName, method));
}
final Comparator<OrderedElement> comparator = Comparator.comparing(OrderedElement::methodName);
Collections.sort(orderedElements, comparator);
for (final OrderedElement orderedElement : orderedElements) {
// Check if the path is available
final RestTypeRequest methodType = ApiTool.apiAnnotationGetTypeRequest2(orderedElement.method());
if (methodType == null) {
LOGGER.info(" [{}] {} ==> No methode type @PATH, @GET ...", methodType, orderedElement.methodName());
continue;
}
final ApiModel model = new ApiModel(clazz, orderedElement.method(), this.restEndPoint, consumes, produces,
previousModel);
this.interfaces.add(model);
}
}
}

View File

@ -0,0 +1,182 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ApiModel {
static final Logger LOGGER = LoggerFactory.getLogger(ApiModel.class);
public record OptionalClassModel(
List<ClassModel> model,
boolean optional) {}
Class<?> originClass;
Method orignMethod;
// Name of the REST end-point name
public String restEndPoint;
// Type of the request:
public RestTypeRequest restTypeRequest;
// Description of the API
public String description;
// need to generate the progression of stream (if possible)
public boolean needGenerateProgress;
// List of types returned by the API
public List<ClassModel> returnTypes = new ArrayList<>();;
// Name of the API (function name)
public String name;
// list of all parameters (/{key}/...
public final Map<String, List<ClassModel>> parameters = new HashMap<>();
// list of all query (?key...)
public final Map<String, List<ClassModel>> queries = new HashMap<>();
// when request multi-part, need to separate it.
public final Map<String, OptionalClassModel> multiPartParameters = new HashMap<>();
// model of data available
public final List<ClassModel> unnamedElement = new ArrayList<>();
// Possible input type of the REST API
public List<String> consumes = new ArrayList<>();
// Possible output type of the REST API
public List<String> produces = new ArrayList<>();
private void updateReturnTypes(final Method method, final ModelGroup previousModel) throws Exception {
// get return type from the user specification:
final Class<?>[] returnTypeModel = ApiTool.apiAnnotationGetAsyncType(method);
LOGGER.info("Get return Type async = {}", returnTypeModel);
if (returnTypeModel != null) {
if (returnTypeModel.length == 0) {
throw new IOException("Create a @AsyncType with empty elements ...");
}
for (final Class<?> clazz : returnTypeModel) {
if (clazz == Void.class || clazz == void.class) {
this.returnTypes.add(previousModel.add(Void.class));
} else if (clazz == List.class) {
throw new IOException("Unmanaged List.class in @AsyncType.");
} else if (clazz == Map.class) {
throw new IOException("Unmanaged Map.class in @AsyncType.");
} else {
this.returnTypes.add(previousModel.add(clazz));
}
}
if (this.returnTypes.size() == 0) {
this.produces.clear();
}
return;
}
final Class<?> returnTypeModelRaw = method.getReturnType();
LOGGER.info("Get return Type RAW = {}", returnTypeModelRaw.getCanonicalName());
if (returnTypeModelRaw == Map.class) {
LOGGER.warn("Model Map");
final Type listType = method.getGenericReturnType();
final ClassModel modelGenerated = ClassModel.getModel(listType, previousModel);
this.returnTypes.add(modelGenerated);
LOGGER.warn("Model Map ==> {}", modelGenerated);
return;
} else if (returnTypeModelRaw == List.class) {
LOGGER.warn("Model List");
final Type listType = method.getGenericReturnType();
final ClassModel modelGenerated = ClassModel.getModel(listType, previousModel);
this.returnTypes.add(modelGenerated);
LOGGER.warn("Model List ==> {}", modelGenerated);
return;
} else {
LOGGER.warn("Model Object");
this.returnTypes.add(previousModel.add(returnTypeModelRaw));
}
LOGGER.warn("List of returns elements:");
for (final ClassModel elem : this.returnTypes) {
LOGGER.warn(" - {}", elem);
}
if (this.returnTypes.size() == 0) {
this.produces.clear();
}
}
public ApiModel(final Class<?> clazz, final Method method, final String baseRestEndPoint,
final List<String> consume, final List<String> produce, final ModelGroup previousModel) throws Exception {
this.originClass = clazz;
this.orignMethod = method;
String tmpPath = ApiTool.apiAnnotationGetPath(method);
if (tmpPath == null) {
tmpPath = "";
}
this.restEndPoint = baseRestEndPoint + "/" + tmpPath;
this.restTypeRequest = ApiTool.apiAnnotationGetTypeRequest2(method);
this.name = method.getName();
this.description = ApiTool.apiAnnotationGetOperationDescription(method);
this.consumes = ApiTool.apiAnnotationGetConsumes2(consume, method);
this.produces = ApiTool.apiAnnotationProduces2(produce, method);
LOGGER.trace(" [{}] {} => {}/{}", baseRestEndPoint, this.name, this.restEndPoint);
this.needGenerateProgress = ApiTool.apiAnnotationTypeScriptProgress(method);
updateReturnTypes(method, previousModel);
LOGGER.trace(" return: {}", this.returnTypes.size());
for (final ClassModel elem : this.returnTypes) {
LOGGER.trace(" - {}", elem);
}
// LOGGER.info(" Parameters:");
for (final Parameter parameter : method.getParameters()) {
// Security context are internal parameter (not available from API)
if (ApiTool.apiAnnotationIsContext(parameter)) {
continue;
}
final Class<?> parameterType = parameter.getType();
final List<ClassModel> parameterModel = new ArrayList<>();
final Class<?>[] asyncType = ApiTool.apiAnnotationGetAsyncType(parameter);
if (asyncType != null) {
for (final Class<?> elem : asyncType) {
final ClassModel modelGenerated = ClassModel.getModel(elem, previousModel);
parameterModel.add(modelGenerated);
}
} else if (parameterType == List.class) {
final Type parameterrizedType = parameter.getParameterizedType();
final ClassModel modelGenerated = ClassModel.getModel(parameterrizedType, previousModel);
parameterModel.add(modelGenerated);
} else if (parameterType == Map.class) {
final Type parameterrizedType = parameter.getParameterizedType();
final ClassModel modelGenerated = ClassModel.getModel(parameterrizedType, previousModel);
parameterModel.add(modelGenerated);
} else {
parameterModel.add(previousModel.add(parameterType));
}
final String pathParam = ApiTool.apiAnnotationGetPathParam(parameter);
final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter);
final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter);
final boolean formDataParamOptional = ApiTool.apiAnnotationGetFormDataOptional(parameter);
if (queryParam != null) {
if (!this.queries.containsKey(queryParam)) {
this.queries.put(queryParam, parameterModel);
}
} else if (pathParam != null) {
if (!this.parameters.containsKey(pathParam)) {
this.parameters.put(pathParam, parameterModel);
}
} else if (formDataParam != null) {
if (!this.multiPartParameters.containsKey(formDataParam)) {
this.multiPartParameters.put(formDataParam,
new OptionalClassModel(parameterModel, formDataParamOptional));
}
} else {
this.unnamedElement.addAll(parameterModel);
}
}
if (this.unnamedElement.size() > 1) {
throw new IOException("Can not parse the API, enmpty element is more than 1 in " + this.name);
}
}
}

View File

@ -0,0 +1,231 @@
package org.kar.archidata.externalRestApi.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.ARCHIVE;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.FormDataOptional;
import org.kar.archidata.annotation.RESTORE;
import org.kar.archidata.annotation.TypeScriptProgress;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
public class ApiTool {
public static String apiAnnotationGetPath(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class);
if (annotation.length == 0) {
return null;
}
return ((Path) annotation[0]).value();
}
public static List<String> apiAnnotationProduces(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Produces) annotation[0]).value());
}
public static List<String> apiAnnotationProduces(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Produces) annotation[0]).value());
}
public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static List<String> apiAnnotationProduces(final Class<?> clazz, final Method method) throws Exception {
final List<String> data = apiAnnotationProduces(method);
if (data != null) {
return data;
}
return apiAnnotationProduces(clazz);
}
public static List<String> apiAnnotationProduces2(final List<String> parentProduce, final Method method)
throws Exception {
final List<String> data = apiAnnotationProduces(method);
if (data != null) {
return data;
}
return parentProduce;
}
public static String apiAnnotationGetOperationDescription(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Operation.class);
if (annotation.length == 0) {
return null;
}
return ((Operation) annotation[0]).description();
}
public static String apiAnnotationGetPath(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class);
if (annotation.length == 0) {
return null;
}
return ((Path) annotation[0]).value();
}
public static String apiAnnotationGetTypeRequest(final Method element) throws Exception {
if (element.getDeclaredAnnotationsByType(GET.class).length == 1) {
return "GET";
}
if (element.getDeclaredAnnotationsByType(POST.class).length == 1) {
return "POST";
}
if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) {
return "PUT";
}
if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) {
return "PATCH";
}
if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) {
return "DELETE";
}
if (element.getDeclaredAnnotationsByType(RESTORE.class).length == 1) {
return "RESTORE";
}
if (element.getDeclaredAnnotationsByType(ARCHIVE.class).length == 1) {
return "ARCHIVE";
}
return null;
}
public static RestTypeRequest apiAnnotationGetTypeRequest2(final Method element) throws Exception {
if (element.getDeclaredAnnotationsByType(GET.class).length == 1) {
return RestTypeRequest.GET;
}
if (element.getDeclaredAnnotationsByType(POST.class).length == 1) {
return RestTypeRequest.POST;
}
if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) {
return RestTypeRequest.PUT;
}
if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) {
return RestTypeRequest.PATCH;
}
if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) {
return RestTypeRequest.DELETE;
}
if (element.getDeclaredAnnotationsByType(RESTORE.class).length == 1) {
return RestTypeRequest.RESTORE;
}
if (element.getDeclaredAnnotationsByType(ARCHIVE.class).length == 1) {
return RestTypeRequest.ARCHIVE;
}
return null;
}
public static String apiAnnotationGetPathParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(PathParam.class);
if (annotation.length == 0) {
return null;
}
return ((PathParam) annotation[0]).value();
}
public static String apiAnnotationGetQueryParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(QueryParam.class);
if (annotation.length == 0) {
return null;
}
return ((QueryParam) annotation[0]).value();
}
public static boolean apiAnnotationGetFormDataOptional(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataOptional.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static String apiAnnotationGetFormDataParam(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataParam.class);
if (annotation.length == 0) {
return null;
}
return ((FormDataParam) annotation[0]).value();
}
public static Class<?>[] apiAnnotationGetAsyncType(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
}
public static Class<?>[] apiAnnotationGetAsyncType(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
}
public static List<String> apiAnnotationGetConsumes(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Consumes) annotation[0]).value());
}
public static List<String> apiAnnotationGetConsumes(final Class<?> element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class);
if (annotation.length == 0) {
return null;
}
return Arrays.asList(((Consumes) annotation[0]).value());
}
public static List<String> apiAnnotationGetConsumes(final Class<?> clazz, final Method method) throws Exception {
final List<String> data = apiAnnotationGetConsumes(method);
if (data != null) {
return data;
}
return apiAnnotationGetConsumes(clazz);
}
public static List<String> apiAnnotationGetConsumes2(final List<String> parentConsume, final Method method)
throws Exception {
final List<String> data = apiAnnotationGetConsumes(method);
if (data != null) {
return data;
}
return parentConsume;
}
public static boolean apiAnnotationIsContext(final Parameter element) throws Exception {
return element.getDeclaredAnnotationsByType(Context.class).length != 0;
}
}

View File

@ -0,0 +1,67 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ClassEnumModel extends ClassModel {
protected ClassEnumModel(final Class<?> clazz) {
this.originClasses = clazz;
this.noWriteSpecificMode = true;
}
@Override
public String toString() {
final StringBuilder out = new StringBuilder();
out.append("ClassEnumModel [");
out.append(this.originClasses.getCanonicalName());
out.append("]");
return out.toString();
}
final Map<String, Object> listOfValues = new HashMap<>();
@Override
public void analyze(final ModelGroup group) throws IOException {
if (this.analyzeDone) {
return;
}
this.analyzeDone = true;
final Class<?> clazz = this.originClasses;
final Object[] constants = clazz.getEnumConstants();
// Try to get a get Value element to serialize:
try {
final Method getValueMethod = clazz.getMethod("getValue");
for (final Object constant : constants) {
final String name = constant.toString();
final Object value = getValueMethod.invoke(constant);
this.listOfValues.put(name, value);
}
return;
} catch (final Exception e) {
//e.printStackTrace();
}
for (final Object elem : constants) {
this.listOfValues.put(elem.toString(), elem.toString());
}
}
public Map<String, Object> getListOfValues() {
return this.listOfValues;
}
@Override
public Set<ClassModel> getAlls() {
return Set.of(this);
}
@Override
public Set<ClassModel> getDependencyGroupModels() {
return Set.of(this);
}
}

View File

@ -0,0 +1,47 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
public class ClassListModel extends ClassModel {
public ClassModel valueModel;
public ClassListModel(final ClassModel valueModel) {
this.valueModel = valueModel;
}
public ClassListModel(final Class<?> clazz, final ModelGroup previousModel) throws IOException {
this.valueModel = getModel(clazz, previousModel);
}
public ClassListModel(final Type model, final ModelGroup previousModel) throws IOException {
this.valueModel = getModel(model, previousModel);
}
public ClassListModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException {
final Type model = listType.getActualTypeArguments()[0];
this.valueModel = getModel(model, previousModel);
}
@Override
public String toString() {
return "ClassListModel [valueModel=" + this.valueModel + "]";
}
@Override
public void analyze(final ModelGroup group) throws IOException {
throw new IOException("Analyze can not be done at this phase for List...");
}
@Override
public Set<ClassModel> getAlls() {
return this.valueModel.getAlls();
}
@Override
public Set<ClassModel> getDependencyGroupModels() {
return this.valueModel.getDependencyGroupModels();
}
}

View File

@ -0,0 +1,52 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
public class ClassMapModel extends ClassModel {
public ClassModel keyModel;
public ClassModel valueModel;
public ClassMapModel(final ClassModel keyModel, final ClassModel valueModel) {
this.keyModel = keyModel;
this.valueModel = valueModel;
}
public ClassMapModel(final Type listTypeKey, final Type listTypeValue, final ModelGroup previousModel)
throws IOException {
this.keyModel = getModel(listTypeKey, previousModel);
this.valueModel = getModel(listTypeValue, previousModel);
}
public ClassMapModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException {
this.keyModel = getModel(listType.getActualTypeArguments()[0], previousModel);
this.valueModel = getModel(listType.getActualTypeArguments()[1], previousModel);
}
@Override
public String toString() {
return "ClassMapModel [keyModel=" + this.keyModel + ", valueModel=" + this.valueModel + "]";
}
@Override
public void analyze(final ModelGroup group) throws IOException {
throw new IOException("Analyze can not be done at this phase for Map...");
}
@Override
public Set<ClassModel> getAlls() {
final Set<ClassModel> out = new HashSet<>(this.keyModel.getAlls());
out.addAll(this.valueModel.getAlls());
return out;
}
@Override
public Set<ClassModel> getDependencyGroupModels() {
final Set<ClassModel> out = new HashSet<>(this.valueModel.getDependencyGroupModels());
out.addAll(this.keyModel.getDependencyGroupModels());
return out;
}
}

View File

@ -0,0 +1,83 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class ClassModel {
protected boolean analyzeDone = false;
protected Class<?> originClasses = null;
protected boolean noWriteSpecificMode = false;
protected List<ClassModel> dependencyModels = new ArrayList<>();
public Class<?> getOriginClasses() {
return this.originClasses;
}
public boolean isNoWriteSpecificMode() {
return this.noWriteSpecificMode;
}
protected boolean isCompatible(final Class<?> clazz) {
return this.originClasses == clazz;
}
public List<ClassModel> getDependencyModels() {
return this.dependencyModels;
}
public abstract Set<ClassModel> getDependencyGroupModels();
public static ClassModel getModel(final Type type, final ModelGroup previousModel) throws IOException {
if (type instanceof final ParameterizedType paramType) {
final Type[] typeArguments = paramType.getActualTypeArguments();
if (paramType.getRawType() == List.class) {
return new ClassListModel(typeArguments[0], previousModel);
}
if (paramType.getRawType() == Map.class) {
return new ClassMapModel(typeArguments[0], typeArguments[1], previousModel);
}
throw new IOException("Fail to manage parametrized type...");
}
return previousModel.add((Class<?>) type);
}
public static ClassModel getModelBase(
final Class<?> clazz,
final Type parameterizedType,
final ModelGroup previousModel) throws IOException {
/*
if (clazz == List.class) {
return new ClassListModel((ParameterizedType) parameterizedType, previousModel);
}
if (clazz == Map.class) {
return new ClassMapModel((ParameterizedType) parameterizedType, previousModel);
}
return previousModel.add(clazz);
*/
return getModel(parameterizedType, previousModel);
}
public static ClassModel getModel(final Class<?> type, final ModelGroup previousModel) throws IOException {
if (type == List.class) {
throw new IOException("Fail to manage parametrized type...");
}
if (type == Map.class) {
throw new IOException("Fail to manage parametrized type...");
}
return previousModel.add(type);
}
public abstract void analyze(final ModelGroup group) throws Exception;
public abstract Set<ClassModel> getAlls();
public List<String> getReadOnlyField() {
return List.of();
}
}

View File

@ -0,0 +1,270 @@
package org.kar.archidata.externalRestApi.model;
import java.io.IOException;
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.List;
import java.util.Set;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.Size;
public class ClassObjectModel extends ClassModel {
static final Logger LOGGER = LoggerFactory.getLogger(ClassObjectModel.class);
public ClassObjectModel(final Class<?> clazz) {
this.originClasses = clazz;
}
@Override
public String toString() {
final StringBuilder out = new StringBuilder();
out.append("ClassObjectModel [");
out.append(this.originClasses.getCanonicalName());
out.append("]");
return out.toString();
}
private static boolean isFieldFromSuperClass(final Class<?> model, final String filedName) {
final Class<?> superClass = model.getSuperclass();
if (superClass == null) {
return false;
}
for (final Field field : superClass.getFields()) {
String name;
try {
name = AnnotationTools.getFieldName(field);
if (filedName.equals(name)) {
return true;
}
} catch (final Exception e) {
// TODO Auto-generated catch block
LOGGER.trace("Catch error field name in parent create data table: {}", e.getMessage());
}
}
return false;
}
public record FieldProperty(
String name,
ClassModel model,
ClassModel linkClass, // link class when use remote ID (ex: list<UUID>)
String comment,
int sizeMin, // String SizeMin
int sizeMax, // String SizeMax
Long min, // number min value
Long max, // number max value
Boolean readOnly,
Boolean notNull,
Boolean columnNotNull,
Boolean nullable) {
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 Boolean readOnly, final Boolean notNull, final Boolean columnNotNull, final Boolean nullable) {
this.name = name;
this.model = model;
this.linkClass = linkClass;
this.comment = comment;
this.sizeMin = sizeMin;
this.sizeMax = sizeMax;
this.min = min;
this.max = max;
this.readOnly = readOnly;
this.notNull = notNull;
this.columnNotNull = columnNotNull;
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) {
final ManyToOne manyToOne = AnnotationTools.getManyToOne(field);
if (manyToOne != null) {
if (manyToOne.targetEntity() != null && manyToOne.targetEntity() != void.class) {
return manyToOne.targetEntity();
}
return null;
}
final ManyToMany manyToMany = AnnotationTools.getManyToMany(field);
if (manyToMany != null) {
if (manyToMany.targetEntity() != null && manyToMany.targetEntity() != void.class) {
return manyToMany.targetEntity();
}
return null;
}
final OneToMany oneToMany = AnnotationTools.getOneToMany(field);
if (oneToMany != null) {
if (oneToMany.targetEntity() != null && oneToMany.targetEntity() != void.class) {
return oneToMany.targetEntity();
}
return null;
}
return null;
}
private static ClassModel getSubModelIfExist(final Field field, final ModelGroup previous) throws IOException {
final Class<?> tmp = getSubModelIfExist2(field);
if (tmp == null) {
return null;
}
return ClassModel.getModel(tmp, previous);
}
public FieldProperty(final Field field, final ModelGroup previous) throws DataAccessException, IOException {
this(field.getName(), //
ClassModel.getModel(field.getGenericType(), previous), //
getSubModelIfExist(field, previous), //
AnnotationTools.getComment(field), //
getStringMinSize(field), //
getStringMaxSize(field), //
AnnotationTools.getConstraintsMin(field), //
AnnotationTools.getConstraintsMax(field), //
AnnotationTools.getSchemaReadOnly(field), //
AnnotationTools.getConstraintsNotNull(field), //
AnnotationTools.getColumnNotNull(field), //
AnnotationTools.getNullable(field));
}
}
String name = "";
boolean isPrimitive = false;
String description = null;
String example = null;
ClassModel extendsClass = null;
List<FieldProperty> fields = new ArrayList<>();
public String getName() {
return this.name;
}
public boolean isPrimitive() {
return this.isPrimitive;
}
public String getDescription() {
return this.description;
}
public String getExample() {
return this.example;
}
public ClassModel getExtendsClass() {
return this.extendsClass;
}
public List<FieldProperty> getFields() {
return this.fields;
}
@Override
public void analyze(final ModelGroup previous) throws Exception {
if (this.analyzeDone) {
return;
}
this.analyzeDone = true;
final Class<?> clazz = this.originClasses;
this.noWriteSpecificMode = AnnotationTools.getNoWriteSpecificMode(clazz);
this.isPrimitive = clazz.isPrimitive();
if (this.isPrimitive) {
return;
}
final List<Class<?>> basicClass = List.of(Void.class, void.class, Character.class, char.class, Short.class,
short.class, Integer.class, int.class, Long.class, long.class, Float.class, float.class, Double.class,
double.class, Date.class, Timestamp.class, LocalDate.class, LocalTime.class);
if (basicClass.contains(clazz)) {
return;
}
// Local generation of class:
LOGGER.trace("parse class: '{}'", clazz.getCanonicalName());
final List<String> alreadyAdded = new ArrayList<>();
for (final Field elem : clazz.getFields()) {
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
continue;
}
final String dataName = elem.getName();
if (isFieldFromSuperClass(clazz, dataName)) {
LOGGER.trace(" SKIP: '{}'", elem.getName());
continue;
}
if (alreadyAdded.contains(dataName)) {
LOGGER.trace(" SKIP2: '{}'", elem.getName());
continue;
}
alreadyAdded.add(dataName);
LOGGER.trace(" + '{}'", elem.getName());
LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType());
final FieldProperty porperty = new FieldProperty(elem, previous);
for (final ClassModel depModel : porperty.model().getAlls()) {
if (!this.dependencyModels.contains(depModel)) {
this.dependencyModels.add(depModel);
}
}
this.fields.add(new FieldProperty(elem, previous));
}
this.name = clazz.getName();
final String[] elems = this.name.split("\\$");
if (elems.length == 2) {
LOGGER.warn("Can have conflict in generation: {} (Remove class path) ==> {}", this.name, elems[1]);
this.name = elems[1];
}
this.description = AnnotationTools.getSchemaDescription(clazz);
this.example = AnnotationTools.getSchemaExample(clazz);
final Class<?> parentClass = clazz.getSuperclass();
// manage heritage
if (parentClass != null && parentClass != Object.class && parentClass != Record.class) {
this.extendsClass = previous.add(parentClass);
this.dependencyModels.add(this.extendsClass);
}
}
@Override
public Set<ClassModel> getDependencyGroupModels() {
return Set.of(this);
}
@Override
public Set<ClassModel> getAlls() {
return Set.of(this);
}
@Override
public List<String> getReadOnlyField() {
final List<String> out = new ArrayList<>();
for (final FieldProperty field : this.fields) {
if (field.readOnly()) {
out.add(field.name);
}
}
if (this.extendsClass != null) {
out.addAll(this.extendsClass.getReadOnlyField());
}
return out;
}
}

View File

@ -0,0 +1,53 @@
package org.kar.archidata.externalRestApi.model;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.Response;
public class ModelGroup {
static final Logger LOGGER = LoggerFactory.getLogger(ModelGroup.class);
public List<ClassModel> models = new ArrayList<>();
public ModelGroup() {}
public void addAll(final List<Class<?>> classes) {
for (final Class<?> clazz : classes) {
add(clazz);
}
}
public ClassModel add(Class<?> clazz) {
if (clazz == Response.class) {
clazz = Object.class;
}
if (clazz == Number.class) {
return null;
}
//LOGGER.trace("Search element {}", clazz.getCanonicalName());
for (final ClassModel value : this.models) {
if (value.isCompatible(clazz)) {
//LOGGER.trace(" ==> return {}", value);
return value;
}
}
if (clazz.isEnum()) {
final ClassModel elem = new ClassEnumModel(clazz);
this.models.add(elem);
//LOGGER.trace(" ==> return enum {}", elem);
return elem;
}
// create new model:
final ClassModel elem = new ClassObjectModel(clazz);
this.models.add(elem);
//LOGGER.trace(" ==> return object {}", elem);
return elem;
}
public List<ClassModel> getModels() {
return this.models;
}
}

View File

@ -0,0 +1,5 @@
package org.kar.archidata.externalRestApi.model;
public enum RestTypeRequest {
GET, POST, PUT, PATCH, DELETE, RESTORE, ARCHIVE
}

View File

@ -0,0 +1,458 @@
package org.kar.archidata.externalRestApi.typescript;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import org.kar.archidata.dataAccess.DataExport;
import org.kar.archidata.externalRestApi.model.ApiGroupModel;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.kar.archidata.externalRestApi.model.ApiModel.OptionalClassModel;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.kar.archidata.externalRestApi.model.RestTypeRequest;
import org.kar.archidata.externalRestApi.typescript.TsClassElement.DefinedPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
public class TsApiGeneration {
static final Logger LOGGER = LoggerFactory.getLogger(TsApiGeneration.class);
public static String getBaseHeader() {
return """
/**
* Interface of the server (auto-generated code)
*/
""";
}
public static String generateClassEnumModelTypescript(
final ClassEnumModel model,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
imports.add(model);
final TsClassElement tsModel = tsGroup.find(model);
return tsModel.tsTypeName;
}
public static String generateClassObjectModelTypescript(
final ClassObjectModel model,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType != DefinedPosition.NATIVE) {
if (importWrite == null || tsModel.models.get(0).isNoWriteSpecificMode()) {
imports.add(model);
} else {
importWrite.add(model);
}
}
if (tsModel.nativeType != DefinedPosition.NORMAL) {
return tsModel.tsTypeName;
}
if (importWrite != null && !tsModel.models.get(0).isNoWriteSpecificMode()) {
return tsModel.tsTypeName + "Write";
}
return tsModel.tsTypeName;
}
public static String generateClassMapModelTypescript(
final ClassMapModel model,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
final StringBuilder out = new StringBuilder();
out.append("{[key: ");
out.append(generateClassModelTypescript(model.keyModel, tsGroup, imports, importWrite));
out.append("]: ");
out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importWrite));
out.append(";}");
return out.toString();
}
public static String generateClassListModelTypescript(
final ClassListModel model,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
final StringBuilder out = new StringBuilder();
out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, importWrite));
out.append("[]");
return out.toString();
}
public static String generateClassModelTypescript(
final ClassModel model,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
if (model instanceof final ClassObjectModel objectModel) {
return generateClassObjectModelTypescript(objectModel, tsGroup, imports, importWrite);
}
if (model instanceof final ClassListModel listModel) {
return generateClassListModelTypescript(listModel, tsGroup, imports, importWrite);
}
if (model instanceof final ClassMapModel mapModel) {
return generateClassMapModelTypescript(mapModel, tsGroup, imports, importWrite);
}
if (model instanceof final ClassEnumModel enumModel) {
return generateClassEnumModelTypescript(enumModel, tsGroup, imports, importWrite);
}
throw new IOException("Impossible model:" + model);
}
public static String generateClassModelsTypescript(
final List<ClassModel> models,
final TsClassElementGroup tsGroup,
final Set<ClassModel> imports,
final Set<ClassModel> importWrite) throws IOException {
if (models.size() == 0) {
return "void";
}
final StringBuilder out = new StringBuilder();
boolean isFirst = true;
for (final ClassModel model : models) {
if (isFirst) {
isFirst = false;
} else {
out.append(" | ");
}
final String data = generateClassModelTypescript(model, tsGroup, imports, importWrite);
out.append(data);
}
return out.toString();
}
public static String capitalizeFirstLetter(final String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
public static void generateApiFile(
final ApiGroupModel element,
final String pathPackage,
final TsClassElementGroup tsGroup) throws IOException {
final StringBuilder data = new StringBuilder();
data.append("export namespace ");
data.append(element.name);
data.append(" {\n");
final Set<ClassModel> imports = new HashSet<>();
final Set<ClassModel> zodImports = new HashSet<>();
final Set<ClassModel> isImports = new HashSet<>();
final Set<ClassModel> writeImports = new HashSet<>();
final Set<String> toolImports = new HashSet<>();
for (final ApiModel interfaceElement : element.interfaces) {
final List<String> consumes = interfaceElement.consumes;
final List<String> produces = interfaceElement.produces;
final boolean needGenerateProgress = interfaceElement.needGenerateProgress;
final String returnModelNameIfComplex = capitalizeFirstLetter(interfaceElement.name) + "TypeReturn";
final String returnComplexModel = TsClassElement.generateLocalModel(returnModelNameIfComplex,
interfaceElement.returnTypes, tsGroup);
if (returnComplexModel != null) {
data.append("\n\n");
data.append(returnComplexModel.replaceAll("(?m)^", "\t"));
for (final ClassModel elem : interfaceElement.returnTypes) {
zodImports.addAll(elem.getDependencyGroupModels());
}
}
if (interfaceElement.description != null) {
data.append("\n\t/**\n\t * ");
data.append(interfaceElement.description);
data.append("\n\t */");
}
data.append("\n\texport function ");
data.append(interfaceElement.name);
data.append("({\n\t\t\trestConfig,");
if (!interfaceElement.queries.isEmpty()) {
data.append("\n\t\t\tqueries,");
}
if (!interfaceElement.parameters.isEmpty()) {
data.append("\n\t\t\tparams,");
}
if (produces != null && produces.size() > 1) {
data.append("\n\t\t\tproduce,");
}
if (interfaceElement.unnamedElement.size() == 1 || interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\t\tdata,");
}
if (needGenerateProgress) {
data.append("\n\t\t\tcallbacks,");
}
data.append("\n\t\t}: {");
data.append("\n\t\trestConfig: RESTConfig,");
toolImports.add("RESTConfig");
if (!interfaceElement.queries.isEmpty()) {
data.append("\n\t\tqueries: {");
for (final Entry<String, List<ClassModel>> queryEntry : interfaceElement.queries.entrySet()) {
data.append("\n\t\t\t");
data.append(queryEntry.getKey());
data.append("?: ");
data.append(generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, null));
data.append(",");
}
data.append("\n\t\t},");
}
if (!interfaceElement.parameters.isEmpty()) {
data.append("\n\t\tparams: {");
for (final Entry<String, List<ClassModel>> paramEntry : interfaceElement.parameters.entrySet()) {
data.append("\n\t\t\t");
data.append(paramEntry.getKey());
data.append(": ");
data.append(generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, null));
data.append(",");
}
data.append("\n\t\t},");
}
if (interfaceElement.unnamedElement.size() == 1) {
data.append("\n\t\tdata: ");
data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, imports,
writeImports));
data.append(",");
} else if (interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\tdata: {");
for (final Entry<String, OptionalClassModel> pathEntry : interfaceElement.multiPartParameters
.entrySet()) {
data.append("\n\t\t\t");
data.append(pathEntry.getKey());
if (pathEntry.getValue().optional()) {
data.append("?");
}
data.append(": ");
data.append(generateClassModelsTypescript(pathEntry.getValue().model(), tsGroup, imports,
writeImports));
data.append(",");
}
data.append("\n\t\t},");
}
if (produces != null && produces.size() > 1) {
data.append("\n\t\tproduce: ");
String isFist = null;
for (final String elem : produces) {
String lastElement = null;
if (MediaType.APPLICATION_JSON.equals(elem)) {
lastElement = "HTTPMimeType.JSON";
toolImports.add("HTTPMimeType");
}
if (MediaType.MULTIPART_FORM_DATA.equals(elem)) {
lastElement = "HTTPMimeType.MULTIPART";
toolImports.add("HTTPMimeType");
}
if (DataExport.CSV_TYPE.equals(elem)) {
lastElement = "HTTPMimeType.CSV";
toolImports.add("HTTPMimeType");
}
if (lastElement != null) {
if (isFist == null) {
isFist = lastElement;
} else {
data.append(" | ");
}
data.append(lastElement);
} else {
LOGGER.error("Unmanaged model type: {}", elem);
}
}
data.append(",");
}
if (needGenerateProgress) {
data.append("\n\t\tcallbacks?: RESTCallbacks,");
toolImports.add("RESTCallbacks");
}
data.append("\n\t}): Promise<");
if (returnComplexModel != null) {
data.append(returnModelNameIfComplex);
data.append("> {");
data.append("\n\t\treturn RESTRequestJson({");
toolImports.add("RESTRequestJson");
} else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports,
null);
data.append(returnType);
data.append("> {");
if ("void".equals(returnType)) {
data.append("\n\t\treturn RESTRequestVoid({");
toolImports.add("RESTRequestVoid");
} else {
isImports.addAll(interfaceElement.returnTypes);
data.append("\n\t\treturn RESTRequestJson({");
toolImports.add("RESTRequestJson");
}
}
data.append("\n\t\t\trestModel: {");
data.append("\n\t\t\t\tendPoint: \"");
data.append(interfaceElement.restEndPoint);
data.append("\",");
data.append("\n\t\t\t\trequestType: HTTPRequestModel.");
toolImports.add("HTTPRequestModel");
data.append(interfaceElement.restTypeRequest);
data.append(",");
if (consumes != null) {
for (final String elem : consumes) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.JSON,");
toolImports.add("HTTPMimeType");
break;
} else if (MediaType.MULTIPART_FORM_DATA.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.MULTIPART,");
toolImports.add("HTTPMimeType");
break;
} else if (MediaType.TEXT_PLAIN.equals(elem)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
toolImports.add("HTTPMimeType");
break;
}
}
} else if (RestTypeRequest.DELETE.equals(interfaceElement.restTypeRequest)) {
data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,");
toolImports.add("HTTPMimeType");
}
if (produces != null) {
if (produces.size() > 1) {
data.append("\n\t\t\t\taccept: produce,");
} else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup,
imports, null);
if (!"void".equals(returnType)) {
for (final String elem : produces) {
if (MediaType.APPLICATION_JSON.equals(elem)) {
data.append("\n\t\t\t\taccept: HTTPMimeType.JSON,");
toolImports.add("HTTPMimeType");
break;
}
}
}
}
}
data.append("\n\t\t\t},");
data.append("\n\t\t\trestConfig,");
if (!interfaceElement.parameters.isEmpty()) {
data.append("\n\t\t\tparams,");
}
if (!interfaceElement.queries.isEmpty()) {
data.append("\n\t\t\tqueries,");
}
if (interfaceElement.unnamedElement.size() == 1) {
data.append("\n\t\t\tdata,");
} else if (interfaceElement.multiPartParameters.size() != 0) {
data.append("\n\t\t\tdata,");
}
if (needGenerateProgress) {
data.append("\n\t\t\tcallbacks,");
}
data.append("\n\t\t}");
if (returnComplexModel != null) {
data.append(", is");
data.append(returnModelNameIfComplex);
} else {
final TsClassElement retType = tsGroup.find(interfaceElement.returnTypes.get(0));
if (retType.tsCheckType != null) {
data.append(", ");
data.append(retType.tsCheckType);
imports.add(interfaceElement.returnTypes.get(0));
}
}
data.append(");");
data.append("\n\t};");
}
data.append("\n}\n");
final StringBuilder out = new StringBuilder();
out.append(getBaseHeader());
final List<String> toolImportsList = new ArrayList<>(toolImports);
Collections.sort(toolImportsList);
if (toolImportsList.size() != 0) {
out.append("import {");
for (final String elem : toolImportsList) {
out.append("\n\t");
out.append(elem);
out.append(",");
}
out.append("\n} from \"../rest-tools\";\n\n");
}
if (zodImports.size() != 0) {
out.append("import { z as zod } from \"zod\"\n");
}
final Set<String> finalImportSet = new TreeSet<>();
for (final ClassModel model : imports) {
final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
finalImportSet.add(tsModel.tsTypeName);
}
for (final ClassModel model : isImports) {
final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
if (tsModel.tsCheckType != null) {
finalImportSet.add(tsModel.tsCheckType);
}
}
for (final ClassModel model : zodImports) {
final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType == DefinedPosition.NATIVE) {
continue;
}
finalImportSet.add("Zod" + tsModel.tsTypeName);
}
for (final ClassModel model : writeImports) {
final TsClassElement tsModel = tsGroup.find(model);
if (tsModel.nativeType != DefinedPosition.NORMAL) {
continue;
}
if (tsModel.models.get(0).isNoWriteSpecificMode()) {
continue;
}
finalImportSet.add(tsModel.tsTypeName + "Write");
}
if (finalImportSet.size() != 0) {
out.append("import {");
for (final String elem : finalImportSet) {
out.append("\n\t");
out.append(elem);
out.append(",");
}
out.append("\n} from \"../model\";\n\n");
}
out.append(data.toString());
final Path path = Paths.get(pathPackage + File.separator + "api");
if (Files.notExists(path)) {
Files.createDirectories(path);
}
final String fileName = TsClassElement.determineFileName(element.name);
final FileWriter myWriter = new FileWriter(
pathPackage + File.separator + "api" + File.separator + fileName + ".ts");
myWriter.write(out.toString());
myWriter.close();
}
}

View File

@ -0,0 +1,590 @@
package org.kar.archidata.externalRestApi.typescript;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map.Entry;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel.FieldProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TsClassElement {
static final Logger LOGGER = LoggerFactory.getLogger(TsClassElement.class);
public enum DefinedPosition {
NATIVE, // Native element of TS language.
BASIC, // basic wrapping for JAVA type.
NORMAL // Normal Object to interpret.
}
public List<ClassModel> models;
public String zodName;
public String tsTypeName;
public String tsCheckType;
public String declaration;
public String fileName = null;
public String comment = null;
public DefinedPosition nativeType = DefinedPosition.NORMAL;
public static String determineFileName(final String className) {
return className.replaceAll("([a-z])([A-Z])", "$1-$2").replaceAll("([A-Z])([A-Z][a-z])", "$1-$2").toLowerCase();
}
public TsClassElement(final List<ClassModel> model, final String zodName, final String tsTypeName,
final String tsCheckType, final String declaration, final DefinedPosition nativeType) {
this.models = model;
this.zodName = zodName;
this.tsTypeName = tsTypeName;
this.tsCheckType = tsCheckType;
this.declaration = declaration;
this.nativeType = nativeType;
this.fileName = determineFileName(tsTypeName);
}
public TsClassElement(final ClassModel model) {
this.models = List.of(model);
this.zodName = "Zod" + model.getOriginClasses().getSimpleName();
this.tsTypeName = model.getOriginClasses().getSimpleName();
this.tsCheckType = "is" + model.getOriginClasses().getSimpleName();
this.declaration = null;
this.fileName = determineFileName(this.tsTypeName);
}
public boolean isCompatible(final ClassModel model) {
return this.models.contains(model);
}
public String getBaseHeader() {
return """
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
""";
}
public String generateEnum(final ClassEnumModel model, final TsClassElementGroup tsGroup) throws IOException {
final StringBuilder out = new StringBuilder();
out.append(getBaseHeader());
out.append("\n");
//out.append(generateComment(model));
if (System.getenv("ARCHIDATA_GENERATE_ZOD_ENUM") != null) {
boolean first = true;
out.append("export const ");
out.append(this.tsTypeName);
out.append(" = ");
out.append("zod.enum([");
for (final Entry<String, Object> elem : model.getListOfValues().entrySet()) {
if (!first) {
out.append(",\n\t");
} else {
out.append("\n\t");
first = false;
}
out.append("'");
out.append(elem.getKey());
out.append("'");
}
if (first) {
out.append("]}");
} else {
out.append("\n\t])");
}
out.append(";\n");
out.append(generateZodInfer(this.tsTypeName, this.zodName));
} else {
boolean first = true;
out.append("export enum ");
out.append(this.tsTypeName);
out.append(" {");
for (final Entry<String, Object> elem : model.getListOfValues().entrySet()) {
if (!first) {
out.append(",\n\t");
} else {
out.append("\n\t");
first = false;
}
out.append(elem.getKey());
out.append(" = ");
if (elem.getValue() instanceof final Integer value) {
out.append(value);
} else {
out.append("'");
out.append(elem.getValue());
out.append("'");
}
}
if (first) {
out.append("}");
} else {
out.append(",\n\t}");
}
out.append(";\n");
out.append("\nexport const ");
out.append(this.zodName);
out.append(" = zod.nativeEnum(");
out.append(this.tsTypeName);
out.append(");\n");
}
out.append(generateExportCheckFunctionWrite(""));
return out.toString();
}
private static String generateExportCheckFunction(
final String tsCheckType,
final String tsTypeName,
final String zodName) {
final StringBuilder out = new StringBuilder();
out.append("\nexport function ");
out.append(tsCheckType);
out.append("(data: any): data is ");
out.append(tsTypeName);
out.append(" {\n\ttry {\n\t\t");
out.append(zodName);
out.append("""
.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='""");
out.append(zodName);
out.append("""
' error=${e}`);
return false;
}
}
""");
return out.toString();
}
private String generateExportCheckFunctionWrite(final String writeString) {
return generateExportCheckFunction(this.tsCheckType + writeString, this.tsTypeName + writeString,
this.zodName + writeString);
}
public String generateImports(final List<ClassModel> depModels, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
for (final ClassModel depModel : depModels) {
final TsClassElement tsModel = tsGroup.find(depModel);
if (tsModel.nativeType != DefinedPosition.NATIVE) {
out.append("import {");
out.append(tsModel.zodName);
if (tsModel.nativeType == DefinedPosition.NORMAL && !(tsModel.models.get(0).isNoWriteSpecificMode())) {
out.append(", ");
out.append(tsModel.zodName);
out.append("Write ");
}
out.append("} from \"./");
out.append(tsModel.fileName);
out.append("\";\n");
}
}
return out.toString();
}
private Object generateComment(final ClassObjectModel model) {
final StringBuilder out = new StringBuilder();
if (model.getDescription() != null || model.getExample() != null) {
out.append("/**\n");
if (model.getDescription() != null) {
for (final String elem : model.getDescription().split("\n")) {
out.append(" * ");
out.append(elem);
out.append("\n");
}
}
if (model.getExample() != null) {
out.append(" * Example:\n");
out.append(" * ```\n");
for (final String elem : model.getExample().split("\n")) {
out.append(" * ");
out.append(elem);
out.append("\n");
}
out.append(" * ```\n");
}
out.append(" */\n");
}
return out.toString();
}
public boolean isOptionalTypeZod(final FieldProperty field) {
// Common checking element (apply to List, Map, ...)
if (field.nullable()) {
return true;
}
if (field.notNull()) {
return false;
}
// Other object:
if (field.model().getOriginClasses() == null || field.model().getOriginClasses().isPrimitive()) {
return false;
}
if (field.columnNotNull()) {
return false;
}
return true;
}
public String optionalTypeZod(final FieldProperty field) {
// Common checking element (apply to List, Map, ...)
if (isOptionalTypeZod(field)) {
return ".optional()";
}
return "";
}
public String optionalWriteTypeZod(final FieldProperty field) {
// Common checking element (apply to List, Map, ...)
if (isOptionalTypeZod(field)) {
return ".nullable()";
}
return "";
}
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() {
final StringBuilder out = new StringBuilder();
out.append(getBaseHeader());
out.append("\n");
out.append("export const ");
out.append(this.zodName);
out.append(" = ");
out.append(this.declaration);
out.append(";");
out.append(generateZodInfer(this.tsTypeName, this.zodName));
return out.toString();
}
public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException {
final StringBuilder out = new StringBuilder();
out.append(getBaseHeader());
out.append(generateImports(model.getDependencyModels(), tsGroup));
out.append("\n");
// ------------------------------------------------------------------------
// -- Generate read mode
// ------------------------------------------------------------------------
out.append(generateComment(model));
out.append("export const ");
out.append(this.zodName);
out.append(" = ");
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName);
out.append(".extend({");
} else {
out.append("zod.object({");
}
out.append("\n");
for (final FieldProperty field : model.getFields()) {
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(readOnlyZod(field));
out.append(optionalTypeZod(field));
out.append(",\n");
}
final List<String> omitField = model.getReadOnlyField();
out.append("\n});\n");
out.append(generateZodInfer(this.tsTypeName, this.zodName));
out.append(generateExportCheckFunctionWrite(""));
// check if we need to generate write mode :
if (!model.isNoWriteSpecificMode()) {
// ------------------------------------------------------------------------
// -- Generate write mode
// ------------------------------------------------------------------------
//out.append(generateComment(model));
out.append("export const ");
out.append(this.zodName);
out.append("Write = ");
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(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");
*/
}
return out.toString();
}
private static String generateZodInfer(final String tsName, final String zodName) {
final StringBuilder out = new StringBuilder();
out.append("\nexport type ");
out.append(tsName);
out.append(" = zod.infer<typeof ");
out.append(zodName);
out.append(">;\n");
return out.toString();
}
private static String generateTsMap(final ClassMapModel model, final TsClassElementGroup tsGroup) {
final StringBuilder out = new StringBuilder();
out.append("zod.record(");
if (model.keyModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateTsList(fieldListModel, tsGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateTsMap(fieldMapModel, tsGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateTsObject(fieldObjectModel, tsGroup);
out.append(tmp);
} else if (model.keyModel instanceof final ClassEnumModel fieldEnumModel) {
final String tmp = generateTsEnum(fieldEnumModel, tsGroup);
out.append(tmp);
}
out.append(", ");
if (model.valueModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateTsList(fieldListModel, tsGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateTsMap(fieldMapModel, tsGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateTsObject(fieldObjectModel, tsGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassEnumModel fieldEnumModel) {
final String tmp = generateTsEnum(fieldEnumModel, tsGroup);
out.append(tmp);
}
out.append(")");
return out.toString();
}
private static String generateTsEnum(final ClassEnumModel model, final TsClassElementGroup tsGroup) {
final TsClassElement tsParentModel = tsGroup.find(model);
return tsParentModel.zodName;
}
private static String generateTsObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) {
final TsClassElement tsParentModel = tsGroup.find(model);
return tsParentModel.zodName;
}
private static String generateTsList(final ClassListModel model, final TsClassElementGroup tsGroup) {
final StringBuilder out = new StringBuilder();
out.append("zod.array(");
if (model.valueModel instanceof final ClassListModel fieldListModel) {
final String tmp = generateTsList(fieldListModel, tsGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassMapModel fieldMapModel) {
final String tmp = generateTsMap(fieldMapModel, tsGroup);
out.append(tmp);
} else if (model.valueModel instanceof final ClassObjectModel fieldObjectModel) {
final String tmp = generateTsObject(fieldObjectModel, tsGroup);
out.append(tmp);
}
out.append(")");
return out.toString();
}
public void generateFile(final String pathPackage, final TsClassElementGroup tsGroup) throws IOException {
if (this.nativeType == DefinedPosition.NATIVE) {
return;
}
final ClassModel model = this.models.get(0);
String data = "";
if (this.nativeType == DefinedPosition.BASIC && model instanceof ClassObjectModel) {
data = generateBaseObject();
} else if (model instanceof final ClassEnumModel modelEnum) {
data = generateEnum(modelEnum, tsGroup);
} else if (model instanceof final ClassObjectModel modelObject) {
data = generateObject(modelObject, tsGroup);
}
final Path path = Paths.get(pathPackage + File.separator + "model");
if (Files.notExists(path)) {
Files.createDirectories(path);
}
final FileWriter myWriter = new FileWriter(
pathPackage + File.separator + "model" + File.separator + this.fileName + ".ts");
myWriter.write(data);
myWriter.close();
}
private static String generateLocalModelBase(final ClassModel model, final TsClassElementGroup tsGroup)
throws IOException {
if (model instanceof final ClassObjectModel objectModel) {
return generateTsObject(objectModel, tsGroup);
}
if (model instanceof final ClassEnumModel enumModel) {
return generateTsEnum(enumModel, tsGroup);
}
if (model instanceof final ClassListModel listModel) {
return generateTsList(listModel, tsGroup);
}
if (model instanceof final ClassMapModel mapModel) {
return generateTsMap(mapModel, tsGroup);
}
return "";
}
public static String generateLocalModel(
final String ModelName,
final List<ClassModel> models,
final TsClassElementGroup tsGroup) throws IOException {
if (models.size() == 1) {
if (models.get(0) instanceof ClassObjectModel) {
return null;
}
if (models.get(0) instanceof ClassEnumModel) {
return null;
}
}
final StringBuilder out = new StringBuilder();
out.append("export const Zod");
out.append(ModelName);
out.append(" = ");
if (models.size() == 1) {
out.append(generateLocalModelBase(models.get(0), tsGroup));
out.append(";");
} else {
out.append("z.union([\n");
for (final ClassModel model : models) {
out.append("\t");
out.append(generateLocalModelBase(models.get(0), tsGroup));
out.append(",\n");
}
out.append("]);");
}
//model.getDependencyModels()
out.append(generateZodInfer(ModelName, "Zod" + ModelName));
out.append(generateExportCheckFunction("is" + ModelName, ModelName, "Zod" + ModelName));
return out.toString();
}
}

View File

@ -0,0 +1,27 @@
package org.kar.archidata.externalRestApi.typescript;
import java.util.List;
import org.kar.archidata.externalRestApi.model.ClassModel;
public class TsClassElementGroup {
private final List<TsClassElement> tsElements;
public List<TsClassElement> getTsElements() {
return this.tsElements;
}
public TsClassElementGroup(final List<TsClassElement> tsElements) {
this.tsElements = tsElements;
}
public TsClassElement find(final ClassModel model) {
for (final TsClassElement elem : this.tsElements) {
if (elem.isCompatible(model)) {
return elem;
}
}
return null;
}
}

View File

@ -12,6 +12,7 @@ import java.util.Map.Entry;
import org.kar.archidata.annotation.security.PermitTokenInURI; import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.catcher.RestErrorResponse; import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.exception.SystemException;
import org.kar.archidata.model.UserByToken; import org.kar.archidata.model.UserByToken;
import org.kar.archidata.tools.JWTWrapper; import org.kar.archidata.tools.JWTWrapper;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -23,6 +24,7 @@ import jakarta.annotation.Priority;
import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Priorities; import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ContainerRequestFilter;
@ -42,18 +44,40 @@ public class AuthenticationFilter implements ContainerRequestFilter {
@Context @Context
private ResourceInfo resourceInfo; private ResourceInfo resourceInfo;
protected final String applicationName; protected final String applicationName;
protected final String issuer;
public static final String AUTHENTICATION_SCHEME = "Bearer"; public static final String AUTHENTICATION_SCHEME = "Bearer";
public static final String APIKEY = "ApiKey"; public static final String APIKEY = "ApiKey";
public AuthenticationFilter(final String applicationName) { public AuthenticationFilter(final String applicationName) {
this.applicationName = applicationName; this.applicationName = applicationName;
this.issuer = "KarAuth";
}
public AuthenticationFilter(final String applicationName, final String issuer) {
this.applicationName = applicationName;
this.issuer = issuer;
}
public String getRequestedPath(final ContainerRequestContext requestContext) {
final Class<?> resourceClass = this.resourceInfo.getResourceClass();
final Method resourceMethod = this.resourceInfo.getResourceMethod();
final String classPath = resourceClass.isAnnotationPresent(Path.class)
? resourceClass.getAnnotation(Path.class).value()
: "";
final String methodPath = resourceMethod.isAnnotationPresent(Path.class)
? resourceMethod.getAnnotation(Path.class).value()
: "";
final String fullPath = (classPath.startsWith("/") ? "" : "/") + classPath
+ (methodPath.startsWith("/") ? "" : "/") + methodPath;
return fullPath;
} }
@Override @Override
public void filter(final ContainerRequestContext requestContext) throws IOException { public void filter(final ContainerRequestContext requestContext) throws IOException {
/* logger.debug("-----------------------------------------------------"); logger.debug("---- Check if have authorization ----"); /* logger.debug("-----------------------------------------------------"); logger.debug("---- Check if have authorization ----");
* logger.debug("-----------------------------------------------------"); logger.debug(" for:{}", requestContext.getUriInfo().getPath()); */ * logger.debug("-----------------------------------------------------"); logger.debug(" for:{}", requestContext.getUriInfo().getPath()); */
final Method method = this.resourceInfo.getResourceMethod(); final Method method = this.resourceInfo.getResourceMethod();
// Access denied for all // Access denied for all
if (method.isAnnotationPresent(DenyAll.class)) { if (method.isAnnotationPresent(DenyAll.class)) {
@ -140,12 +164,13 @@ public class AuthenticationFilter implements ContainerRequestFilter {
final List<String> roles = Arrays.asList(rolesAnnotation.value()); final List<String> roles = Arrays.asList(rolesAnnotation.value());
// check if the user have the right: // check if the user have the right:
boolean haveRight = false; boolean haveRight = false;
for (final String role : roles) { try {
if (userContext.isUserInRole(role)) { haveRight = checkRight(requestContext, userContext, roles);
haveRight = true; } catch (final SystemException e) {
break; // TODO Auto-generated catch block
} e.printStackTrace();
} }
// Is user valid? // Is user valid?
if (!haveRight) { if (!haveRight) {
LOGGER.error("REJECTED not enought right : {} require: {}", requestContext.getUriInfo().getPath(), roles); LOGGER.error("REJECTED not enought right : {} require: {}", requestContext.getUriInfo().getPath(), roles);
@ -157,6 +182,18 @@ public class AuthenticationFilter implements ContainerRequestFilter {
// logger.debug("Get local user : {} / {}", user, userByToken); // logger.debug("Get local user : {} / {}", user, userByToken);
} }
protected boolean checkRight(
final ContainerRequestContext requestContext,
final MySecurityContext userContext,
final List<String> roles) throws SystemException {
for (final String role : roles) {
if (userContext.isUserInRole(this.applicationName + "/" + role)) {
return true;
}
}
return false;
}
private boolean isTokenBasedAuthentication(final String authorizationHeader) { private boolean isTokenBasedAuthentication(final String authorizationHeader) {
// Check if the Authorization header is valid // Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace // It must not be null and must be prefixed with "Bearer" plus a whitespace
@ -193,7 +230,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
// must be override to be good implementation // must be override to be good implementation
protected UserByToken validateJwtToken(final String authorization) throws Exception { protected UserByToken validateJwtToken(final String authorization) throws Exception {
// logger.debug(" validate token : " + authorization); // logger.debug(" validate token : " + authorization);
final JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null); final JWTClaimsSet ret = JWTWrapper.validateToken(authorization, this.issuer, null);
// check the token is valid !!! (signed and coherent issuer... // check the token is valid !!! (signed and coherent issuer...
if (ret == null) { if (ret == null) {
LOGGER.error("The token is not valid: '{}'", authorization); LOGGER.error("The token is not valid: '{}'", authorization);
@ -208,13 +245,16 @@ public class AuthenticationFilter implements ContainerRequestFilter {
user.type = UserByToken.TYPE_USER; user.type = UserByToken.TYPE_USER;
final Object rowRight = ret.getClaim("right"); final Object rowRight = ret.getClaim("right");
if (rowRight != null) { if (rowRight != null) {
final Map<String, Map<String, Object>> rights = (Map<String, Map<String, Object>>) ret.getClaim("right"); LOGGER.info("Detect right in Authentication Filter: {}", rowRight);
user.right = (Map<String, Map<String, Object>>) 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);
} else { } else {
LOGGER.error("Connect with no right for this application='{}' full Right='{}'", this.applicationName, LOGGER.error("Connect with no right for this application='{}' full Right='{}'", this.applicationName,
rights); rights);
} }
*/
} }
// logger.debug("request user: '{}' right: '{}' row='{}'", userUID, user.right, rowRight); // logger.debug("request user: '{}' right: '{}' row='{}'", userUID, user.right, rowRight);
return user; return user;

View File

@ -16,9 +16,12 @@ public class CORSFilter implements ContainerResponseFilter {
// System.err.println("filter cors ..." + request.toString()); // System.err.println("filter cors ..." + request.toString());
response.getHeaders().add("Access-Control-Allow-Origin", "*"); response.getHeaders().add("Access-Control-Allow-Origin", "*");
response.getHeaders().add("Access-Control-Allow-Range", "bytes");
response.getHeaders().add("access-control-expose-headers", "range");
response.getHeaders().add("Access-Control-Allow-Headers", response.getHeaders().add("Access-Control-Allow-Headers",
"Origin, content-type, Content-type, Accept, Authorization, mime-type, filename"); "Origin, content-type, Content-type, Accept, Authorization, mime-type, filename, Range");
response.getHeaders().add("Access-Control-Allow-Credentials", "true"); response.getHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD"); response.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, ARCHIVE, RESTORE, OPTIONS, HEAD");
} }
} }

View File

@ -1,13 +1,17 @@
package org.kar.archidata.filter; package org.kar.archidata.filter;
import java.security.Principal; import java.security.Principal;
import java.util.Set;
import org.kar.archidata.model.UserByToken; import org.kar.archidata.model.UserByToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
// https://simplapi.wordpress.com/2015/09/19/jersey-jax-rs-securitycontext-in-action/ // https://simplapi.wordpress.com/2015/09/19/jersey-jax-rs-securitycontext-in-action/
class MySecurityContext implements SecurityContext { public class MySecurityContext implements SecurityContext {
private static final Logger LOGGER = LoggerFactory.getLogger(MySecurityContext.class);
private final GenericContext contextPrincipale; private final GenericContext contextPrincipale;
private final String sheme; private final String sheme;
@ -22,17 +26,128 @@ class MySecurityContext implements SecurityContext {
return this.contextPrincipale; return this.contextPrincipale;
} }
@Override public Object getRightOfRoleInGroup(final String group, final String role) {
public boolean isUserInRole(final String role) {
if (this.contextPrincipale.userByToken != null) { if (this.contextPrincipale.userByToken != null) {
final Object value = this.contextPrincipale.userByToken.right.get(role); return this.contextPrincipale.userByToken.getRight(group, role);
if (value instanceof final Boolean ret) { }
return ret; return null;
} }
public Set<String> getGroups() {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.getGroups();
}
return Set.of();
}
public boolean groupExist(final String group) {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.groupExist(group);
} }
return false; return false;
} }
// Not sure the Long type is definitive.
public Long getUserID() {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.id;
}
return null;
}
public boolean checkRightInGroup(
final String group,
final String role,
final boolean needRead,
final boolean needWrite) {
if ("USER".equals(role)) {
if (groupExist(group)) {
return true;
}
return false;
}
// get associated Roles:
final Object rightPart = getRightOfRoleInGroup(group, role);
LOGGER.info("detect : {}", 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;
}
if (!needRead && needWrite && dataRight == PartRight.WRITE.getValue()) {
return true;
}
if (needRead && !needWrite && dataRight == PartRight.READ.getValue()) {
return true;
}
return false;
}
@Override
public boolean isUserInRole(final String role) {
String roleEdit = role;
boolean needRead = false;
boolean needWrite = false;
// Check if the API overwrite the right needed for this API.
if (roleEdit.contains(":")) {
if (roleEdit.endsWith(":w")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 2);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needWrite = true;
} else if (roleEdit.endsWith(":r")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 2);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needRead = true;
} else if (roleEdit.endsWith(":rw")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 3);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needRead = true;
needWrite = true;
} else {
LOGGER.error("Request check right of an unknow right mode: {} (after ':')", roleEdit);
return false;
}
}
if (roleEdit.contains("/")) {
final String[] elements = roleEdit.split("/");
return checkRightInGroup(elements[0], elements[1], needRead, needWrite);
}
// Special case, if the token is valid, it is an USER ...
if ("USER".equals(roleEdit)) {
return true;
}
return checkRightInGroup("?system?", roleEdit, needRead, needWrite);
}
public Object getRole(final String role) {
LOGGER.info("contextPrincipale={}", this.contextPrincipale);
if (this.contextPrincipale.userByToken != null) {
LOGGER.info("contextPrincipale.userByToken={}", this.contextPrincipale.userByToken);
LOGGER.info("contextPrincipale.userByToken.right={}", this.contextPrincipale.userByToken.right);
return this.contextPrincipale.userByToken.right.get(role);
}
return null;
}
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return "https".equalsIgnoreCase(this.sheme); return "https".equalsIgnoreCase(this.sheme);

View File

@ -0,0 +1,30 @@
package org.kar.archidata.filter;
import com.fasterxml.jackson.annotation.JsonValue;
public enum PartRight {
NONE(0), //
READ(1), //
WRITE(2), //
READ_WRITE(3);
private final int value;
PartRight(final int value) {
this.value = value;
}
@JsonValue
public int getValue() {
return this.value;
}
public static PartRight fromValue(final int value) {
for (final PartRight element : values()) {
if (element.getValue() == value) {
return element;
}
}
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
}
}

View File

@ -14,6 +14,8 @@ import org.kar.archidata.migration.model.Migration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class MigrationEngine { public class MigrationEngine {
final static Logger LOGGER = LoggerFactory.getLogger(MigrationEngine.class); final static Logger LOGGER = LoggerFactory.getLogger(MigrationEngine.class);
@ -103,6 +105,8 @@ public class MigrationEngine {
/** Process the automatic migration of the system /** Process the automatic migration of the system
* @param config SQL connection for the migration * @param config SQL connection for the migration
* @throws IOException Error if access on the DB */ * @throws IOException Error if access on the DB */
@SuppressFBWarnings({ "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING",
"SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE" })
public void migrateErrorThrow(final DBConfig config) throws MigrationException { public void migrateErrorThrow(final DBConfig config) throws MigrationException {
LOGGER.info("Execute migration ... [BEGIN]"); LOGGER.info("Execute migration ... [BEGIN]");
// check the integrity of the migrations: // check the integrity of the migrations:
@ -318,12 +322,12 @@ public class MigrationEngine {
boolean find = false; boolean find = false;
for (int iii = this.datas.size() - 1; iii >= 0; iii--) { for (int iii = this.datas.size() - 1; iii >= 0; iii--) {
if (!find) { if (!find) {
if (this.datas.get(iii).getName() == currentVersion.name) { if (this.datas.get(iii).getName().equals(currentVersion.name)) {
find = true; find = true;
} }
continue; continue;
} }
if (this.datas.get(iii).getName() == currentVersion.name) { if (this.datas.get(iii).getName().equals(currentVersion.name)) {
break; break;
} }
toApply.add(this.datas.get(iii)); toApply.add(this.datas.get(iii));

View File

@ -4,8 +4,8 @@ import org.kar.archidata.annotation.DataDeleted;
import org.kar.archidata.annotation.DataNotRead; import org.kar.archidata.annotation.DataNotRead;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
public class GenericDataSoftDelete extends GenericData { public class GenericDataSoftDelete extends GenericData {
@ -13,7 +13,7 @@ public class GenericDataSoftDelete extends GenericData {
@Column(nullable = false) @Column(nullable = false)
@DefaultValue("'0'") @DefaultValue("'0'")
@DataDeleted @DataDeleted
@NotNull
@Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true)
@Nullable
public Boolean deleted = null; public Boolean deleted = null;
} }

View File

@ -9,23 +9,23 @@ import org.kar.archidata.annotation.UpdateTimestamp;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
public class GenericTiming { public class GenericTiming {
@DataNotRead @DataNotRead
@CreationTimestamp @CreationTimestamp
@Column(nullable = false) @Column(nullable = false)
@NotNull
@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", required = false, example = "2000-01-23T01:23:45.678+01:00", readOnly = true)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@Nullable
public Date createdAt = null; public Date createdAt = null;
@DataNotRead @DataNotRead
@UpdateTimestamp @UpdateTimestamp
@Column(nullable = false) @Column(nullable = false)
@NotNull
@Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true) @Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
// public Instant updatedAt = null; // public Instant updatedAt = null;
@Nullable
public Date updatedAt = null; public Date updatedAt = null;
} }

View File

@ -5,6 +5,7 @@ import java.util.UUID;
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.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
public class UUIDGenericData extends GenericTiming { public class UUIDGenericData extends GenericTiming {
@ -12,5 +13,6 @@ public class UUIDGenericData extends GenericTiming {
@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", required = false, readOnly = true, example = "e6b33c1c-d24d-11ee-b616-02420a030102")
@NotNull
public UUID uuid = null; public UUID uuid = null;
} }

View File

@ -4,8 +4,8 @@ import org.kar.archidata.annotation.DataDeleted;
import org.kar.archidata.annotation.DataNotRead; import org.kar.archidata.annotation.DataNotRead;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
public class UUIDGenericDataSoftDelete extends UUIDGenericData { public class UUIDGenericDataSoftDelete extends UUIDGenericData {
@ -13,7 +13,7 @@ public class UUIDGenericDataSoftDelete extends UUIDGenericData {
@Column(nullable = false) @Column(nullable = false)
@DefaultValue("'0'") @DefaultValue("'0'")
@DataDeleted @DataDeleted
@NotNull
@Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true)
@Nullable
public Boolean deleted = null; public Boolean deleted = null;
} }

View File

@ -16,6 +16,7 @@ CREATE TABLE `user` (
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List; import java.util.List;
import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
@ -24,36 +25,42 @@ import com.fasterxml.jackson.annotation.JsonFormat;
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.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@Table(name = "user") @Table(name = "user")
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends GenericDataSoftDelete { public class User extends GenericDataSoftDelete {
@NotNull
@Column(length = 128) @Column(length = 128)
@Size(min = 3, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9-_ \\.]+$")
public String login = null; public String login = null;
@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 Timestamp lastConnection = null; public Timestamp lastConnection = null;
@DefaultValue("'0'")
@Column(nullable = false)
public boolean admin = false;
@DefaultValue("'0'") @DefaultValue("'0'")
@Column(nullable = false) @Column(nullable = false)
public boolean blocked = false; public boolean blocked = false;
@DefaultValue("'0'") @Column(length = 512)
@Column(nullable = false) public String blockedReason;
public boolean removed = false;
@Schema(description = "List of Id of the specific covers") @Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class) @DataJson(targetEntity = Data.class)
public List<Long> covers; @Nullable
public List<UUID> covers;
@Override @Override
public String toString() { public String toString() {
return "User [login=" + this.login + ", last=" + this.lastConnection + ", admin=" + this.admin + "]"; return "User [login=" + this.login + ", last=" + this.lastConnection + ", blocked=" + this.blocked
+ ", blockedReason=" + this.blockedReason + "]";
} }
} }

View File

@ -2,6 +2,7 @@ package org.kar.archidata.model;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class UserByToken { public class UserByToken {
public static final int TYPE_USER = -1; public static final int TYPE_USER = -1;
@ -13,16 +14,38 @@ public class UserByToken {
public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID
public String name = null; public String name = null;
// Right map // Right map
public Map<String, Object> right = new HashMap<>(); public Map<String, Map<String, Object>> right = new HashMap<>();
public boolean hasRight(final String key, final Object value) { public Set<String> getGroups() {
if (!this.right.containsKey(key)) { return this.right.keySet();
}
public boolean groupExist(final String group) {
if (!this.right.containsKey(group)) {
return false;
}
return this.right.containsKey(group);
}
public Object getRight(final String group, final String key) {
if (!this.right.containsKey(group)) {
return null;
}
final Map<String, Object> rightGroup = this.right.get(group);
if (!rightGroup.containsKey(key)) {
return null;
}
return rightGroup.get(key);
}
public boolean hasRight(final String group, final String key, final Object value) {
final Object data = getRight(group, key);
if (data == null) {
return false; return false;
} }
final Object data = this.right.get(key);
if (data instanceof final Boolean elem) { if (data instanceof final Boolean elem) {
if (value instanceof final Boolean castVal) { if (value instanceof final Boolean castVal) {
if (elem == castVal) { if (elem.equals(castVal)) {
return true; return true;
} }
} }
@ -38,7 +61,7 @@ public class UserByToken {
} }
if (data instanceof final Long elem) { if (data instanceof final Long elem) {
if (value instanceof final Long castVal) { if (value instanceof final Long castVal) {
if (elem == castVal) { if (elem.equals(castVal)) {
return true; return true;
} }
} }
@ -46,7 +69,7 @@ public class UserByToken {
} }
if (data instanceof final Double elem) { if (data instanceof final Double elem) {
if (value instanceof final Double castVal) { if (value instanceof final Double castVal) {
if (elem == castVal) { if (elem.equals(castVal)) {
return true; return true;
} }
} }

View File

@ -11,9 +11,11 @@ import java.nio.file.StandardCopyOption;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.apache.tika.Tika;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.api.DataResource; import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
@ -22,10 +24,15 @@ import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.ReadAllColumn; import org.kar.archidata.dataAccess.options.ReadAllColumn;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
public class DataTools { public class DataTools {
@ -35,6 +42,9 @@ public class DataTools {
public final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks public final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks
/** Upload some data */ /** Upload some data */
private static long tmpFolderId = 1; private static long tmpFolderId = 1;
public final static String[] SUPPORTED_IMAGE_MIME_TYPE = { "image/jpeg", "image/png", "image/webp" };
public final static String[] SUPPORTED_AUDIO_MIME_TYPE = { "audio/x-matroska" };
public final static String[] SUPPORTED_VIDEO_MIME_TYPE = { "video/x-matroska", "video/webm" };
public static void createFolder(final String path) throws IOException { public static void createFolder(final String path) throws IOException {
if (!Files.exists(java.nio.file.Path.of(path))) { if (!Files.exists(java.nio.file.Path.of(path))) {
@ -89,20 +99,12 @@ public class DataTools {
return null; return null;
} }
public static Data createNewData(final long tmpUID, final String originalFileName, final String sha512) public static Data createNewData(
throws IOException, SQLException { final long tmpUID,
// determine mime type: final String originalFileName,
String mimeType = ""; final String sha512,
final String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1); final String mimeType) throws IOException, SQLException {
mimeType = switch (extension.toLowerCase()) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "webp" -> "image/webp";
case "mka" -> "audio/x-matroska";
case "mkv" -> "video/x-matroska";
case "webm" -> "video/webm";
default -> throw new IOException("Can not find the mime type of data input: '" + extension + "'");
};
final String tmpPath = getTmpFileInData(tmpUID); final String tmpPath = getTmpFileInData(tmpUID);
final long fileSize = Files.size(Paths.get(tmpPath)); final long fileSize = Files.size(Paths.get(tmpPath));
Data out = new Data(); Data out = new Data();
@ -128,6 +130,23 @@ public class DataTools {
return out; return out;
} }
public static Data createNewData(final long tmpUID, final String originalFileName, final String sha512)
throws IOException, SQLException {
// determine mime type:
String mimeType = "";
final String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
mimeType = switch (extension.toLowerCase()) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "webp" -> "image/webp";
case "mka" -> "audio/x-matroska";
case "mkv" -> "video/x-matroska";
case "webm" -> "video/webm";
default -> throw new IOException("Can not find the mime type of data input: '" + extension + "'");
};
return createNewData(tmpUID, originalFileName, sha512, mimeType);
}
public static void undelete(final UUID id) { public static void undelete(final UUID id) {
try { try {
DataAccess.unsetDelete(Data.class, id); DataAccess.unsetDelete(Data.class, id);
@ -141,6 +160,10 @@ public class DataTools {
return saveFile(uploadedInputStream, getTmpFileInData(idData)); return saveFile(uploadedInputStream, getTmpFileInData(idData));
} }
public static String saveTemporaryFile(final byte[] uploadedInputStream, final long idData) {
return saveFile(uploadedInputStream, getTmpFileInData(idData));
}
public static void removeTemporaryFile(final long idData) { public static void removeTemporaryFile(final long idData) {
final String filepath = getTmpFileInData(idData); final String filepath = getTmpFileInData(idData);
if (Files.exists(Paths.get(filepath))) { if (Files.exists(Paths.get(filepath))) {
@ -186,6 +209,31 @@ public class DataTools {
return out; return out;
} }
public static String saveFile(final byte[] bytes, final String serverLocation) {
String out = "";
try {
final OutputStream outpuStream = new FileOutputStream(new File(serverLocation));
final MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(bytes, 0, bytes.length);
outpuStream.write(bytes, 0, bytes.length);
LOGGER.info("Flush input stream ... {}", serverLocation);
outpuStream.flush();
outpuStream.close();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
} catch (final IOException ex) {
LOGGER.error("Can not write in temporary file ... ");
ex.printStackTrace();
} catch (final NoSuchAlgorithmException ex) {
LOGGER.error("Can not find sha512 algorithms");
ex.printStackTrace();
}
return out;
}
// curl http://localhost:9993/api/users/3 // curl http://localhost:9993/api/users/3
// @Secured // @Secured
/* @GET /* @GET
@ -211,67 +259,144 @@ public class DataTools {
if (data.contentEquals("null")) { if (data.contentEquals("null")) {
return null; return null;
} }
if (data.contentEquals("undefined")) {
return null;
}
return data; return data;
} }
public static <CLASS_TYPE, ID_TYPE> Response uploadCover( public static String getMimeType(final byte[] data) {
final Tika tika = new Tika();
final String mimeType = tika.detect(data);
return mimeType;
}
public static <CLASS_TYPE, ID_TYPE> void uploadCoverFromUri(
final Class<CLASS_TYPE> clazz, final Class<CLASS_TYPE> clazz,
final ID_TYPE id, final ID_TYPE id,
String fileName, final String url) throws Exception {
final InputStream fileInputStream,
final FormDataContentDisposition fileMetaData) {
try {
// correct input string stream :
fileName = multipartCorrection(fileName);
// public NodeSmall uploadFile(final FormDataMultiPart form) { LOGGER.info(" - id: {}", id);
LOGGER.info("Upload media file: {}", fileMetaData); LOGGER.info(" - url: {} ", url);
LOGGER.info(" - id: {}", id); final CLASS_TYPE media = DataAccess.get(clazz, id);
LOGGER.info(" - file_name: ", fileName); if (media == null) {
LOGGER.info(" - fileInputStream: {}", fileInputStream); throw new InputException(clazz.getCanonicalName(),
LOGGER.info(" - fileMetaData: {}", fileMetaData); "[" + id.toString() + "] Id does not exist or removed...");
final CLASS_TYPE media = DataAccess.get(clazz, id); }
if (media == null) { // Download data:
return Response.notModified("Media Id does not exist or removed...").build();
} final Client client = ClientBuilder.newClient();
byte[] dataResponse = null;
final long tmpUID = getTmpDataId(); try {
final String sha512 = saveTemporaryFile(fileInputStream, tmpUID); final WebTarget target = client.target(url);
Data data = getWithSha512(sha512); final Response response = target.request().get();
if (data == null) { if (response.getStatus() != 200) {
LOGGER.info("Need to add the data in the BDD ... "); throw new FailException(Response.Status.BAD_GATEWAY,
try { clazz.getCanonicalName() + "[" + id.toString() + "] Can not download the media");
data = createNewData(tmpUID, fileName, sha512); }
} catch (final IOException ex) { dataResponse = response.readEntity(byte[].class);
removeTemporaryFile(tmpUID); } catch (final Exception ex) {
ex.printStackTrace(); throw new FailException(Response.Status.INTERNAL_SERVER_ERROR,
return Response.notModified("can not create input media").build(); clazz.getCanonicalName() + "[" + id.toString() + "] can not create input media", ex);
} catch (final SQLException ex) { }
ex.printStackTrace(); if (dataResponse == null) {
removeTemporaryFile(tmpUID); throw new FailException(Response.Status.NOT_ACCEPTABLE,
return Response.notModified("Error in SQL insertion ...").build(); clazz.getCanonicalName() + "[" + id.toString() + "] Data does not exist");
} }
} else if (data.deleted) { if (dataResponse.length == 0 || dataResponse.length == 50 * 1024 * 1024) {
LOGGER.error("Data already exist but deleted"); throw new FailException(Response.Status.NOT_ACCEPTABLE, clazz.getCanonicalName() + "[" + id.toString()
undelete(data.uuid); + "] Data size is not correct " + dataResponse.length);
data.deleted = false; }
} else {
LOGGER.error("Data already exist ... all good"); final long tmpUID = getTmpDataId();
} final String sha512 = saveTemporaryFile(dataResponse, tmpUID);
// Fist step: retrieve all the Id of each parents:... Data data = getWithSha512(sha512);
LOGGER.info("Find typeNode"); final String mimeType = getMimeType(dataResponse);
if (id instanceof final Long idLong) { if (!Arrays.asList(SUPPORTED_IMAGE_MIME_TYPE).contains(mimeType)) {
AddOnDataJson.addLink(clazz, idLong, "covers", data.uuid); throw new FailException(Response.Status.NOT_ACCEPTABLE,
} else if (id instanceof final UUID idUUID) { clazz.getCanonicalName() + "[" + id.toString() + "] Data CoverType is not accesptable: " + mimeType
AddOnDataJson.addLink(clazz, idUUID, "covers", data.uuid); + "support only: " + String.join(", ", SUPPORTED_IMAGE_MIME_TYPE));
} else {
throw new IOException("Fail to add Cover can not detect type..."); }
} if (data == null) {
return Response.ok(DataAccess.get(clazz, id)).build(); LOGGER.info("Need to add the data in the BDD ... ");
} catch (final Exception ex) { try {
System.out.println("Cat ann unexpected error ... "); data = createNewData(tmpUID, url, sha512, mimeType);
ex.printStackTrace(); } catch (final IOException ex) {
removeTemporaryFile(tmpUID);
throw new FailException(Response.Status.NOT_MODIFIED,
clazz.getCanonicalName() + "[" + id.toString() + "] can not create input media", ex);
} catch (final SQLException ex) {
removeTemporaryFile(tmpUID);
throw new FailException(Response.Status.NOT_MODIFIED,
clazz.getCanonicalName() + "[" + id.toString() + "] Error in SQL insertion", ex);
}
} else if (data.deleted) {
LOGGER.error("Data already exist but deleted");
undelete(data.uuid);
data.deleted = false;
} else {
LOGGER.error("Data already exist ... all good");
}
// Fist step: retrieve all the Id of each parents:...
LOGGER.info("Find typeNode");
if (id instanceof final Long idLong) {
AddOnDataJson.addLink(clazz, idLong, "covers", data.uuid);
} else if (id instanceof final UUID idUUID) {
AddOnDataJson.addLink(clazz, idUUID, "covers", data.uuid);
} else {
throw new IOException("Fail to add Cover can not detect type...");
}
}
public static <CLASS_TYPE, ID_TYPE> void uploadCover(
final Class<CLASS_TYPE> clazz,
final ID_TYPE id,
final InputStream fileInputStream,
final FormDataContentDisposition fileMetaData) throws Exception {
// public NodeSmall uploadFile(final FormDataMultiPart form) {
LOGGER.info("Upload media file: {}", fileMetaData);
LOGGER.info(" - id: {}", id);
LOGGER.info(" - file_name: {} ", fileMetaData.getFileName());
LOGGER.info(" - fileInputStream: {}", fileInputStream);
LOGGER.info(" - fileMetaData: {}", fileMetaData);
final CLASS_TYPE media = DataAccess.get(clazz, id);
if (media == null) {
throw new InputException(clazz.getCanonicalName(),
"[" + id.toString() + "] Id does not exist or removed...");
}
final long tmpUID = getTmpDataId();
final String sha512 = saveTemporaryFile(fileInputStream, tmpUID);
Data data = getWithSha512(sha512);
if (data == null) {
LOGGER.info("Need to add the data in the BDD ... ");
try {
data = createNewData(tmpUID, fileMetaData.getFileName(), sha512);
} catch (final IOException ex) {
removeTemporaryFile(tmpUID);
throw new FailException(Response.Status.NOT_MODIFIED,
clazz.getCanonicalName() + "[" + id.toString() + "] can not create input media", ex);
} catch (final SQLException ex) {
removeTemporaryFile(tmpUID);
throw new FailException(Response.Status.NOT_MODIFIED,
clazz.getCanonicalName() + "[" + id.toString() + "] Error in SQL insertion", ex);
}
} else if (data.deleted) {
LOGGER.error("Data already exist but deleted");
undelete(data.uuid);
data.deleted = false;
} else {
LOGGER.error("Data already exist ... all good");
}
// Fist step: retrieve all the Id of each parents:...
LOGGER.info("Find typeNode");
if (id instanceof final Long idLong) {
AddOnDataJson.addLink(clazz, idLong, "covers", data.uuid);
} else if (id instanceof final UUID idUUID) {
AddOnDataJson.addLink(clazz, idUUID, "covers", data.uuid);
} else {
throw new IOException("Fail to add Cover can not detect type...");
} }
return Response.serverError().build();
} }
} }

View File

@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
@ -106,7 +107,7 @@ public class JWTWrapper {
} }
in.close(); in.close();
// print result // print result
// LOGGER.debug(response.toString()); LOGGER.debug(response.toString());
final ObjectMapper mapper = new ObjectMapper(); final ObjectMapper mapper = new ObjectMapper();
final PublicKey values = mapper.readValue(response.toString(), PublicKey.class); final PublicKey values = mapper.readValue(response.toString(), PublicKey.class);
rsaPublicJWK = RSAKey.parse(values.key); rsaPublicJWK = RSAKey.parse(values.key);
@ -219,6 +220,10 @@ public class JWTWrapper {
try { try {
// On the consumer side, parse the JWS and verify its RSA signature // On the consumer side, parse the JWS and verify its RSA signature
final SignedJWT signedJWT = SignedJWT.parse(signedToken); final SignedJWT signedJWT = SignedJWT.parse(signedToken);
if (signedJWT == null) {
LOGGER.error("FAIL to parse signing");
return null;
}
if (ConfigBaseVariable.getTestMode() && signedToken.endsWith(TestSigner.test_signature)) { if (ConfigBaseVariable.getTestMode() && signedToken.endsWith(TestSigner.test_signature)) {
LOGGER.warn("Someone use a test token: {}", signedToken); LOGGER.warn("Someone use a test token: {}", signedToken);
} else if (rsaPublicJWK == null) { } else if (rsaPublicJWK == null) {
@ -226,7 +231,7 @@ public class JWTWrapper {
if (!ConfigBaseVariable.getTestMode()) { if (!ConfigBaseVariable.getTestMode()) {
return null; return null;
} }
final String rawSignature = signedJWT.getSigningInput().toString(); final String rawSignature = new String(signedJWT.getSigningInput(), StandardCharsets.UTF_8);
if (rawSignature.equals(TestSigner.test_signature)) { if (rawSignature.equals(TestSigner.test_signature)) {
// Test token : .application.. // Test token : .application..
} else { } else {

View File

@ -16,7 +16,6 @@ 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.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.exc.MismatchedInputException;
@ -56,7 +55,9 @@ public class RESTApi {
"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(), new TypeReference<List<T>>() {});
return this.mapper.readValue(httpResponse.body(),
this.mapper.getTypeFactory().constructCollectionType(List.class, clazz));
} }
public <T> T get(final Class<T> clazz, final String urlOffset) public <T> T get(final Class<T> clazz, final String urlOffset)
@ -119,7 +120,8 @@ public class RESTApi {
} }
} }
protected <T, U> T modelSendJson(final String model, final Class<T> clazz, final String urlOffset, String body) @SuppressWarnings("unchecked")
public <T, U> T modelSendJson(final String model, final Class<T> clazz, final String urlOffset, String body)
throws RESTErrorResponseExeption, IOException, InterruptedException { throws RESTErrorResponseExeption, 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);
@ -153,6 +155,9 @@ public class RESTApi {
"Fail to get the ERROR data [" + httpResponse.statusCode() + "] " + httpResponse.body()); "Fail to get the ERROR data [" + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
if (clazz == Void.class || clazz == void.class) {
return null;
}
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T) httpResponse.body(); return (T) httpResponse.body();
} }
@ -160,7 +165,8 @@ public class RESTApi {
return this.mapper.readValue(httpResponse.body(), clazz); return this.mapper.readValue(httpResponse.body(), clazz);
} }
protected <T> T modelSendMap( @SuppressWarnings("unchecked")
public <T> T modelSendMap(
final String model, final String model,
final Class<T> clazz, final Class<T> clazz,
final String urlOffset, final String urlOffset,
@ -190,13 +196,84 @@ public class RESTApi {
"Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); "Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
if (clazz == Void.class || clazz == void.class) {
return null;
}
if (clazz.equals(String.class)) { if (clazz.equals(String.class)) {
return (T) httpResponse.body(); return (T) httpResponse.body();
} }
return this.mapper.readValue(httpResponse.body(), clazz); return this.mapper.readValue(httpResponse.body(), clazz);
} }
public <T, U> void delete(final Class<T> clazz, final String urlOffset) /**
* Call a DELETE on a REST API
* @param urlOffset Offset to call the API
*/
public void delete(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
delete(Void.class, urlOffset);
}
/**
* Call a DELETE on a REST API with retrieving some data
* @param <T> Type of data that might be received.
* @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API
* @return The parsed object received.
*/
public <T> T delete(final Class<T> clazz, final String urlOffset)
throws RESTErrorResponseExeption, IOException, InterruptedException {
return simpleRequest("DELETE", clazz, urlOffset);
}
/**
* Call an ARCHIVE on a REST API
* @param urlOffset Offset to call the API
*/
public void archive(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
archive(Void.class, urlOffset);
}
/**
* Call a ARCHIVE on a REST API with retrieving some data
* @param <T> Type of data that might be received.
* @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API
* @return The parsed object received.
*/
public <T> T archive(final Class<T> clazz, final String urlOffset)
throws RESTErrorResponseExeption, IOException, InterruptedException {
return simpleRequest("ARCHIVE", clazz, urlOffset);
}
/**
* Call an RESTORE on a REST API
* @param urlOffset Offset to call the API
*/
public void restore(final String urlOffset) throws RESTErrorResponseExeption, IOException, InterruptedException {
restore(Void.class, urlOffset);
}
/**
* Call a RESTORE on a REST API with retrieving some data
* @param <T> Type of data that might be received.
* @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API
* @return The parsed object received.
*/
public <T> T restore(final Class<T> clazz, final String urlOffset)
throws RESTErrorResponseExeption, IOException, InterruptedException {
return simpleRequest("RESTORE", clazz, urlOffset);
}
/**
* Call a key on a REST API with retrieving some data
* @param <T> Type of data that might be received.
* @param model name of the key for the REST call
* @param clazz Class model of the data that might be parsed.
* @param urlOffset Offset to call the API
* @return The parsed object received.
*/
public <T> T simpleRequest(final String model, final Class<T> clazz, final String urlOffset)
throws RESTErrorResponseExeption, IOException, InterruptedException { throws RESTErrorResponseExeption, 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)
@ -204,7 +281,7 @@ public class RESTApi {
if (this.token != null) { if (this.token != null) {
requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Bearer " + this.token); requestBuilding = requestBuilding.header(HttpHeaders.AUTHORIZATION, "Bearer " + this.token);
} }
final HttpRequest request = requestBuilding.DELETE().build(); final HttpRequest request = requestBuilding.method(model, BodyPublishers.ofString("")).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) {
try { try {
@ -216,5 +293,12 @@ public class RESTApi {
"Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); "Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
if (clazz == Void.class || clazz == void.class) {
return null;
}
if (clazz.equals(String.class)) {
return (T) httpResponse.body();
}
return this.mapper.readValue(httpResponse.body(), clazz);
} }
} }

View File

@ -0,0 +1,14 @@
package org.kar.archidata.tools;
public class StringTools {
public static String RandGeneratedStr(final int length) {
final String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz0123456789éàê_- '()";
final StringBuilder out = new StringBuilder(length);
for (int iii = 0; iii < length; iii++) {
final int chId = (int) (base.length() * Math.random());
out.append(base.charAt(chId));
}
return out.toString();
}
}

View File

@ -47,7 +47,7 @@ public class UuidUtils {
this.base = startingUUID.until(Instant.now(), ChronoUnit.SECONDS); this.base = startingUUID.until(Instant.now(), ChronoUnit.SECONDS);
final String serveurBaseUUID = System.getenv("UUID_SERVER_ID"); final String serveurBaseUUID = System.getenv("UUID_SERVER_ID");
if (serveurBaseUUID != null) { if (serveurBaseUUID != null) {
long serverId = Long.valueOf(serveurBaseUUID); long serverId = Long.parseLong(serveurBaseUUID);
serverId %= 0xFFFF; serverId %= 0xFFFF;
this.base += (serverId << (64 - 16)); this.base += (serverId << (64 - 16));
} else { } else {

View File

@ -4,377 +4,451 @@
* @license MPL-2 * @license MPL-2
*/ */
import { RestErrorResponse } from "./model" import { RestErrorResponse, isRestErrorResponse } from "./model";
export enum HTTPRequestModel { export enum HTTPRequestModel {
DELETE = 'DELETE', ARCHIVE = "ARCHIVE",
GET = 'GET', DELETE = "DELETE",
PATCH = 'PATCH', HEAD = "HEAD",
POST = 'POST', GET = "GET",
PUT = 'PUT', OPTION = "OPTION",
PATCH = "PATCH",
POST = "POST",
PUT = "PUT",
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 {
// base of the server: http(s)://my.server.org/plop/api/ // base of the server: http(s)://my.server.org/plop/api/
server: string; server: string;
// Token to access of the data. // Token to access of the data.
token?: string; token?: string;
} }
export interface RESTModel { export interface RESTModel {
// base of the local API request: "sheep/{id}". // base of the local API request: "sheep/{id}".
endPoint: string; endPoint: string;
// Type of the request. // Type of the request.
requestType?: HTTPRequestModel; requestType?: HTTPRequestModel;
// Input type requested. // Input type requested.
accept?: HTTPMimeType; accept?: HTTPMimeType;
// Content of the local data. // Content of the local data.
contentType?: HTTPMimeType; contentType?: HTTPMimeType;
// Mode of the TOKEN in URL or Header (?token:${tokenInUrl}) // Mode of the TOKEN in URL or Header (?token:${tokenInUrl})
tokenInUrl?: boolean; tokenInUrl?: boolean;
} }
export interface ModelResponseHttp { export interface ModelResponseHttp {
status: number; status: number;
data: any; data: any;
}
export function isArrayOf<TYPE>(
data: any,
typeChecker: (subData: any) => subData is TYPE,
length?: number
): data is TYPE[] {
if (!Array.isArray(data)) {
return false;
}
if (!data.every(typeChecker)) {
return false;
}
if (length !== undefined && data.length != length) {
return false;
}
return true;
} }
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;
} }
// generic progression callback // generic progression callback
export type ProgressCallback = (count: number, total: number) => void; export type ProgressCallback = (count: number, total: number) => void;
export interface RESTAbort { export interface RESTAbort {
abort?: () => boolean abort?: () => boolean;
} }
// Rest generic callback have a basic model to upload and download advancement. // Rest generic callback have a basic model to upload and download advancement.
export interface RESTCallbacks { export interface RESTCallbacks {
progressUpload?: ProgressCallback, progressUpload?: ProgressCallback;
progressDownload?: ProgressCallback, progressDownload?: ProgressCallback;
abortHandle?: RESTAbort, abortHandle?: RESTAbort;
}; }
export interface RESTRequestType { export interface RESTRequestType {
restModel: RESTModel, restModel: RESTModel;
restConfig: RESTConfig, restConfig: RESTConfig;
data?: any, data?: any;
params?: object, params?: object;
queries?: object, queries?: object;
callback?: RESTCallbacks, callbacks?: RESTCallbacks;
}; }
function replaceAll(input, searchValue, replaceValue) { function replaceAll(input, searchValue, replaceValue) {
return input.split(searchValue).join(replaceValue); return input.split(searchValue).join(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({ restModel, restConfig, params, queries }: RESTRequestType): string { export function RESTUrl({
// Create the URL PATH: restModel,
let generateUrl = `${removeTrailingSlashes(restConfig.server)}/${removeLeadingSlashes(restModel.endPoint)}`; restConfig,
if (params !== undefined) { params,
for (let key of Object.keys(params)) { queries,
generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`); }: RESTRequestType): string {
// Create the URL PATH:
let generateUrl = `${removeTrailingSlashes(
restConfig.server
)}/${removeLeadingSlashes(restModel.endPoint)}`;
if (params !== undefined) {
for (let key of Object.keys(params)) {
generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`);
}
}
if (
queries === undefined &&
(restConfig.token === undefined || restModel.tokenInUrl !== true)
) {
return generateUrl;
}
const searchParams = new URLSearchParams();
if (queries !== undefined) {
for (let key of Object.keys(queries)) {
const value = queries[key];
if (Array.isArray(value)) {
for (const element of value) {
searchParams.append(`${key}`, `${element}`);
} }
} else {
searchParams.append(`${key}`, `${value}`);
}
} }
if (queries === undefined && (restConfig.token === undefined || restModel.tokenInUrl !== true)) { }
return generateUrl; if (restConfig.token !== undefined && restModel.tokenInUrl === true) {
} searchParams.append("Authorization", `Bearer ${restConfig.token}`);
const searchParams = new URLSearchParams(); }
if (queries !== undefined) { return generateUrl + "?" + searchParams.toString();
for (let key of Object.keys(queries)) {
const value = queries[key];
if (Array.isArray(value)) {
for (let iii = 0; iii < value.length; iii++) {
searchParams.append(`${key}`, `${value[iii]}`);
}
} else {
searchParams.append(`${key}`, `${value}`);
}
}
}
if (restConfig.token !== undefined && restModel.tokenInUrl === true) {
searchParams.append('Authorization', `Bearer ${restConfig.token}`);
}
return generateUrl + "?" + searchParams.toString();
} }
export function fetchProgress(
export function fetchProgress(generateUrl: string, { method, headers, body }: { generateUrl: string,
method: HTTPRequestModel, {
headers: any, method,
body: any, headers,
}, { progressUpload, progressDownload, abortHandle }: RESTCallbacks): Promise<Response> { body,
const xhr = { }: {
io: new XMLHttpRequest() method: HTTPRequestModel;
headers: any;
body: any;
},
{ progressUpload, progressDownload, abortHandle }: RESTCallbacks
): Promise<Response> {
const xhr: {
io?: XMLHttpRequest;
} = {
io: new XMLHttpRequest(),
};
return new Promise((resolve, reject) => {
// Stream the upload progress
if (progressUpload) {
xhr.io?.upload.addEventListener("progress", (dataEvent) => {
if (dataEvent.lengthComputable) {
progressUpload(dataEvent.loaded, dataEvent.total);
}
});
} }
return new Promise((resolve, reject) => { // Stream the download progress
// Stream the upload progress if (progressDownload) {
if (progressUpload) { xhr.io?.addEventListener("progress", (dataEvent) => {
xhr.io.upload.addEventListener("progress", (dataEvent) => { if (dataEvent.lengthComputable) {
if (dataEvent.lengthComputable) { progressDownload(dataEvent.loaded, dataEvent.total);
//console.log(` ==> has a progress event: ${dataEvent.loaded} / ${dataEvent.total}`);
progressUpload(dataEvent.loaded, dataEvent.total);
}
});
} }
// Stream the download progress });
if (progressDownload) { }
xhr.io.addEventListener("progress", (dataEvent) => { if (abortHandle) {
if (dataEvent.lengthComputable) { abortHandle.abort = () => {
//console.log(` ==> download progress:: ${dataEvent.loaded} / ${dataEvent.total}`); if (xhr.io) {
progressUpload(dataEvent.loaded, dataEvent.total); console.log(`Request abort on the XMLHttpRequest: ${generateUrl}`);
} xhr.io.abort();
}); return true;
} }
if (abortHandle) { console.log(
abortHandle.abort = () => { `Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}`
if (xhr.io) { );
console.log(`Request abort on the XMLHttpRequest: ${generateUrl}`); return false;
xhr.io.abort(); };
return true; }
} // Check if we have an internal Fail:
console.log(`Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}`); xhr.io?.addEventListener("error", () => {
return false; xhr.io = undefined;
} reject(new TypeError("Failed to fetch"));
}
// Check if we have an internal Fail:
xhr.io.addEventListener('error', () => {
xhr.io = undefined;
reject(new TypeError('Failed to fetch'))
});
// Capture the end of the stream
xhr.io.addEventListener("loadend", () => {
if (xhr.io.readyState !== XMLHttpRequest.DONE) {
//console.log(` ==> READY state`);
return;
}
if (xhr.io.status === 0) {
//the stream has been aborted
reject(new TypeError('Fetch has been aborted'));
return;
}
// Stream is ended, transform in a generic response:
const response = new Response(xhr.io.response, {
status: xhr.io.status,
statusText: xhr.io.statusText
});
const headersArray = replaceAll(xhr.io.getAllResponseHeaders().trim(), "\r\n", "\n").split('\n');
headersArray.forEach(function (header) {
const firstColonIndex = header.indexOf(':');
if (firstColonIndex !== -1) {
var key = header.substring(0, firstColonIndex).trim();
var value = header.substring(firstColonIndex + 1).trim();
response.headers.set(key, value);
} else {
response.headers.set(header, "");
}
});
xhr.io = undefined;
resolve(response);
});
xhr.io.open(method, generateUrl, true);
if (!isNullOrUndefined(headers)) {
for (const [key, value] of Object.entries(headers)) {
xhr.io.setRequestHeader(key, value as string);
}
}
xhr.io.send(body);
}); });
// Capture the end of the stream
xhr.io?.addEventListener("loadend", () => {
if (xhr.io?.readyState !== XMLHttpRequest.DONE) {
return;
}
if (xhr.io?.status === 0) {
//the stream has been aborted
reject(new TypeError("Fetch has been aborted"));
return;
}
// Stream is ended, transform in a generic response:
const response = new Response(xhr.io.response, {
status: xhr.io.status,
statusText: xhr.io.statusText,
});
const headersArray = replaceAll(
xhr.io.getAllResponseHeaders().trim(),
"\r\n",
"\n"
).split("\n");
headersArray.forEach(function (header) {
const firstColonIndex = header.indexOf(":");
if (firstColonIndex !== -1) {
const key = header.substring(0, firstColonIndex).trim();
const value = header.substring(firstColonIndex + 1).trim();
response.headers.set(key, value);
} else {
response.headers.set(header, "");
}
});
xhr.io = undefined;
resolve(response);
});
xhr.io?.open(method, generateUrl, true);
if (!isNullOrUndefined(headers)) {
for (const [key, value] of Object.entries(headers)) {
xhr.io?.setRequestHeader(key, value as string);
}
}
xhr.io?.send(body);
});
} }
export function RESTRequest({ restModel, restConfig, data, params, queries, callback }: RESTRequestType): Promise<ModelResponseHttp> { export function RESTRequest({
// Create the URL PATH: restModel,
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); restConfig,
let headers: any = {}; data,
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { params,
headers['Authorization'] = `Bearer ${restConfig.token}`; queries,
callbacks,
}: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
let headers: any = {};
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) {
headers["Authorization"] = `Bearer ${restConfig.token}`;
}
if (restModel.accept !== undefined) {
headers["Accept"] = restModel.accept;
}
if (restModel.requestType !== HTTPRequestModel.GET &&
restModel.requestType !== HTTPRequestModel.ARCHIVE &&
restModel.requestType !== HTTPRequestModel.RESTORE
) {
// if Get we have not a content type, the body is empty
if (restModel.contentType !== HTTPMimeType.MULTIPART &&
restModel.contentType !== undefined
) {
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****"
headers["Content-Type"] = restModel.contentType;
} }
if (restModel.accept !== undefined) { }
headers['Accept'] = restModel.accept; let body = data;
if (restModel.contentType === HTTPMimeType.JSON) {
body = JSON.stringify(data);
} else if (restModel.contentType === HTTPMimeType.MULTIPART) {
const formData = new FormData();
for (const name in data) {
formData.append(name, data[name]);
} }
if (restModel.requestType !== HTTPRequestModel.GET) { body = formData;
// if Get we have not a content type, the body is empty }
if (restModel.contentType !== HTTPMimeType.MULTIPART) { return new Promise((resolve, reject) => {
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" let action: undefined | Promise<Response> = undefined;
headers['Content-Type'] = restModel.contentType; if (
} isNullOrUndefined(callbacks) ||
(isNullOrUndefined(callbacks.progressDownload) &&
isNullOrUndefined(callbacks.progressUpload) &&
isNullOrUndefined(callbacks.abortHandle))
) {
// No information needed: call the generic fetch interface
action = fetch(generateUrl, {
method: restModel.requestType,
headers,
body,
});
} else {
// need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x
action = fetchProgress(
generateUrl,
{
method: restModel.requestType ?? HTTPRequestModel.GET,
headers,
body,
},
callbacks
);
} }
let body = data; action
if (restModel.contentType === HTTPMimeType.JSON) { .then((response: Response) => {
body = JSON.stringify(data); if (response.status >= 200 && response.status <= 299) {
} else if (restModel.contentType === HTTPMimeType.MULTIPART) { const contentType = response.headers.get("Content-Type");
const formData = new FormData(); if (
for (const name in data) { !isNullOrUndefined(restModel.accept) &&
formData.append(name, data[name]); restModel.accept !== contentType
} ) {
body = formData reject({
} name: "Model accept type incompatible",
return new Promise((resolve, reject) => { time: Date().toString(),
let action: undefined | Promise<Response> = undefined; status: 901,
if (isNullOrUndefined(callback) message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`,
|| (isNullOrUndefined(callback.progressDownload) statusMessage: "Fetch error",
&& isNullOrUndefined(callback.progressUpload) error: "rest-tools.ts Wrong type in the message return type",
&& isNullOrUndefined(callback.abortHandle))) { } as RestErrorResponse);
// No information needed: call the generic fetch interface } else if (contentType === HTTPMimeType.JSON) {
action = fetch(generateUrl, { response
method: restModel.requestType, .json()
headers, .then((value: any) => {
body, resolve({ status: response.status, data: value });
}); })
} else { .catch((reason: Error) => {
// need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x
action = fetchProgress(generateUrl, {
method: restModel.requestType ?? HTTPRequestModel.GET,
headers,
body,
}, callback);
}
action.then((response: Response) => {
if (response.status >= 200 && response.status <= 299) {
const contentType = response.headers.get('Content-Type');
if (!isNullOrUndefined(restModel.accept) && restModel.accept !== contentType) {
reject({
time: Date().toString(),
status: 901,
error: `REST check wrong type: ${restModel.accept} != ${contentType}`,
statusMessage: "Fetch error",
message: "rest-tools.ts Wrong type in the message return type"
} as RestErrorResponse);
} else if (contentType === HTTPMimeType.JSON) {
response
.json()
.then((value: any) => {
//console.log(`RECEIVE ==> ${response.status}=${ JSON.stringify(value, null, 2)}`);
resolve({ status: response.status, data: value });
})
.catch((reason: any) => {
reject({
time: Date().toString(),
status: 902,
error: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error",
message: "rest-tools.ts Wrong message model to parse"
} as RestErrorResponse);
});
} else {
resolve({ status: response.status, data: response.body });
}
} else {
reject({ reject({
name: "API serialization error",
time: Date().toString(),
status: 902,
message: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error",
error: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse);
});
} else {
resolve({ status: response.status, data: response.body });
}
} else {
// the answer is not correct not a 2XX
// clone the response to keep the raw data if case of error:
response
.clone()
.json()
.then((value: any) => {
if (isRestErrorResponse(value)) {
reject(value);
} else {
response
.text()
.then((dataError: string) => {
reject({
name: "API serialization error",
time: Date().toString(),
status: 903,
message: `REST parse error json with wrong type fail. ${dataError}`,
statusMessage: "Fetch parse error",
error: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse);
})
.catch((reason: any) => {
reject({
name: "API serialization error",
time: Date().toString(),
status: response.status,
message: `unmanaged error model: ??? with error: ${reason}`,
statusMessage: "Fetch ERROR parse error",
error: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse);
});
}
})
.catch((reason: Error) => {
response
.text()
.then((dataError: string) => {
reject({
name: "API serialization error",
time: Date().toString(), time: Date().toString(),
status: response.status, status: response.status,
error: `${response.body}`, message: `unmanaged error model: ${dataError} with error: ${reason}`,
statusMessage: "Fetch code error", statusMessage: "Fetch ERROR TEXT parse error",
message: "rest-tools.ts Wrong return code" error: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse); } as RestErrorResponse);
} })
}).catch((error: any) => { .catch((reason: any) => {
reject({ reject({
time: Date(), name: "API serialization error",
status: 999, time: Date().toString(),
error: error, status: response.status,
statusMessage: "Fetch catch error", message: `unmanaged error model: ??? with error: ${reason}`,
message: "rest-tools.ts detect an error in the fetch request" statusMessage: "Fetch ERROR TEXT FAIL",
error: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse);
});
}); });
}); }
}); })
.catch((error: Error) => {
if (isRestErrorResponse(error)) {
reject(error);
} else {
reject({
name: "Request fail",
time: Date(),
status: 999,
message: error,
statusMessage: "Fetch catch error",
error: "rest-tools.ts detect an error in the fetch request",
});
}
});
});
} }
export function RESTRequestJson<TYPE>(
request: RESTRequestType,
export function RESTRequestJson<TYPE>(request: RESTRequestType, checker: (data: any) => data is TYPE): Promise<TYPE> { checker?: (data: any) => data is TYPE
return new Promise((resolve, reject) => { ): Promise<TYPE> {
RESTRequest(request).then((value: ModelResponseHttp) => { return new Promise((resolve, reject) => {
if (isNullOrUndefined(checker)) { RESTRequest(request)
console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`); .then((value: ModelResponseHttp) => {
resolve(value.data); if (isNullOrUndefined(checker)) {
} else if (checker(value.data)) { console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`);
resolve(value.data); resolve(value.data);
} else { } else if (checker === undefined || checker(value.data)) {
reject({ resolve(value.data);
time: Date().toString(), } else {
status: 950, reject({
error: "REST Fail to verify the data", name: "Model check fail",
statusMessage: "API cast ERROR", time: Date().toString(),
message: "api.ts Check type as fail" status: 950,
} as RestErrorResponse); error: "REST Fail to verify the data",
} statusMessage: "API cast ERROR",
}).catch((reason: RestErrorResponse) => { message: "api.ts Check type as fail",
reject(reason); } as RestErrorResponse);
}); }
}); })
} .catch((reason: RestErrorResponse) => {
export function RESTRequestJsonArray<TYPE>(request: RESTRequestType, checker: (data: any) => data is TYPE): Promise<TYPE[]> { reject(reason);
return new Promise((resolve, reject) => { });
RESTRequest(request).then((value: ModelResponseHttp) => { });
if (isArrayOf(value.data, checker)) {
resolve(value.data);
} else {
reject({
time: Date().toString(),
status: 950,
error: "REST Fail to verify the data",
statusMessage: "API cast ERROR",
message: "api.ts Check type as fail"
} as RestErrorResponse);
}
}).catch((reason: RestErrorResponse) => {
reject(reason);
});
});
} }
export function RESTRequestVoid(request: RESTRequestType): Promise<void> { export function RESTRequestVoid(request: RESTRequestType): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
RESTRequest(request).then((value: ModelResponseHttp) => { RESTRequest(request)
resolve(); .then((value: ModelResponseHttp) => {
}).catch((reason: RestErrorResponse) => { resolve();
reject(reason); })
}); .catch((reason: RestErrorResponse) => {
}); reject(reason);
});
});
} }

View File

@ -3,12 +3,12 @@
# Default logging detail level for all instances of SimpleLogger. # Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error"). # Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info". # If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=debug org.slf4j.simpleLogger.defaultLogLevel=INFO
# Logging detail level for a SimpleLogger instance named "xxxxx". # Logging detail level for a SimpleLogger instance named "xxxxx".
# Must be one of ("trace", "debug", "info", "warn", or "error"). # Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, the default logging detail level is used. # If not specified, the default logging detail level is used.
#org.slf4j.simpleLogger.log.xxxxx= org.slf4j.simpleLogger.log.org.kar.archidata=TRACE
# Set to true if you want the current date and time to be included in output messages. # Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup. # Default is false, and will output the number of milliseconds elapsed since startup.

View File

@ -0,0 +1,40 @@
package test.kar.archidata;
import java.io.IOException;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConfigureDb {
final static private Logger LOGGER = LoggerFactory.getLogger(ConfigureDb.class);
public static void configure() throws IOException {
if (true) {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
} else {
// Enable this if you want to access to a local MySQL base to test with an adminer
ConfigBaseVariable.bdDatabase = "test_db";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.dbUser = "root";
//ConfigBaseVariable.dbPassword = "password";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
}
public static void clear() throws IOException {
LOGGER.info("Remove the test db");
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
}
}

View File

@ -11,11 +11,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,22 +26,12 @@ public class TestJson {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -12,11 +12,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,22 +26,12 @@ public class TestListJson {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -11,12 +11,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,22 +28,12 @@ public class TestManyToMany {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@ -129,7 +116,7 @@ public class TestManyToMany {
Assertions.assertNotNull(retrieve.otherData); Assertions.assertNotNull(retrieve.otherData);
Assertions.assertEquals(insertedData.otherData, retrieve.otherData); Assertions.assertEquals(insertedData.otherData, retrieve.otherData);
Assertions.assertNotNull(retrieve.remote); Assertions.assertNotNull(retrieve.remote);
Assertions.assertEquals(retrieve.remote.size(), 2); Assertions.assertEquals(2, retrieve.remote.size());
Assertions.assertEquals(retrieve.remote.get(0), insertedRemote1.id); Assertions.assertEquals(retrieve.remote.get(0), insertedRemote1.id);
Assertions.assertEquals(retrieve.remote.get(1), insertedRemote2.id); Assertions.assertEquals(retrieve.remote.get(1), insertedRemote2.id);
@ -141,7 +128,7 @@ public class TestManyToMany {
Assertions.assertNotNull(retrieveExpand.otherData); Assertions.assertNotNull(retrieveExpand.otherData);
Assertions.assertEquals(insertedData.otherData, retrieveExpand.otherData); Assertions.assertEquals(insertedData.otherData, retrieveExpand.otherData);
Assertions.assertNotNull(retrieveExpand.remote); Assertions.assertNotNull(retrieveExpand.remote);
Assertions.assertEquals(retrieveExpand.remote.size(), 2); Assertions.assertEquals(2, retrieveExpand.remote.size());
Assertions.assertEquals(retrieveExpand.remote.get(0).id, insertedRemote1.id); Assertions.assertEquals(retrieveExpand.remote.get(0).id, insertedRemote1.id);
Assertions.assertEquals(retrieveExpand.remote.get(1).id, insertedRemote2.id); Assertions.assertEquals(retrieveExpand.remote.get(1).id, insertedRemote2.id);

View File

@ -11,17 +11,17 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import test.kar.archidata.model.TypeManyToOneRemote; import test.kar.archidata.model.TypeManyToOneRemote;
import test.kar.archidata.model.TypeManyToOneRoot; import test.kar.archidata.model.TypeManyToOneRoot;
import test.kar.archidata.model.TypeManyToOneRootExpand; import test.kar.archidata.model.TypeManyToOneRootExpand;
import test.kar.archidata.model.TypeManyToOneUUIDRemote;
import test.kar.archidata.model.TypeManyToOneUUIDRoot;
import test.kar.archidata.model.TypeManyToOneUUIDRootExpand;
@ExtendWith(StepwiseExtension.class) @ExtendWith(StepwiseExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -30,30 +30,21 @@ public class TestManyToOne {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@Test @Test
public void testCreateTable() throws Exception { public void testCreateTable() throws Exception {
final List<String> sqlCommand = DataFactory.createTable(TypeManyToOneRemote.class); final List<String> sqlCommand = DataFactory.createTable(TypeManyToOneRemote.class);
final List<String> sqlCommand2 = DataFactory.createTable(TypeManyToOneRoot.class); sqlCommand.addAll(DataFactory.createTable(TypeManyToOneRoot.class));
sqlCommand.addAll(sqlCommand2); sqlCommand.addAll(DataFactory.createTable(TypeManyToOneUUIDRoot.class));
sqlCommand.addAll(DataFactory.createTable(TypeManyToOneUUIDRemote.class));
for (final String elem : sqlCommand) { for (final String elem : sqlCommand) {
LOGGER.debug("request: '{}'", elem); LOGGER.debug("request: '{}'", elem);
DataAccess.executeSimpleQuery(elem); DataAccess.executeSimpleQuery(elem);
@ -62,7 +53,7 @@ public class TestManyToOne {
@Order(2) @Order(2)
@Test @Test
public void testAddAlements() throws Exception { public void testRemoteLong() throws Exception {
TypeManyToOneRemote remote = new TypeManyToOneRemote(); TypeManyToOneRemote remote = new TypeManyToOneRemote();
remote.data = "remote1"; remote.data = "remote1";
final TypeManyToOneRemote insertedRemote1 = DataAccess.insert(remote); final TypeManyToOneRemote insertedRemote1 = DataAccess.insert(remote);
@ -119,4 +110,63 @@ public class TestManyToOne {
Assertions.assertEquals(insertedData.otherData, retrieve2.otherData); Assertions.assertEquals(insertedData.otherData, retrieve2.otherData);
Assertions.assertNull(retrieve2.remote); Assertions.assertNull(retrieve2.remote);
} }
@Order(3)
@Test
public void testRemoteUUID() throws Exception {
TypeManyToOneUUIDRemote remote = new TypeManyToOneUUIDRemote();
remote.data = "remote1";
final TypeManyToOneUUIDRemote insertedRemote1 = DataAccess.insert(remote);
Assertions.assertEquals(insertedRemote1.data, remote.data);
remote = new TypeManyToOneUUIDRemote();
remote.data = "remote2";
final TypeManyToOneUUIDRemote insertedRemote2 = DataAccess.insert(remote);
Assertions.assertEquals(insertedRemote2.data, remote.data);
final TypeManyToOneUUIDRoot test = new TypeManyToOneUUIDRoot();
test.otherData = "kjhlkjlkj";
test.remoteUuid = insertedRemote2.uuid;
final TypeManyToOneUUIDRoot insertedData = DataAccess.insert(test);
Assertions.assertNotNull(insertedData);
Assertions.assertNotNull(insertedData.uuid);
Assertions.assertEquals(test.otherData, insertedData.otherData);
Assertions.assertEquals(insertedRemote2.uuid, insertedData.remoteUuid);
TypeManyToOneUUIDRoot retrieve = DataAccess.get(TypeManyToOneUUIDRoot.class, insertedData.uuid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.uuid);
Assertions.assertEquals(insertedData.uuid, retrieve.uuid);
Assertions.assertEquals(insertedData.otherData, retrieve.otherData);
Assertions.assertEquals(insertedRemote2.uuid, retrieve.remoteUuid);
TypeManyToOneUUIDRootExpand retrieve2 = DataAccess.get(TypeManyToOneUUIDRootExpand.class, insertedData.uuid);
Assertions.assertNotNull(retrieve2);
Assertions.assertNotNull(retrieve2.uuid);
Assertions.assertEquals(insertedData.uuid, retrieve2.uuid);
Assertions.assertEquals(insertedData.otherData, retrieve2.otherData);
Assertions.assertNotNull(retrieve2.remote);
Assertions.assertEquals(insertedRemote2.uuid, retrieve2.remote.uuid);
Assertions.assertEquals(insertedRemote2.data, retrieve2.remote.data);
// remove values:
final int count = DataAccess.delete(TypeManyToOneUUIDRemote.class, remote.uuid);
Assertions.assertEquals(1, count);
// check fail:
retrieve = DataAccess.get(TypeManyToOneUUIDRoot.class, insertedData.uuid);
Assertions.assertNotNull(retrieve);
Assertions.assertNotNull(retrieve.uuid);
Assertions.assertEquals(insertedData.uuid, retrieve.uuid);
Assertions.assertEquals(insertedData.otherData, retrieve.otherData);
Assertions.assertEquals(insertedRemote2.uuid, retrieve.remoteUuid);
retrieve2 = DataAccess.get(TypeManyToOneUUIDRootExpand.class, insertedData.uuid);
Assertions.assertNotNull(retrieve2);
Assertions.assertNotNull(retrieve2.uuid);
Assertions.assertEquals(insertedData.uuid, retrieve2.uuid);
Assertions.assertEquals(insertedData.otherData, retrieve2.otherData);
Assertions.assertNull(retrieve2.remote);
}
} }

View File

@ -4,21 +4,24 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import test.kar.archidata.model.TypesTable; import test.kar.archidata.model.TypeOneToManyRemote;
import test.kar.archidata.model.TypeOneToManyRoot;
import test.kar.archidata.model.TypeOneToManyRootExpand;
import test.kar.archidata.model.TypeOneToManyUUIDRemote;
import test.kar.archidata.model.TypeOneToManyUUIDRoot;
import test.kar.archidata.model.TypeOneToManyUUIDRootExpand;
@ExtendWith(StepwiseExtension.class) @ExtendWith(StepwiseExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -27,28 +30,21 @@ public class TestOneToMany {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@Test @Test
public void testCreateTable() throws Exception { public void testCreateTable() throws Exception {
final List<String> sqlCommand = DataFactory.createTable(TypesTable.class); final List<String> sqlCommand = DataFactory.createTable(TypeOneToManyRemote.class);
sqlCommand.addAll(DataFactory.createTable(TypeOneToManyRoot.class));
sqlCommand.addAll(DataFactory.createTable(TypeOneToManyUUIDRemote.class));
sqlCommand.addAll(DataFactory.createTable(TypeOneToManyUUIDRoot.class));
for (final String elem : sqlCommand) { for (final String elem : sqlCommand) {
LOGGER.debug("request: '{}'", elem); LOGGER.debug("request: '{}'", elem);
DataAccess.executeSimpleQuery(elem); DataAccess.executeSimpleQuery(elem);
@ -57,7 +53,161 @@ public class TestOneToMany {
@Order(2) @Order(2)
@Test @Test
public void testPlop() throws Exception { public void testParentLong() throws Exception {
// create parent:
final TypeOneToManyRoot root = new TypeOneToManyRoot();
root.otherData = "plouf";
final TypeOneToManyRoot insertedRoot = DataAccess.insert(root);
Assertions.assertEquals(insertedRoot.otherData, root.otherData);
Assertions.assertNull(insertedRoot.remoteIds);
final TypeOneToManyRoot root2 = new TypeOneToManyRoot();
root2.otherData = "plouf 2";
final TypeOneToManyRoot insertedRoot2 = DataAccess.insert(root2);
Assertions.assertEquals(insertedRoot2.otherData, root2.otherData);
Assertions.assertNull(insertedRoot2.remoteIds);
// Create Some Remotes
final TypeOneToManyRemote remote10 = new TypeOneToManyRemote();
remote10.data = "remote10";
remote10.rootId = insertedRoot.id;
final TypeOneToManyRemote insertedRemote10 = DataAccess.insert(remote10);
Assertions.assertEquals(insertedRemote10.data, remote10.data);
Assertions.assertEquals(insertedRemote10.rootId, remote10.rootId);
final TypeOneToManyRemote remote11 = new TypeOneToManyRemote();
remote11.data = "remote11";
remote11.rootId = insertedRoot.id;
final TypeOneToManyRemote insertedRemote11 = DataAccess.insert(remote11);
Assertions.assertEquals(insertedRemote11.data, remote11.data);
Assertions.assertEquals(insertedRemote11.rootId, remote11.rootId);
final TypeOneToManyRemote remote20 = new TypeOneToManyRemote();
remote20.data = "remote20";
remote20.rootId = insertedRoot2.id;
final TypeOneToManyRemote insertedRemote20 = DataAccess.insert(remote20);
Assertions.assertEquals(insertedRemote20.data, remote20.data);
Assertions.assertEquals(insertedRemote20.rootId, remote20.rootId);
// Check remote are inserted
final TypeOneToManyRoot retreiveRoot1 = DataAccess.get(TypeOneToManyRoot.class, insertedRoot.id);
Assertions.assertEquals(retreiveRoot1.otherData, insertedRoot.otherData);
Assertions.assertNotNull(retreiveRoot1.remoteIds);
Assertions.assertEquals(2, retreiveRoot1.remoteIds.size());
Assertions.assertEquals(insertedRemote10.id, retreiveRoot1.remoteIds.get(0));
Assertions.assertEquals(insertedRemote11.id, retreiveRoot1.remoteIds.get(1));
final TypeOneToManyRoot retreiveRoot2 = DataAccess.get(TypeOneToManyRoot.class, insertedRoot2.id);
Assertions.assertEquals(retreiveRoot2.otherData, insertedRoot2.otherData);
Assertions.assertNotNull(retreiveRoot2.remoteIds);
Assertions.assertEquals(1, retreiveRoot2.remoteIds.size());
Assertions.assertEquals(insertedRemote20.id, retreiveRoot2.remoteIds.get(0));
// Check remote are inserted and expandable
final TypeOneToManyRootExpand retreiveRootExpand1 = DataAccess.get(TypeOneToManyRootExpand.class,
insertedRoot.id);
Assertions.assertEquals(retreiveRootExpand1.otherData, insertedRoot.otherData);
Assertions.assertNotNull(retreiveRootExpand1.remotes);
Assertions.assertEquals(2, retreiveRootExpand1.remotes.size());
Assertions.assertEquals(insertedRemote10.id, retreiveRootExpand1.remotes.get(0).id);
Assertions.assertEquals(insertedRemote10.rootId, retreiveRootExpand1.remotes.get(0).rootId);
Assertions.assertEquals(insertedRemote10.data, retreiveRootExpand1.remotes.get(0).data);
Assertions.assertEquals(insertedRemote11.id, retreiveRootExpand1.remotes.get(1).id);
Assertions.assertEquals(insertedRemote11.rootId, retreiveRootExpand1.remotes.get(1).rootId);
Assertions.assertEquals(insertedRemote11.data, retreiveRootExpand1.remotes.get(1).data);
final TypeOneToManyRootExpand retreiveRootExpand2 = DataAccess.get(TypeOneToManyRootExpand.class,
insertedRoot2.id);
Assertions.assertEquals(retreiveRootExpand2.otherData, insertedRoot2.otherData);
Assertions.assertNotNull(retreiveRootExpand2.remotes);
Assertions.assertEquals(1, retreiveRootExpand2.remotes.size());
Assertions.assertEquals(insertedRemote20.id, retreiveRootExpand2.remotes.get(0).id);
Assertions.assertEquals(insertedRemote20.rootId, retreiveRootExpand2.remotes.get(0).rootId);
Assertions.assertEquals(insertedRemote20.data, retreiveRootExpand2.remotes.get(0).data);
}
@Order(2)
@Test
public void testParentUUID() throws Exception {
// create parent:
final TypeOneToManyUUIDRoot root = new TypeOneToManyUUIDRoot();
root.otherData = "plouf";
final TypeOneToManyUUIDRoot insertedRoot = DataAccess.insert(root);
Assertions.assertEquals(insertedRoot.otherData, root.otherData);
Assertions.assertNull(insertedRoot.remoteIds);
final TypeOneToManyUUIDRoot root2 = new TypeOneToManyUUIDRoot();
root2.otherData = "plouf 2";
final TypeOneToManyUUIDRoot insertedRoot2 = DataAccess.insert(root2);
Assertions.assertEquals(insertedRoot2.otherData, root2.otherData);
Assertions.assertNull(insertedRoot2.remoteIds);
// Create Some Remotes
final TypeOneToManyUUIDRemote remote10 = new TypeOneToManyUUIDRemote();
remote10.data = "remote10";
remote10.rootUuid = insertedRoot.uuid;
final TypeOneToManyUUIDRemote insertedRemote10 = DataAccess.insert(remote10);
Assertions.assertEquals(insertedRemote10.data, remote10.data);
Assertions.assertEquals(insertedRemote10.rootUuid, remote10.rootUuid);
final TypeOneToManyUUIDRemote remote11 = new TypeOneToManyUUIDRemote();
remote11.data = "remote11";
remote11.rootUuid = insertedRoot.uuid;
final TypeOneToManyUUIDRemote insertedRemote11 = DataAccess.insert(remote11);
Assertions.assertEquals(insertedRemote11.data, remote11.data);
Assertions.assertEquals(insertedRemote11.rootUuid, remote11.rootUuid);
final TypeOneToManyUUIDRemote remote20 = new TypeOneToManyUUIDRemote();
remote20.data = "remote20";
remote20.rootUuid = insertedRoot2.uuid;
final TypeOneToManyUUIDRemote insertedRemote20 = DataAccess.insert(remote20);
Assertions.assertEquals(insertedRemote20.data, remote20.data);
Assertions.assertEquals(insertedRemote20.rootUuid, remote20.rootUuid);
// Check remote are inserted
final TypeOneToManyUUIDRoot retreiveRoot1 = DataAccess.get(TypeOneToManyUUIDRoot.class, insertedRoot.uuid);
Assertions.assertEquals(retreiveRoot1.otherData, insertedRoot.otherData);
Assertions.assertNotNull(retreiveRoot1.remoteIds);
Assertions.assertEquals(2, retreiveRoot1.remoteIds.size());
Assertions.assertEquals(insertedRemote10.uuid, retreiveRoot1.remoteIds.get(0));
Assertions.assertEquals(insertedRemote11.uuid, retreiveRoot1.remoteIds.get(1));
final TypeOneToManyUUIDRoot retreiveRoot2 = DataAccess.get(TypeOneToManyUUIDRoot.class, insertedRoot2.uuid);
Assertions.assertEquals(retreiveRoot2.otherData, insertedRoot2.otherData);
Assertions.assertNotNull(retreiveRoot2.remoteIds);
Assertions.assertEquals(1, retreiveRoot2.remoteIds.size());
Assertions.assertEquals(insertedRemote20.uuid, retreiveRoot2.remoteIds.get(0));
// Check remote are inserted and expandable
final TypeOneToManyUUIDRootExpand retreiveRootExpand1 = DataAccess.get(TypeOneToManyUUIDRootExpand.class,
insertedRoot.uuid);
Assertions.assertEquals(retreiveRootExpand1.otherData, insertedRoot.otherData);
Assertions.assertNotNull(retreiveRootExpand1.remotes);
Assertions.assertEquals(2, retreiveRootExpand1.remotes.size());
Assertions.assertEquals(insertedRemote10.uuid, retreiveRootExpand1.remotes.get(0).uuid);
Assertions.assertEquals(insertedRemote10.rootUuid, retreiveRootExpand1.remotes.get(0).rootUuid);
Assertions.assertEquals(insertedRemote10.data, retreiveRootExpand1.remotes.get(0).data);
Assertions.assertEquals(insertedRemote11.uuid, retreiveRootExpand1.remotes.get(1).uuid);
Assertions.assertEquals(insertedRemote11.rootUuid, retreiveRootExpand1.remotes.get(1).rootUuid);
Assertions.assertEquals(insertedRemote11.data, retreiveRootExpand1.remotes.get(1).data);
final TypeOneToManyUUIDRootExpand retreiveRootExpand2 = DataAccess.get(TypeOneToManyUUIDRootExpand.class,
insertedRoot2.uuid);
Assertions.assertEquals(retreiveRootExpand2.otherData, insertedRoot2.otherData);
Assertions.assertNotNull(retreiveRootExpand2.remotes);
Assertions.assertEquals(1, retreiveRootExpand2.remotes.size());
Assertions.assertEquals(insertedRemote20.uuid, retreiveRootExpand2.remotes.get(0).uuid);
Assertions.assertEquals(insertedRemote20.rootUuid, retreiveRootExpand2.remotes.get(0).rootUuid);
Assertions.assertEquals(insertedRemote20.data, retreiveRootExpand2.remotes.get(0).data);
} }
} }

View File

@ -11,11 +11,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -28,22 +25,12 @@ public class TestRawQuery {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@ -83,13 +70,13 @@ public class TestRawQuery {
test.floatData = 7.0F; test.floatData = 7.0F;
DataAccess.insert(test); DataAccess.insert(test);
{ {
String query = """ final String query = """
SELECT * SELECT *
FROM TypesTable FROM TypesTable
WHERE `intData` = ? WHERE `intData` = ?
ORDER BY id DESC ORDER BY id DESC
"""; """;
List<Object> parameters = List.of(Integer.valueOf(99)); final List<Object> parameters = List.of(Integer.valueOf(99));
// Try to retrieve all the data: // Try to retrieve all the data:
final List<TypesTable> retrieve = DataAccess.query(TypesTable.class, query, parameters); final List<TypesTable> retrieve = DataAccess.query(TypesTable.class, query, parameters);
@ -102,13 +89,13 @@ public class TestRawQuery {
} }
{ {
String query = """ final String query = """
SELECT DISTINCT intData SELECT DISTINCT intData
FROM TypesTable FROM TypesTable
WHERE `intData` = ? WHERE `intData` = ?
ORDER BY id DESC ORDER BY id DESC
"""; """;
List<Object> parameters = List.of(Integer.valueOf(99)); final List<Object> parameters = List.of(Integer.valueOf(99));
// Try to retrieve all the data: // Try to retrieve all the data:
final List<TypesTable> retrieve = DataAccess.query(TypesTable.class, query, parameters); final List<TypesTable> retrieve = DataAccess.query(TypesTable.class, query, parameters);
@ -118,4 +105,4 @@ public class TestRawQuery {
} }
} }
} }

View File

@ -14,12 +14,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,26 +33,15 @@ public class TestSimpleTable {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Clear the static test: // Clear the static test:
idOfTheObject = null; idOfTheObject = null;
startAction = null; startAction = null;
ConfigureDb.configure();
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -14,11 +14,9 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.QueryOptions; import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,26 +34,16 @@ public class TestSimpleTableSoftDelete {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Clear the static test: // Clear the static test:
idOfTheObject = null; idOfTheObject = null;
startAction = null; startAction = null;
// Connect the dataBase... ConfigureDb.configure();
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -11,11 +11,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,22 +26,12 @@ public class TestTypeEnum1 {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -11,11 +11,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,22 +26,12 @@ public class TestTypeEnum2 {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)

View File

@ -16,11 +16,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.DataFactory; import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,22 +30,12 @@ public class TestTypes {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) { ConfigureDb.configure();
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
// Connect the dataBase...
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
entry.connect();
} }
@AfterAll @AfterAll
public static void removeDataBase() throws IOException { public static void removeDataBase() throws IOException {
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@ -468,4 +455,4 @@ public class TestTypes {
DataAccess.delete(TypesTable.class, insertedData.id); DataAccess.delete(TypesTable.class, insertedData.id);
} }
} }

View File

@ -0,0 +1,47 @@
package test.kar.archidata.externalRestApi;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
public class TestAnalyzeApiName {
final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiName.class);
public class ApiName {
@GET
public void firstName() {
}
@GET
public void SecondName() {
}
}
@Test
public void testNames() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ApiName.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals("ApiName", api.getAllApi().get(0).name);
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("firstName");
Assertions.assertNotNull(model);
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("SecondName");
Assertions.assertNotNull(model);
}
}
}

View File

@ -0,0 +1,143 @@
package test.kar.archidata.externalRestApi;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
public class TestAnalyzeApiParameterType {
final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiParameterType.class);
public enum TestEnum {
PLOP, PLIP
}
public class TestObject {
public int value;
}
public class BasicParameter {
@GET
public void setInteger1(final int parameter) {}
@GET
public void setInteger2(final Integer parameter) {}
@GET
public void setString(final String parameter) {}
@GET
public void setObject(final TestObject parameter) {}
@GET
public void setEnum(final TestEnum parameter) {}
}
@Test
public void testBasicParameter() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(BasicParameter.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(5, api.getAllApi().get(0).interfaces.size());
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setInteger1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.unnamedElement.get(0));
Assertions.assertEquals(int.class, classModel.getOriginClasses());
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setInteger2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.unnamedElement.get(0));
Assertions.assertEquals(Integer.class, classModel.getOriginClasses());
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setString");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.unnamedElement.get(0));
Assertions.assertEquals(String.class, classModel.getOriginClasses());
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setObject");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.unnamedElement.get(0));
Assertions.assertEquals(TestObject.class, classModel.getOriginClasses());
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setEnum");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassEnumModel classModel = Assertions.assertInstanceOf(ClassEnumModel.class,
model.unnamedElement.get(0));
Assertions.assertEquals(TestEnum.class, classModel.getOriginClasses());
}
}
public class ListParameter {
@GET
public void setList(final List<Integer> parameter) {}
}
@Test
public void testListParameter() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ListParameter.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(1, api.getAllApi().get(0).interfaces.size());
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setList");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassListModel classModel = Assertions.assertInstanceOf(ClassListModel.class,
model.unnamedElement.get(0));
final ClassObjectModel classModelValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classModel.valueModel);
Assertions.assertEquals(Integer.class, classModelValue.getOriginClasses());
}
public class MapParameter {
@GET
public void setMap(final Map<String, Integer> parameter) {}
}
@Test
public void testMapParameter() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(MapParameter.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(1, api.getAllApi().get(0).interfaces.size());
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("setMap");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.unnamedElement.size());
final ClassMapModel classModel = Assertions.assertInstanceOf(ClassMapModel.class, model.unnamedElement.get(0));
final ClassObjectModel classModelKey = Assertions.assertInstanceOf(ClassObjectModel.class, classModel.keyModel);
Assertions.assertEquals(String.class, classModelKey.getOriginClasses());
final ClassObjectModel classModelValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classModel.valueModel);
Assertions.assertEquals(Integer.class, classModelValue.getOriginClasses());
}
}

View File

@ -0,0 +1,107 @@
package test.kar.archidata.externalRestApi;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
public class TestAnalyzeApiPath {
final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiPath.class);
public class NoPath {
@GET
public void noPath() {
}
@GET
@Path("plop")
public void withPath() {
}
@GET
@Path("/plop")
public void withPath2() {
}
}
@Test
public void testNoPath() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(NoPath.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals("", api.getAllApi().get(0).restEndPoint);
Assertions.assertEquals(3, api.getAllApi().get(0).interfaces.size());
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("noPath");
Assertions.assertNotNull(model);
Assertions.assertEquals("/", model.restEndPoint);
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("withPath");
Assertions.assertNotNull(model);
Assertions.assertEquals("/plop", model.restEndPoint);
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("withPath2");
Assertions.assertNotNull(model);
Assertions.assertEquals("//plop", model.restEndPoint);
}
}
@Path("/kaboom")
public class WithPath {
@GET
public void noPath() {
}
@GET
@Path("plop")
public void withPath() {
}
@GET
@Path("/plop")
public void withPath2() {
}
}
@Test
public void testWithPath() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(WithPath.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals("/kaboom", api.getAllApi().get(0).restEndPoint);
Assertions.assertEquals(3, api.getAllApi().get(0).interfaces.size());
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("noPath");
Assertions.assertNotNull(model);
Assertions.assertEquals("/kaboom/", model.restEndPoint);
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("withPath");
Assertions.assertNotNull(model);
Assertions.assertEquals("/kaboom/plop", model.restEndPoint);
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("withPath2");
Assertions.assertNotNull(model);
Assertions.assertEquals("/kaboom//plop", model.restEndPoint);
}
}
}

View File

@ -0,0 +1,605 @@
package test.kar.archidata.externalRestApi;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.model.ApiModel;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
import org.kar.archidata.externalRestApi.model.ClassMapModel;
import org.kar.archidata.externalRestApi.model.ClassObjectModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.core.Response;
public class TestAnalyzeApiReturn {
final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiReturn.class);
public enum TestEnum {
PLOP, PLIP
}
public class TestObject {
public int value;
}
public class ReturnValueVoid {
@GET
public void getVoid1() {}
@GET
public Void getVoid2() {
return null;
}
}
@Test
public void testReturnVoid() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueVoid.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getVoid1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(void.class, classModel.getOriginClasses());
}
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getVoid2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Void.class, classModel.getOriginClasses());
}
}
public class ReturnValueInteger {
@GET
public int getInteger1() {
return 0;
}
@GET
public Integer getInteger2() {
return 0;
}
}
@Test
public void testReturnInteger() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueInteger.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check int
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getInteger1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(int.class, classModel.getOriginClasses());
}
// Check Integer
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getInteger2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Integer.class, classModel.getOriginClasses());
}
}
public class ReturnValueShort {
@GET
public short getShort1() {
return 0;
}
@GET
public Short getShort2() {
return 0;
}
}
@Test
public void testReturnShort() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueShort.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check short
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getShort1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(short.class, classModel.getOriginClasses());
}
// Check Short
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getShort2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Short.class, classModel.getOriginClasses());
}
}
public class ReturnValueLong {
@GET
public long getLong1() {
return 0;
}
@GET
public Long getLong2() {
return 0L;
}
}
@Test
public void testReturnLong() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueLong.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check long
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getLong1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(long.class, classModel.getOriginClasses());
}
// Check Long
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getLong2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
Assertions.assertInstanceOf(ClassObjectModel.class, model.returnTypes.get(0));
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Long.class, classModel.getOriginClasses());
}
}
public class ReturnValueFloat {
@GET
public float getFloat1() {
return 0;
}
@GET
public Float getFloat2() {
return 0.0f;
}
}
@Test
public void testReturnFloat() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueFloat.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check float
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getFloat1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(float.class, classModel.getOriginClasses());
}
// Check Float
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getFloat2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Float.class, classModel.getOriginClasses());
}
}
public class ReturnValueDouble {
@GET
public double getDouble1() {
return 0;
}
@GET
public Double getDouble2() {
return 0.0;
}
}
@Test
public void testReturnDouble() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueDouble.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check double
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getDouble1");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(double.class, classModel.getOriginClasses());
}
// Check Double
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getDouble2");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Double.class, classModel.getOriginClasses());
}
}
public class ReturnValueString {
@GET
public String getString() {
return "0";
}
}
@Test
public void testReturnString() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueString.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(1, api.getAllApi().get(0).interfaces.size());
// Check String
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getString");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(String.class, classModel.getOriginClasses());
}
}
public class ReturnValueAny {
@GET
public Response getResponse() {
return null;
}
@GET
public Object getObject() {
return null;
}
}
@Test
public void testReturnAny() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueAny.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(2, api.getAllApi().get(0).interfaces.size());
// Check Response ==> represent a Any value then it wrapped as Object
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getResponse");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Object.class, classModel.getOriginClasses());
}
// Check Object
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getObject");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassObjectModel classModel = Assertions.assertInstanceOf(ClassObjectModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(Object.class, classModel.getOriginClasses());
}
}
public class ReturnValueEnum {
@GET
public TestEnum getEnum() {
return TestEnum.PLIP;
}
}
@Test
public void testReturnEnum() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueEnum.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(1, api.getAllApi().get(0).interfaces.size());
// Check Enum
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getEnum");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
final ClassEnumModel classModel = Assertions.assertInstanceOf(ClassEnumModel.class,
model.returnTypes.get(0));
Assertions.assertEquals(TestEnum.class, classModel.getOriginClasses());
}
}
public class ReturnValueList {
@GET
public List<Integer> getListInteger() {
return null;
}
@GET
public List<TestEnum> getListEnum() {
return null;
}
@GET
public List<TestObject> getListObject() {
return null;
}
@GET
public List<List<Integer>> getListListInteger() {
return null;
}
@GET
public List<Map<String, Integer>> getListMapInteger() {
return null;
}
}
@Test
public void testReturnList() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueList.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(5, api.getAllApi().get(0).interfaces.size());
// Check List<Integer>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getListInteger");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassListModel classListModel = Assertions.assertInstanceOf(ClassListModel.class,
model.returnTypes.get(0));
// Level 1
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classListModel.valueModel);
Assertions.assertEquals(Integer.class, classModelOfValue.getOriginClasses());
}
// Check List<TestEnum>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getListEnum");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassListModel classListModel = Assertions.assertInstanceOf(ClassListModel.class,
model.returnTypes.get(0));
// Level 1
final ClassEnumModel classModelOfValue = Assertions.assertInstanceOf(ClassEnumModel.class,
classListModel.valueModel);
Assertions.assertEquals(TestEnum.class, classModelOfValue.getOriginClasses());
}
// Check List<TestObject>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getListObject");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassListModel classListModel = Assertions.assertInstanceOf(ClassListModel.class,
model.returnTypes.get(0));
// Level 1
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classListModel.valueModel);
Assertions.assertEquals(TestObject.class, classModelOfValue.getOriginClasses());
}
// Check List<List<Integer>>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getListListInteger");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassListModel classListModel = Assertions.assertInstanceOf(ClassListModel.class,
model.returnTypes.get(0));
// Level 1
final ClassListModel classList2Model = Assertions.assertInstanceOf(ClassListModel.class,
classListModel.valueModel);
// Level 2
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classList2Model.valueModel);
Assertions.assertEquals(Integer.class, classModelOfValue.getOriginClasses());
}
// Check List<Map<String, Integer>>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getListMapInteger");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassListModel classListModel = Assertions.assertInstanceOf(ClassListModel.class,
model.returnTypes.get(0));
// Level 1
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
classListModel.valueModel);
// Level 2
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.valueModel);
Assertions.assertEquals(Integer.class, classModelOfValue.getOriginClasses());
}
}
// does not test other than key string, but in theory it works.
public class ReturnValueMap {
@GET
public Map<String, Integer> getMapInteger() {
return null;
}
@GET
public Map<String, TestEnum> getMapEnum() {
return null;
}
@GET
public Map<String, TestObject> getMapObject() {
return null;
}
@GET
public Map<String, Map<String, Integer>> getMapMap() {
return null;
}
@GET
public Map<String, List<Integer>> getMapList() {
return null;
}
}
@Test
public void testReturnMap() throws Exception {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(List.of(ReturnValueMap.class));
Assertions.assertEquals(1, api.getAllApi().size());
Assertions.assertEquals(5, api.getAllApi().get(0).interfaces.size());
// Check Map<String, Integer>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getMapInteger");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
model.returnTypes.get(0));
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
// Level 1
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.valueModel);
Assertions.assertEquals(Integer.class, classModelOfValue.getOriginClasses());
}
// Check Map<String, TestEnum>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getMapEnum");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
model.returnTypes.get(0));
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
// Level 1
final ClassEnumModel classModelOfValue = Assertions.assertInstanceOf(ClassEnumModel.class,
classMapModel.valueModel);
Assertions.assertEquals(TestEnum.class, classModelOfValue.getOriginClasses());
}
// Check Map<String, TestObject>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getMapObject");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
model.returnTypes.get(0));
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
// Level 1
final ClassObjectModel classModelOfValue = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.valueModel);
Assertions.assertEquals(TestObject.class, classModelOfValue.getOriginClasses());
}
// Check Map<String, Map<String, Integer>>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getMapMap");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
model.returnTypes.get(0));
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
// Level 1
final ClassMapModel classModelOfValue = Assertions.assertInstanceOf(ClassMapModel.class,
classMapModel.valueModel);
final ClassObjectModel classModelOfValueOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classModelOfValue.keyModel);
Assertions.assertEquals(String.class, classModelOfValueOfKey.getOriginClasses());
final ClassObjectModel classSubModel = Assertions.assertInstanceOf(ClassObjectModel.class,
classModelOfValue.valueModel);
Assertions.assertEquals(Integer.class, classSubModel.getOriginClasses());
}
// Check Map<String, List<Integer>>
{
final ApiModel model = api.getAllApi().get(0).getInterfaceNamed("getMapList");
Assertions.assertNotNull(model);
Assertions.assertEquals(1, model.returnTypes.size());
// Level 0
final ClassMapModel classMapModel = Assertions.assertInstanceOf(ClassMapModel.class,
model.returnTypes.get(0));
final ClassObjectModel classModelOfKey = Assertions.assertInstanceOf(ClassObjectModel.class,
classMapModel.keyModel);
Assertions.assertEquals(String.class, classModelOfKey.getOriginClasses());
// Level 1
final ClassListModel classModelOfValue = Assertions.assertInstanceOf(ClassListModel.class,
classMapModel.valueModel);
final ClassObjectModel classSubModel = Assertions.assertInstanceOf(ClassObjectModel.class,
classModelOfValue.valueModel);
Assertions.assertEquals(Integer.class, classSubModel.getOriginClasses());
}
}
}

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