Compare commits

..

115 Commits

Author SHA1 Message Date
38f5764683 [FIX] order the inputs in typescript generation 2025-04-15 16:22:28 +02:00
6298d09828 [VERSION] update dev tag version 2025-04-08 20:41:48 +02:00
aa8330fa1a [RELEASE] Release v0.27.0 2025-04-08 20:41:45 +02:00
37629b4cb2 [FIX] doc generation 2025-04-08 20:41:00 +02:00
69f69a8113 [FIX] Double and double inclution generate a double inclusion in Typescript generation 2025-04-08 10:27:30 +02:00
dependabot[bot]
7185a34fda [DEV-OPS] (dependabot) Bump advanced-security/maven-dependency-submission-action from 4.1.1 to 4.1.2 (#42)
Bumps
[advanced-security/maven-dependency-submission-action](https://github.com/advanced-security/maven-dependency-submission-action)
from 4.1.1 to 4.1.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/advanced-security/maven-dependency-submission-action/releases">advanced-security/maven-dependency-submission-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Add cycle safety for transitive dependencies by <a
href="https://github.com/juxtin"><code>@​juxtin</code></a> in <a
href="https://redirect.github.com/advanced-security/maven-dependency-submission-action/pull/103">advanced-security/maven-dependency-submission-action#103</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/juxtin"><code>@​juxtin</code></a> made
their first contribution in <a
href="https://redirect.github.com/advanced-security/maven-dependency-submission-action/pull/103">advanced-security/maven-dependency-submission-action#103</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/advanced-security/maven-dependency-submission-action/compare/v4...v4.1.2">https://github.com/advanced-security/maven-dependency-submission-action/compare/v4...v4.1.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="aeab9f8852"><code>aeab9f8</code></a>
Merge pull request <a
href="https://redirect.github.com/advanced-security/maven-dependency-submission-action/issues/106">#106</a>
from advanced-security/juxtin/prep-412</li>
<li><a
href="bc43a53a41"><code>bc43a53</code></a>
Update step numbers</li>
<li><a
href="8ec6a0b12e"><code>8ec6a0b</code></a>
Add note about running npm build</li>
<li><a
href="8c8c37cf51"><code>8c8c37c</code></a>
Merge branch 'main' into juxtin/prep-412</li>
<li><a
href="4ccf7bf0a3"><code>4ccf7bf</code></a>
Update version to 4.1.2 and update release instructions</li>
<li><a
href="973a8cf442"><code>973a8cf</code></a>
Merge pull request <a
href="https://redirect.github.com/advanced-security/maven-dependency-submission-action/issues/104">#104</a>
from advanced-security/juxtin/prep-412</li>
<li><a
href="48f232b0d1"><code>48f232b</code></a>
Update dist files</li>
<li><a
href="769e1e8558"><code>769e1e8</code></a>
Prepare for 4.1.2 release</li>
<li><a
href="298a804769"><code>298a804</code></a>
Upgrade version of Maven plugin</li>
<li><a
href="29fd39885e"><code>29fd398</code></a>
Merge pull request <a
href="https://redirect.github.com/advanced-security/maven-dependency-submission-action/issues/103">#103</a>
from advanced-security/juxtin/handle-cycles</li>
<li>Additional commits viewable in <a
href="4f64ddab9d...aeab9f8852">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=advanced-security/maven-dependency-submission-action&package-manager=github_actions&previous-version=4.1.1&new-version=4.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-08 09:06:16 +02:00
b12108ec00 [FIX] test on DateTime is ended and solution in 90% operational 2025-04-07 23:08:23 +02:00
ecc8829e8c [FEAT] develop a new RESTApi interface to be easiest and flexible 2025-04-06 23:26:46 +02:00
e7e8c48c5c [FEAT] add OffsetDateTime in the BDD interface with UTC force 2025-04-06 23:26:19 +02:00
c09c29bf8d [FEAT] add jakarta some methode to parse the date more flexible 2025-04-06 23:25:48 +02:00
16bb30b260 [FEAT] add some test for time system 2025-04-06 23:24:58 +02:00
f044473a67 [FEAT] Jackson update date serializer to be more permissive 2025-04-06 23:24:12 +02:00
1d375f8580 [VERSION] update dev tag version 2025-04-03 10:30:43 +02:00
e2d35c5060 [RELEASE] Release v0.26.4 2025-04-03 10:30:40 +02:00
9ebd37788d [FIX] ready to deploy on maven central 2025-04-03 10:29:56 +02:00
8dde49adff [FIX] ApiNotNull inplementation 2025-04-03 10:25:33 +02:00
5960bbc666 [VERSION] update dev tag version 2025-04-02 23:50:03 +02:00
afcbb4711c [RELEASE] Release v0.26.2 2025-04-02 23:50:00 +02:00
a0b22e00bd [FEAT] update for maven central 2025-04-02 23:49:28 +02:00
a49e374bd1 [DEV] remove unneeded libraries 2025-04-02 22:28:23 +02:00
2b43d348a8 [VERSION] update dev tag version 2025-04-02 21:46:56 +02:00
242e6b9261 [RELEASE] Release v0.26.0 2025-04-02 21:46:53 +02:00
c627484b49 [FIX] annotation in the common model 2025-04-02 21:42:17 +02:00
44df939873 [FEAT] add ApiNotNull to overwrite all fields possibility 2025-04-02 21:34:27 +02:00
b283f8545c [FEAT] add readable optionnal for field 2025-04-02 21:33:52 +02:00
dependabot[bot]
6e4c10724a [DEV-OPS] (dependabot) Bump org.apache.maven.plugins:maven-surefire-plugin
Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.2...surefire-3.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 19:39:12 +02:00
41aa78667f [FIX] some basic types generation constraint 2025-04-01 22:18:58 +02:00
f7ba570913 [FIX] foreinKeyValidator 2025-04-01 22:18:50 +02:00
55957278a0 [VERSION] update dev tag version 2025-03-30 23:28:36 +02:00
a15be78e10 [RELEASE] Release v0.25.6 2025-03-30 23:27:32 +02:00
abff1ada45 [FIX] (RestApi tool) null object are serialize as null (like browser) 2025-03-30 23:24:17 +02:00
1c769827cf [FIX] (RestApi tool) multipart file is not merged with nest parameter 2025-03-30 23:23:51 +02:00
e09de7cc7a [FEAT] remove dataJson plugin that never exist in NoSQL 2025-03-30 17:00:19 +02:00
a5b9b60294 [FEAT] rename parameter to be clear 2025-03-30 16:59:34 +02:00
288e1f8293 [FEAT] add capabilituy of rest API to manage multipart 2025-03-30 16:58:34 +02:00
d9a5f1ece2 [FEAT] configure deleted element to be missing or false in morphia 2025-03-29 20:23:42 +01:00
f77c6ce13e [FEAT] remove doucle of code 2025-03-29 20:23:04 +01:00
85754f20f8 [FIX] configuration of mongo interface 2025-03-29 20:22:44 +01:00
969bf78576 [FEAT] configure back compatibility with mongo 2025-03-29 20:21:07 +01:00
f0cf1acf8a [FIX] configure morphia that is compatible with mongo 2025-03-29 20:20:21 +01:00
7208db5bdf [FEAT] normalize Mongo and SQL 2025-03-29 20:19:55 +01:00
5b88401d48 [FIX] name of the primary key of the cover is not managed 2025-03-28 21:13:15 +01:00
d53a0719b5 [FEAT] fix 400 error in input error 2025-03-21 08:57:24 +01:00
3e7a1a5473 [VERSION] update dev tag version 2025-03-19 23:36:56 +01:00
c7338c7877 [RELEASE] Release v0.25.4 2025-03-19 23:35:39 +01:00
1007628713 [FEAT] update test 2025-03-19 23:34:24 +01:00
84a968a426 [Fix] correct @OneToMany on ObjectId 2025-03-19 19:15:24 +01:00
581c936bec [VERSION] update dev tag version 2025-03-18 23:07:41 +01:00
a6204032b5 [RELEASE] Release v0.25.2 2025-03-18 23:04:15 +01:00
e824feb8a2 [FIX] fix json serialize null in error (remove) 2025-03-18 15:27:42 +01:00
26ea70f80c [FIX] correct some generic code generation 2025-03-18 15:27:39 +01:00
6291466df0 [VERSION] update dev tag version 2025-03-17 21:59:02 +01:00
2bc7a2c5e3 [RELEASE] Release v0.25.0 2025-03-17 21:55:44 +01:00
dependabot[bot]
6c69bc63c6 [DEV-OPS] (dependabot) Bump all dependency 2025-03-17 21:13:14 +01:00
85ac72648f [FIX] not ready dependency 2025-03-16 11:48:33 +01:00
1281415c48 [DEV] integrate hybernate validator 2025-03-16 11:24:16 +01:00
dependabot[bot]
a4521853c3 [DEV-OPS] (dependabot) Bump com.twelvemonkeys.imageio:imageio-webp
Bumps com.twelvemonkeys.imageio:imageio-webp from 3.11.0 to 3.12.0.

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

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

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

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

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

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

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

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

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

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

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

By default the generation of object are only the object requested

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

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

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

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

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

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


TODO:
=====

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


dependencies:
===========
  - Closes: https://github.com/kangaroo-and-rabbit/archidata/issues/22
2025-03-08 14:32:16 +01:00
2174d7689f [FEAT] add callback to detect and collect errors 2025-03-05 23:48:15 +01:00
15113807b3 [DOC] add some documentation 2025-03-05 23:45:08 +01:00
ffdc6c1249 [VERSION] update dev tag version 2025-02-24 12:34:45 +01:00
1b4e6ca239 [RELEASE] Release v0.24.0 2025-02-24 12:34:41 +01:00
be8a5c713a [FIX] ObjectId manyToOne and OneToMany 2025-02-17 00:16:11 +01:00
c3f03bc1e8 [VERSION] update dev tag version 2025-02-09 22:06:56 +01:00
c7eadc607d [RELEASE] Release v0.23.6 2025-02-09 22:06:53 +01:00
5f89ff7944 [DEV] add variable to permit not check if the DB exist 2025-02-09 22:05:35 +01:00
20d2d004cb [VERSION] update dev tag version 2025-02-06 07:47:20 +01:00
da3c467569 [RELEASE] Release v0.23.4 2025-02-06 07:47:17 +01:00
0c932d4e92 [FEAT] set the object enpty as identical as their parent.
this prevent react-hook-form resolver erreur, it does not support empty object
2025-02-04 21:15:10 +01:00
a400bb99b8 [FEAT] add Jwt token description to be serialize in front 2025-02-02 19:34:23 +01:00
bdc9a4ac4d [VERSION] update dev tag version 2025-01-30 21:45:29 +01:00
b0bf103195 [RELEASE] Release v0.23.2 2025-01-30 21:45:26 +01:00
d36c366ab6 [FIX] zod number gte/lte 2025-01-30 21:25:15 +01:00
cddb4dd7fe [DEV] update dev tag version 2025-01-30 10:01:58 +01:00
218fa3be2e [RELEASE] new version 0.23.0 2025-01-29 23:30:01 +01:00
015c38ff5b [FEAT] add @CheckForeignKey but not tested 2025-01-29 23:30:01 +01:00
24590b2a1e [FIX] correct min and mas size of string 2025-01-29 23:10:53 +01:00
c04660c01a [FEAT] simplify annotation tool 2025-01-29 23:09:49 +01:00
3e81673d38 [FIX] normalize comment when check size 2025-01-29 22:43:22 +01:00
69880df4aa [FIX] missing string size checker 2025-01-29 22:41:08 +01:00
510553550f [FIX] remove unneeded comment 2025-01-29 22:40:34 +01:00
e071d3dbf7 [FEAT] correct the output generation of the typescript ==> missing decimal min and decimal max ==> need to test pattern and email 2025-01-29 00:34:49 +01:00
249e6ad2c8 set JPAChecker agnostic of @ManyToOne primary key type 2025-01-28 23:48:40 +01:00
e156e528c1 [FIX] use instance of DB instead of create a new one 2025-01-28 23:44:36 +01:00
89ab0f3b6a [DEV] update dev tag version 2025-01-28 23:27:30 +01:00
3d5a024084 [RELEASE] new version 0.22.0 2025-01-28 23:27:05 +01:00
ba6478182d [API] move the cheker in the correct folder 2025-01-28 23:25:32 +01:00
cc639243fc [FEAT] add support of @DecimalMin and @DecimalMax 2025-01-28 23:17:34 +01:00
24c226e92c [FEAT] add @Size @Min @Max @parttern @email unit test for JPAChecker 2025-01-28 23:16:18 +01:00
b5fcc3e20c [FEAT,API] remove checker from JsonData and creadte @CheckerAnnotation 2025-01-28 23:15:11 +01:00
d028eb2261 Manage correct rights 2025-01-26 23:38:48 +01:00
ca18d3759d [FIX] congig in config 2025-01-25 15:48:35 +01:00
a7c9bb5e1b [FIX] do not set the table name at unknow when null is find 2025-01-25 15:48:04 +01:00
d011b3a587 [FEAT] add decorator CollectionNotEmpty 2025-01-25 14:41:39 +01:00
6974adbfdf [FEAT] add decorator CollectionItemUnique 2025-01-25 14:41:20 +01:00
461aece7a0 [FEAT] add decorator @CollectionItemNotNull 2025-01-25 14:40:07 +01:00
3f15d560ed [FEAT] better interface for dataJson 2025-01-25 14:38:53 +01:00
8f3c14e28d [FEAT] assert when try to insart null data 2025-01-25 12:16:39 +01:00
0d419f651e [FIX] dataJson checker is not responsive for null data pointer 2025-01-25 12:16:13 +01:00
9dad14d200 [FIX] many-many support every key type 2025-01-25 12:15:25 +01:00
990b7c08da [FIX] correct right: set it when no element is requested 2025-01-18 09:57:55 +01:00
7f393a9e44 [FIX] set User block boolean nullable for creation capability (front pb) 2025-01-18 09:57:52 +01:00
c91291dbce [DOC] log better display of path when call filter authentication 2025-01-18 09:57:47 +01:00
d684b5eaa9 [VERSION] update dev tag version 2025-01-11 17:29:13 +01:00
85b27c0b31 [RELEASE] Release v0.21.0 2025-01-11 17:29:10 +01:00
156 changed files with 7323 additions and 2279 deletions

View File

@@ -30,15 +30,6 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</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">
<attributes>
<attribute name="optional" value="true"/>

View File

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

View File

@@ -9,6 +9,8 @@ on:
- synchronize
- ready_for_review
- reopened
- labeled
- unlabeled
jobs:
check-title:
@@ -17,7 +19,7 @@ jobs:
- 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 ,.'\\-!]+$"
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}]

View File

@@ -32,4 +32,4 @@ jobs:
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@4f64ddab9d742a4806eeb588d238e4c311a8397d
uses: advanced-security/maven-dependency-submission-action@aeab9f885293af501bae8bdfe88c589528ea5e25

179
pom.xml
View File

@@ -1,41 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>kangaroo-and-rabbit</groupId>
<groupId>org.atria-soft</groupId>
<artifactId>archidata</artifactId>
<version>0.21.0</version>
<properties>
<java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version>
<jersey.version>3.1.5</jersey.version>
<jaxb.version>2.3.1</jaxb.version>
<istack.version>4.1.1</istack.version>
</properties>
<repositories>
<repository>
<id>gitea</id>
<url>https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitea</id>
<url>https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/maven</url>
</repository>
<snapshotRepository>
<id>gitea</id>
<url>https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/maven</url>
</snapshotRepository>
</distributionManagement>
<version>0.27.2</version>
<description>Wrapper to manage a simple interface for REST project to bind MySQL, SQLite or mongoDB.</description>
<url>https://github.com/kangaroo-and-rabbit/archidata</url>
<licenses>
<license>
<name>Mozilla Public License 2.0</name>
<url>https://opensource.org/licenses/MPL-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dev1</id>
<name>Edouard DUPIN</name>
<email>yui.heero@gmail.com</email>
<roles>
<role>Lead Developer</role>
</roles>
</developer>
</developers>
<scm>
<url>https://github.com/kangaroo-and-rabbit/archidata</url>
<connection>scm:git:git://github.com/kangaroo-and-rabbit/archidata.git</connection>
<developerConnection>scm:git:ssh:github.com/kangaroo-and-rabbit/archidata.git</developerConnection>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<version>4.0.0-M2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -58,13 +56,13 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.11.0</version>
<version>3.12.0</version>
</dependency>
<!-- Decode JPEG image -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.11.0</version>
<version>3.12.0</version>
</dependency>
<!-- Encode file in webp -->
<dependency>
@@ -76,7 +74,7 @@
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.0.0-BETA2</version>
<version>3.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart -->
<dependency>
@@ -96,9 +94,8 @@
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
@@ -110,22 +107,6 @@
<artifactId>jakarta.ws.rs-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
<version>${istack.version}</version>
</dependency>
<!-- continu to be needed ??? -->
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<!-- Serialize and un-serialize request in JSON-->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
@@ -134,18 +115,18 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.1</version>
<version>2.18.3</version>
</dependency>
<!-- encode output in CSV -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.18.1</version>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.1</version>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -157,18 +138,18 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.0.0</version>
<version>9.2.0</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.0</version>
<version>3.49.1.0</version>
</dependency>
<!-- Interface for JWT token -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.41.1</version>
<version>10.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
@@ -179,37 +160,34 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-jakarta</artifactId>
<version>2.2.23</version>
<version>2.2.29</version>
</dependency>
<!-- spotbug tooling -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.8.6</version>
<version>4.9.3</version>
<scope>compile</scope>
</dependency>
<!-- Morphia -->
<dependency>
<groupId>dev.morphia.morphia</groupId>
<artifactId>morphia-core</artifactId>
<version>2.3.0</version>
<version>2.4.15</version>
</dependency>
<!-- MongoDB Java Driver -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.3.0</version>
<!--<version>5.3.1</version>-->
<!--Morphia 2.4.x does not support version upper than 4.x-->
<version>4.11.5</version>
</dependency>
<!-- Bean Validation (JSR 303 / 380) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.0.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
<version>9.0.0.CR1</version>
</dependency>
<!--
************************************************************
@@ -219,24 +197,24 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0</version>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.24.1</version>
<version>2.25.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
</dependency>
</dependencies>
<build>
@@ -256,23 +234,22 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<version>3.14.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<!--<encoding>${project.build.sourceEncoding}</encoding>-->
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<!-- Create the source bundle -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
@@ -295,7 +272,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.5.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
@@ -314,17 +291,21 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
</configuration>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Check the style of the code -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<version>3.6.0</version>
<configuration>
<configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
@@ -336,7 +317,7 @@
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
<version>2.24.1</version>
<configuration>
<encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding>
@@ -377,19 +358,29 @@
-->
</configuration>
</plugin>
</plugins>
</build>
<!-- Generate Java-docs As Part Of Project Reports -->
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<extensions>true</extensions>
<configuration>
<show>public</show>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
</plugins>
</reporting>
</build>
</project>

View File

@@ -2,9 +2,14 @@ package org.kar.archidata.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import org.kar.archidata.annotation.checker.Checker;
import org.kar.archidata.annotation.checker.CollectionItemNotNull;
import org.kar.archidata.annotation.checker.CollectionItemUnique;
import org.kar.archidata.annotation.checker.CollectionNotEmpty;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.OptionRenameColumn;
import org.kar.archidata.dataAccess.options.OverrideTableName;
@@ -24,6 +29,8 @@ import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
@@ -35,6 +42,60 @@ import jakarta.ws.rs.DefaultValue;
public class AnnotationTools {
static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class);
public static <TYPE extends Annotation> TYPE get(final Parameter param, final Class<TYPE> clazz) {
final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Parameter param, final Class<TYPE> clazz) {
final TYPE[] annotations = param.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations;
}
public static <TYPE extends Annotation> TYPE get(final Field element, final Class<TYPE> clazz) {
final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Field element, final Class<TYPE> clazz) {
final TYPE[] annotations = element.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations;
}
public static <TYPE extends Annotation> TYPE get(final Class<?> classObject, final Class<TYPE> clazz) {
final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations[0];
}
public static <TYPE extends Annotation> TYPE[] gets(final Class<?> classObject, final Class<TYPE> clazz) {
final TYPE[] annotations = classObject.getDeclaredAnnotationsByType(clazz);
if (annotations.length == 0) {
return null;
}
return annotations;
}
// For SQL declaration table Name
public static String getTableName(final Class<?> clazz, final QueryOptions options) throws DataAccessException {
if (options != null) {
@@ -85,12 +146,16 @@ public class AnnotationTools {
return tmp;
}
public static boolean getSchemaReadOnly(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return false;
}
return ((Schema) annotation[0]).readOnly();
public static CollectionItemNotNull getCollectionItemNotNull(final Field element) {
return get(element, CollectionItemNotNull.class);
}
public static CollectionItemUnique getCollectionItemUnique(final Field element) {
return get(element, CollectionItemUnique.class);
}
public static CollectionNotEmpty getCollectionNotEmpty(final Field element) {
return get(element, CollectionNotEmpty.class);
}
public static String getSchemaExample(final Class<?> element) {
@@ -101,14 +166,6 @@ public class AnnotationTools {
return ((Schema) annotation[0]).example();
}
public static boolean getNoWriteSpecificMode(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(NoWriteSpecificMode.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static String getSchemaDescription(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
@@ -134,51 +191,39 @@ public class AnnotationTools {
}
public static ManyToOne getManyToOne(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class);
if (annotation.length == 0) {
return null;
}
return (ManyToOne) annotation[0];
return get(element, ManyToOne.class);
}
public static ManyToMany getManyToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToMany.class);
if (annotation.length == 0) {
return null;
}
return (ManyToMany) annotation[0];
return get(element, ManyToMany.class);
}
public static OneToMany getOneToMany(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(OneToMany.class);
if (annotation.length == 0) {
return null;
}
return (OneToMany) annotation[0];
return get(element, OneToMany.class);
}
public static DataJson getDataJson(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class);
if (annotation.length == 0) {
return null;
}
return (DataJson) annotation[0];
return get(element, DataJson.class);
}
public static Long getConstraintsMax(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class);
if (annotation.length == 0) {
return null;
}
return ((Max) annotation[0]).value();
public static Checker[] getConstraintsCheckers(final Field element) {
return gets(element, Checker.class);
}
public static Long getConstraintsMin(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class);
if (annotation.length == 0) {
return null;
}
return ((Min) annotation[0]).value();
public static DecimalMin getConstraintsDecimalMin(final Field element) {
return get(element, DecimalMin.class);
}
public static DecimalMax getConstraintsDecimalMax(final Field element) {
return get(element, DecimalMax.class);
}
public static Max getConstraintsMax(final Field element) {
return get(element, Max.class);
}
public static Min getConstraintsMin(final Field element) {
return get(element, Min.class);
}
public static int getLimitSize(final Field element) {
@@ -191,27 +236,15 @@ public class AnnotationTools {
}
public static Size getConstraintsSize(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Size.class);
if (annotation.length == 0) {
return null;
}
return (Size) annotation[0];
return get(element, Size.class);
}
public static String getConstraintsPattern(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Pattern.class);
if (annotation.length == 0) {
return null;
}
return ((Pattern) annotation[0]).regexp();
public static Pattern getConstraintsPattern(final Field element) {
return get(element, Pattern.class);
}
public static boolean getConstraintsEmail(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Email.class);
if (annotation.length == 0) {
return false;
}
return true;
public static Email getConstraintsEmail(final Field element) {
return get(element, Email.class);
}
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {
@@ -416,7 +449,7 @@ public class AnnotationTools {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
if (AnnotationTools.getFieldNameRaw(field).equals(name)) {
if (field.getName().equals(name)) {
return true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface FormDataOptional {
}

View File

@@ -1,13 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** When we wend to have only One type for read and write mode (Wrapping API). */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NoWriteSpecificMode {
}

View File

@@ -1,11 +0,0 @@
package org.kar.archidata.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** In case of the update parameter with String input to detect null element. */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeScriptProgress {}

View File

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

View File

@@ -0,0 +1,26 @@
package org.kar.archidata.annotation.apiGenerator;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(FIELD)
public @interface ApiAccessLimitation {
/**
* (Optional) The field is accessible in read (GET)
*/
boolean readable() default true;
/**
* (Optional) The field is accessible in creation (POST)
*/
boolean creatable() default true;
/**
* (Optional) The field is accessible in update mode (PUT, PATCH)
*/
boolean updatable() default true;
}

View File

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

View File

@@ -0,0 +1,57 @@
package org.kar.archidata.annotation.apiGenerator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The ApiGenerationMode annotation is used to indicate the generation mode for
* API operations when producing code for other languages. This annotation is
* particularly useful in code generators for client libraries where specific
* data structures for read, create, and update operations may or may not be needed.
*
* <p>Usage:
* - Target: This annotation can be applied to class types.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle code generation logic.
*
* <p>Behavior:
* - When applied to a class, the ApiGenerationMode annotation specifies
* which API operations (read, create, update) should generate specific
* data structures. This can simplify the generated code by avoiding the
* creation of unnecessary structures.
*
* <p>Example:
* <pre>{@code
* @ApiGenerationMode(creatable=false, updatable=false)
* public class User {
* public String username;
* public String email;
* }
* }</pre>
*
* In this example, the User class will not generate separate data structures
* for create and update operations in the client code, only for read operations.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiGenerationMode {
/**
* (Optional) Enable the generation of specific code for read access
* (generate object: MyClass).
*/
boolean read() default true;
/**
* (Optional) Enable the generation of specific code for create access
* (generate object: MyClassCreate).
*/
boolean create() default false;
/**
* (Optional) Enable the generation of specific code for update access
* (generate object: MyClassUpdate).
*/
boolean update() default false;
}

View File

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

View File

@@ -0,0 +1,45 @@
package org.kar.archidata.annotation.apiGenerator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to explicitly define the nullability of a parameter in an API.
*
* This annotation allows marking a parameter as required (non-null) or optional (nullable),
* overriding any other nullability considerations. It is useful in API generation frameworks
* to ensure precise validation and documentation of method parameters.
*
* <p>Usage:
* - Target: This annotation can be applied to field.
* - Retention: The annotation is retained at runtime, allowing it to be
* processed by frameworks or libraries that handle code generation logic.
*
* <p>Behavior:
* - When applied to a parameter, it explicitly marks it as optional or required in the API.
* This annotation overrides all other considerations regarding nullability.
*
* <p>Example:
* <pre>{@code
* public class User {
* @ReadOnlyField
* @ApiNotNull
* public String username;
* public String email;
* }
* }</pre>
*
* In this example, the `username` field in the `User` class is explicitly marked as non-null in the generated API.
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiNotNull {
/**
* (Optional) Specifies whether the API element can be null.
* If set to `true`, the element is required (non-null).
* If set to `false`, the element is optional (nullable).
*/
boolean value() default true;
}

View File

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

View File

@@ -0,0 +1,30 @@
package org.kar.archidata.annotation.checker;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = CheckForeignKeyValidator.class)
@Target({ ElementType.TYPE, //
ElementType.METHOD, //
ElementType.FIELD, //
ElementType.ANNOTATION_TYPE, //
ElementType.CONSTRUCTOR, //
ElementType.PARAMETER, //
ElementType.TYPE_USE, //
})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckForeignKey {
Class<?> target();
String message() default "Foreign-key does not exist in the DB";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,36 @@
package org.kar.archidata.annotation.checker;
import org.kar.archidata.dataAccess.DataAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CheckForeignKeyValidator implements ConstraintValidator<CheckForeignKey, Object> {
Class<?> target = null;
private final static Logger LOGGER = LoggerFactory.getLogger(CheckForeignKeyValidator.class);
@Override
public void initialize(final CheckForeignKey annotation) {
this.target = annotation.target();
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
}
try {
final long count = DataAccess.count(this.target, value);
if (count != 1) {
return false;
}
} catch (final Exception e) {
LOGGER.error("Fail to access to the DB");
context.buildConstraintViolationWithTemplate("fail to access on the DB").addConstraintViolation();
return false;
}
return true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,14 +11,16 @@ public class GenericCatcher {
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(ConstraintViolationExceptionCatcher.class);
rc.register(QueryParamExceptionCatcher.class);
rc.register(ExceptionCatcher.class);
// Catch jakarta generic errors
rc.register(WebApplicationExceptionCatcher.class);
}
}

View File

@@ -0,0 +1,34 @@
package org.kar.archidata.catcher;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.glassfish.jersey.server.ParamException.QueryParamException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class QueryParamExceptionCatcher implements ExceptionMapper<QueryParamException> {
private static final Logger LOGGER = LoggerFactory.getLogger(QueryParamExceptionCatcher.class);
@Override
public Response toResponse(final QueryParamException exception) {
LOGGER.trace("Catch IllegalArgumentException: {}", exception.getLocalizedMessage());
final RestErrorResponse ret = build(exception);
LOGGER.error("Error OID={}", ret.oid);
return Response.status(Response.Status.BAD_REQUEST).entity(ret).type(MediaType.APPLICATION_JSON).build();
}
private RestErrorResponse build(final QueryParamException exception) {
final List<RestInputError> inputError = new ArrayList<>();
inputError.add(new RestInputError("query", exception.getParameterName(), exception.getCause().getMessage()));
final String errorType = "Error on query input='" + exception.getParameterName() + "'";
return new RestErrorResponse(Response.Status.BAD_REQUEST, Instant.now().toString(), errorType,
"Input parsing fail", inputError);
}
}

View File

@@ -1,15 +1,20 @@
package org.kar.archidata.catcher;
import java.time.Instant;
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.NoWriteSpecificMode;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response;
@NoWriteSpecificMode
@ApiGenerationMode
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RestErrorResponse {
public ObjectId oid = new ObjectId();
@NotNull
@@ -27,6 +32,19 @@ public class RestErrorResponse {
@Column(length = 0)
final public String statusMessage;
@Nullable
final public List<RestInputError> inputError;
public RestErrorResponse(final Response.Status status, final String time, final String error, final String message,
final List<RestInputError> inputError) {
this.time = time;
this.name = error;
this.message = message;
this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase();
this.inputError = inputError;
}
public RestErrorResponse(final Response.Status status, final String time, final String error,
final String message) {
this.time = time;
@@ -34,6 +52,7 @@ public class RestErrorResponse {
this.message = message;
this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase();
this.inputError = null;
}
public RestErrorResponse(final Response.Status status, final String error, final String message) {
@@ -42,6 +61,7 @@ public class RestErrorResponse {
this.message = message;
this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase();
this.inputError = null;
}
public RestErrorResponse(final Response.Status status) {
@@ -50,6 +70,7 @@ public class RestErrorResponse {
this.time = Instant.now().toString();
this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase();
this.inputError = null;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
package org.kar.archidata.converter.Jakarta;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Date;
import org.kar.archidata.tools.DateTools;
import jakarta.annotation.Priority;
import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;
import jakarta.ws.rs.ext.Provider;
@Provider
@Priority(1)
public class DateParamConverterProvider implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
public <T> ParamConverter<T> getConverter(
final Class<T> rawType,
final Type genericType,
final Annotation[] annotations) {
if (rawType != Date.class) {
return null;
}
return (ParamConverter<T>) new DateParamConverter();
}
public class DateParamConverter implements ParamConverter<Date> {
@Override
public Date fromString(final String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return DateTools.parseDate(value);
} catch (final IOException e) {
throw new IllegalArgumentException("Invalid date format. Please use ISO8601", e);
}
}
@Override
public String toString(final Date value) {
if (value == null) {
return null;
}
return DateTools.serializeMilliWithUTCTimeZone(value);
}
}
}

View File

@@ -0,0 +1,52 @@
package org.kar.archidata.converter.Jakarta;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import org.kar.archidata.tools.DateTools;
import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;
import jakarta.ws.rs.ext.Provider;
@Provider
public class OffsetDateTimeParamConverterProvider implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
public <T> ParamConverter<T> getConverter(
final Class<T> rawType,
final Type genericType,
final Annotation[] annotations) {
if (rawType != OffsetDateTime.class) {
return null;
}
return (ParamConverter<T>) new OffsetDateTimeParamConverter();
}
public class OffsetDateTimeParamConverter implements ParamConverter<OffsetDateTime> {
@Override
public OffsetDateTime fromString(final String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return DateTools.parseOffsetDateTime(value);
} catch (final IOException e) {
throw new IllegalArgumentException("Invalid date format. Please use ISO8601", e);
}
}
@Override
public String toString(final OffsetDateTime value) {
if (value == null) {
return null;
}
return DateTools.serializeMilliWithUTCTimeZone(value);
}
};
}

View File

@@ -0,0 +1,19 @@
package org.kar.archidata.converter.jackson;
import java.io.IOException;
import java.util.Date;
import org.kar.archidata.tools.DateTools;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
public class DateDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
final String value = p.getText();
final Date ret = DateTools.parseDate(value);
return ret;
}
}

View File

@@ -0,0 +1,18 @@
package org.kar.archidata.converter.jackson;
import java.io.IOException;
import java.util.Date;
import org.kar.archidata.tools.DateTools;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class DateSerializer extends JsonSerializer<Date> {
@Override
public void serialize(final Date value, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeString(DateTools.serializeMilliWithUTCTimeZone(value));
}
}

View File

@@ -1,5 +1,8 @@
package org.kar.archidata.converter.jackson;
import java.time.OffsetDateTime;
import java.util.Date;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -9,6 +12,10 @@ public class JacksonModules {
final SimpleModule module = new SimpleModule();
module.addSerializer(ObjectId.class, new ObjectIdSerializer());
module.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
module.addSerializer(Date.class, new DateSerializer());
module.addDeserializer(Date.class, new DateDeserializer());
module.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
module.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
return module;
}
}

View File

@@ -0,0 +1,19 @@
package org.kar.archidata.converter.jackson;
import java.io.IOException;
import java.time.OffsetDateTime;
import org.kar.archidata.tools.DateTools;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
public class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
@Override
public OffsetDateTime deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
final String value = p.getText();
final OffsetDateTime ret = DateTools.parseOffsetDateTime(value);
return ret;
}
}

View File

@@ -0,0 +1,18 @@
package org.kar.archidata.converter.jackson;
import java.io.IOException;
import java.time.OffsetDateTime;
import org.kar.archidata.tools.DateTools;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
@Override
public void serialize(final OffsetDateTime value, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeString(DateTools.serializeMilliWithUTCTimeZone(value));
}
}

View File

@@ -185,7 +185,8 @@ public abstract class DBAccess implements Closeable {
/** @param <T>
* @param data
* @param id
* @param filterValue
* @param updateColomn
* @param option
* @return the affected rows.
* @throws Exception */
public <T, ID_TYPE> long update(
@@ -300,8 +301,7 @@ public abstract class DBAccess implements Closeable {
/** Delete items with the specific condition and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before).
* @param clazz Data model that might remove element.
* @param condition Condition to remove elements.
* @param options (Optional) Options of the request.
* @param option (Optional) Options of the request.
* @return Number of element that is removed. */
public long deleteWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz);

View File

@@ -18,6 +18,7 @@ import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.annotation.CreationTimestamp;
import org.kar.archidata.annotation.UpdateTimestamp;
import org.kar.archidata.dataAccess.addOnMongo.AddOnManyToOne;
@@ -27,6 +28,7 @@ import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.FilterValue;
import org.kar.archidata.dataAccess.options.Limit;
import org.kar.archidata.dataAccess.options.OptionSpecifyType;
import org.kar.archidata.dataAccess.options.OrderBy;
import org.kar.archidata.dataAccess.options.QueryOption;
import org.kar.archidata.db.DbIoMorphia;
@@ -128,7 +130,7 @@ public class DBAccessMorphia extends DBAccess {
return groups;
}
protected <T> void setValuedb(
protected <T> void setValueToDb(
final Class<?> type,
final T data,
final Field field,
@@ -136,7 +138,9 @@ public class DBAccessMorphia extends DBAccess {
final Document docSet,
final Document docUnSet) throws Exception {
if (field.get(data) == null) {
docUnSet.append(fieldName, "");
if (docUnSet != null) {
docUnSet.append(fieldName, "");
}
return;
}
if (type == long.class) {
@@ -151,7 +155,7 @@ public class DBAccessMorphia extends DBAccess {
docSet.append(fieldName, field.getFloat(data));
return;
}
if (type == Double.class) {
if (type == double.class) {
docSet.append(fieldName, field.getDouble(data));
return;
}
@@ -196,56 +200,40 @@ public class DBAccessMorphia extends DBAccess {
docSet.append(fieldName, tmp);
return;
}
if (type == ObjectId.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == UUID.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Date.class) {
// TODO ...
/*
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant());
ps.setTimestamp(iii.value, sqlDate);
}*/
*/
}
if (type == Instant.class) {
/*
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final String sqlDate = ((Instant) tmp).toString();
ps.setString(iii.value, sqlDate);
}
*/
}
if (type == LocalDate.class) {
/*
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final java.sql.Date sqlDate = java.sql.Date.valueOf((LocalDate) tmp);
ps.setDate(iii.value, sqlDate);
}
*/
}
if (type == LocalTime.class) {
/*
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp);
ps.setTime(iii.value, sqlDate);
}
*/
}
throw new DataAccessException("Unknown Field Type");
docSet.append(fieldName, tmp);
//throw new DataAccessException("Unknown Field Type");
}
public <T> void setValueFromDoc(
@@ -265,6 +253,11 @@ public class DBAccessMorphia extends DBAccess {
field.set(data, value);
return;
}
if (type == ObjectId.class) {
final ObjectId value = doc.get(fieldName, ObjectId.class);
field.set(data, value);
return;
}
if (type == Long.class || type == long.class) {
final Long value = doc.getLong(fieldName);
field.set(data, value);
@@ -349,8 +342,8 @@ public class DBAccessMorphia extends DBAccess {
final Object value = doc.get(fieldName, field.getType());
field.set(data, value);
} else {
final Object value = createObjectFromDocument(doc.get(fieldName, Document.class), field.getType(), null,
lazyCall);
final Object value = createObjectFromDocument(doc.get(fieldName, Document.class), field.getType(),
new QueryOptions(), lazyCall);
field.set(data, value);
}
@@ -459,32 +452,45 @@ public class DBAccessMorphia extends DBAccess {
Object uniqueId = null;
// real add in the BDD:
ObjectId insertedId = null;
final List<OptionSpecifyType> specificTypes = options.get(OptionSpecifyType.class);
try {
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase()
.getCollection(collectionName);
final Document doc = new Document();
final Document docSet = new Document();
final Document docUnSet = new Document();
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
final String tableFieldName = AnnotationTools.getFieldName(field, options).inTable();
final FieldName tableFieldName = AnnotationTools.getFieldName(field, options);
Object currentInsertValue = field.get(data);
if (AnnotationTools.isPrimaryKey(field)) {
primaryKeyField = field;
if (primaryKeyField.getType() == UUID.class) {
final UUID uuid = UuidUtils.nextUUID();
uniqueId = uuid;
doc.append(tableFieldName, uuid);
docSet.append(tableFieldName.inTable(), uuid);
continue;
} else if (primaryKeyField.getType() == Long.class || primaryKeyField.getType() == long.class) {
// By default the MongoDB does not manage the
final long id = getNextSequenceLongValue(collectionName, tableFieldName);
final long id = getNextSequenceLongValue(collectionName, tableFieldName.inTable());
uniqueId = id;
doc.append(tableFieldName, id);
docSet.append(tableFieldName.inTable(), id);
continue;
}
LOGGER.error("TODO: Manage the ID primary key for type: ");
LOGGER.error("TODO: Manage the ID primary key for type: {}=>{}", clazz.getCanonicalName(),
primaryKeyField.getType());
continue;
}
final boolean createTime = field.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
if (createTime) {
docSet.append(tableFieldName.inTable(), Date.from(Instant.now()));
continue;
}
final boolean updateTime = field.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
if (updateTime) {
docSet.append(tableFieldName.inTable(), Date.from(Instant.now()));
continue;
}
final DataAccessAddOn addOn = findAddOnforField(field);
@@ -495,19 +501,8 @@ public class DBAccessMorphia extends DBAccess {
}
continue;
}
final boolean createTime = field.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
if (createTime) {
doc.append(tableFieldName, Date.from(Instant.now()));
continue;
}
final boolean updateTime = field.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
if (updateTime) {
doc.append(tableFieldName, Date.from(Instant.now()));
continue;
}
if (currentInsertValue == null && !field.getClass().isPrimitive()) {
final DefaultValue[] defaultValue = field.getDeclaredAnnotationsByType(DefaultValue.class);
LOGGER.error("TODO: convert default value in the correct value for the DB...");
if (defaultValue.length == 0) {
continue;
} else {
@@ -518,9 +513,24 @@ public class DBAccessMorphia extends DBAccess {
currentInsertValue = convertDefaultField(value, field);
}
}
doc.append(tableFieldName, currentInsertValue);
// conversion table ...
//doc.append(tableFieldName, currentInsertValue);
if (addOn != null) {
addOn.insertData(this, field, data, options, docSet, docUnSet);
} else {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
if (field.get(data) == null) {
if (currentInsertValue != null) {
docSet.append(tableFieldName.inTable(), currentInsertValue);
}
continue;
}
}
setValueToDb(type, data, field, tableFieldName.inTable(), docSet, null);
}
}
final InsertOneResult result = collection.insertOne(doc);
final InsertOneResult result = collection.insertOne(docSet);
// Get the Object of inserted object:
insertedId = result.getInsertedId().asObjectId().getValue();
LOGGER.info("Document inserted with ID: " + insertedId);
@@ -587,14 +597,14 @@ public class DBAccessMorphia extends DBAccess {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
final String fieldName = AnnotationTools.getFieldName(field, options).inTable();
final FieldName fieldName = AnnotationTools.getFieldName(field, options);
// update field is not conditioned by filter:
final boolean updateTime = field.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
if (updateTime) {
docSet.append(fieldName, Date.from(Instant.now()));
docSet.append(fieldName.inTable(), Date.from(Instant.now()));
continue;
}
if (!filterKey.getValues().contains(fieldName)) {
if (!filterKey.getValues().contains(fieldName.inStruct())) {
continue;
} else if (AnnotationTools.isGenericField(field)) {
continue;
@@ -624,12 +634,10 @@ public class DBAccessMorphia extends DBAccess {
continue;
}
}
setValuedb(type, data, field, fieldName, docSet, docUnSet);
setValueToDb(type, data, field, fieldName.inTable(), docSet, docUnSet);
}
}
// Do the query ...
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase()
.getCollection(collectionName);
final Document actions = new Document();
@@ -652,7 +660,6 @@ public class DBAccessMorphia extends DBAccess {
}
public List<String> generateSelectField(final Class<?> clazz, final QueryOptions options) throws Exception {
// TODO: list of user select fields.
final boolean readAllfields = QueryOptions.readAllColomn(options);
final List<String> fieldsName = new ArrayList<>();
@@ -776,6 +783,8 @@ public class DBAccessMorphia extends DBAccess {
final Class<?> clazz,
final QueryOptions options,
final List<LazyGetter> lazyCall) throws Exception {
final List<OptionSpecifyType> specificTypes = options.get(OptionSpecifyType.class);
LOGGER.info("createObjectFromDocument: {}", clazz.getCanonicalName());
final boolean readAllfields = QueryOptions.readAllColomn(options);
// TODO: manage class that is defined inside a class ==> Not manage for now...
Object data = null;
@@ -790,23 +799,37 @@ public class DBAccessMorphia extends DBAccess {
"Can not find the default constructor for the class: " + clazz.getCanonicalName());
}
for (final Field elem : clazz.getFields()) {
LOGGER.info(" Inspect field: name='{}' type='{}'", elem.getName(), elem.getType().getCanonicalName());
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
LOGGER.info(" ==> static");
continue;
}
final DataAccessAddOn addOn = findAddOnforField(elem);
if (addOn != null && !addOn.canRetrieve(elem)) {
LOGGER.info(" ==> Can not retreive this field");
continue;
}
final boolean notRead = AnnotationTools.isDefaultNotRead(elem);
if (!readAllfields && notRead) {
LOGGER.info(" ==> Not read this element");
continue;
}
if (addOn != null) {
LOGGER.error("TODO: Add on not managed .6. ");
addOn.fillFromDoc(this, doc, elem, data, options, lazyCall);
} else {
setValueFromDoc(elem.getType(), data, elem, doc, lazyCall, options);
Class<?> type = elem.getType();
if (type == Object.class) {
for (final OptionSpecifyType specify : specificTypes) {
if (specify.name.equals(elem.getName())) {
type = specify.clazz;
LOGGER.info("Detect overwrite of typing var={} ... '{}' => '{}'", elem.getName(),
elem.getType().getCanonicalName(), specify.clazz.getCanonicalName());
break;
}
}
}
setValueFromDoc(type, data, elem, doc, lazyCall, options);
}
}
return data;

View File

@@ -14,6 +14,9 @@ import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -158,7 +161,7 @@ public class DBAccessSQL extends DBAccess {
} else {
// TODO : Maybe connect with a temporary not specified connection interface to a db ...
final PreparedStatement ps = this.db.getConnection()
.prepareStatement("SHOW TABLES IN `" + this.db.getCongig().getDbName() + "`");
.prepareStatement("SHOW TABLES IN `" + this.db.getConfig().getDbName() + "`");
final ResultSet rs = ps.executeQuery();
// LOGGER.info("List all tables: equals? '{}'", name);
while (rs.next()) {
@@ -223,16 +226,35 @@ public class DBAccessSQL extends DBAccess {
return out;
}
public byte[][] splitIntoGroupsOf12Bytes(final byte[] input) {
final int inputLength = input.length;
final int numOfGroups = (inputLength + 11) / 12; // Calculate the number of groups needed
final byte[][] groups = new byte[numOfGroups][12];
for (int i = 0; i < numOfGroups; i++) {
final int startIndex = i * 12;
final int endIndex = Math.min(startIndex + 12, inputLength);
groups[i] = Arrays.copyOfRange(input, startIndex, endIndex);
}
return groups;
}
public List<ObjectId> getListOfRawOIDs(final ResultSet rs, final int iii) throws SQLException, DataAccessException {
final byte[] trackString = rs.getBytes(iii);
if (rs.wasNull()) {
return null;
}
final byte[][] elements = splitIntoGroupsOf16Bytes(trackString);
final byte[][] elements = splitIntoGroupsOf12Bytes(trackString);
final List<ObjectId> out = new ArrayList<>();
for (final byte[] elem : elements) {
final ObjectId tmp = new ObjectId(elem);
out.add(tmp);
try {
final ObjectId tmp = new ObjectId(elem);
out.add(tmp);
} catch (final IllegalArgumentException ex) {
ex.printStackTrace();
LOGGER.error("Fail to parse the OID element: {}", elem);
}
}
return out;
}
@@ -259,8 +281,81 @@ public class DBAccessSQL extends DBAccess {
final CountInOut iii,
final Field field,
final PreparedStatement ps) throws Exception {
if (type == ObjectId.class) {
final Object tmp = field.get(data);
if (type == long.class) {
ps.setLong(iii.value, field.getLong(data));
iii.inc();
return;
}
if (type == int.class) {
ps.setInt(iii.value, field.getInt(data));
iii.inc();
return;
}
if (type == float.class) {
ps.setFloat(iii.value, field.getFloat(data));
iii.inc();
return;
}
if (type == double.class) {
ps.setDouble(iii.value, field.getDouble(data));
iii.inc();
return;
}
if (type == boolean.class) {
ps.setBoolean(iii.value, field.getBoolean(data));
iii.inc();
return;
}
final Object tmp = field.get(data);
if (type.isEnum()) {
if (tmp == null) {
ps.setNull(iii.value, Types.VARCHAR);
} else {
ps.setString(iii.value, tmp.toString());
}
} else if (type == Long.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.BIGINT);
} else {
ps.setLong(iii.value, (Long) tmp);
}
} else if (type == Integer.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setInt(iii.value, (Integer) tmp);
}
} else if (type == Float.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.FLOAT);
} else {
ps.setFloat(iii.value, (Float) tmp);
}
} else if (type == Double.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.DOUBLE);
} else {
ps.setDouble(iii.value, (Double) tmp);
}
} else if (type == Boolean.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setBoolean(iii.value, (Boolean) tmp);
}
} else if (type == String.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.VARCHAR);
} else {
ps.setString(iii.value, (String) tmp);
}
} else if (type == Timestamp.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setTimestamp(iii.value, (Timestamp) tmp);
}
} else if (type == ObjectId.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.BINARY);
} else {
@@ -268,75 +363,31 @@ public class DBAccessSQL extends DBAccess {
ps.setBytes(iii.value, dataByte);
}
} else if (type == UUID.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.BINARY);
} else {
final byte[] dataByte = UuidUtils.asBytes((UUID) tmp);
ps.setBytes(iii.value, dataByte);
}
} else if (type == Long.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.BIGINT);
} else {
ps.setLong(iii.value, (Long) tmp);
}
} else if (type == long.class) {
ps.setLong(iii.value, field.getLong(data));
} else if (type == Integer.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setInt(iii.value, (Integer) tmp);
}
} else if (type == int.class) {
ps.setInt(iii.value, field.getInt(data));
} else if (type == Float.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.FLOAT);
} else {
ps.setFloat(iii.value, (Float) tmp);
}
} else if (type == float.class) {
ps.setFloat(iii.value, field.getFloat(data));
} else if (type == Double.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.DOUBLE);
} else {
ps.setDouble(iii.value, (Double) tmp);
}
} else if (type == Double.class) {
ps.setDouble(iii.value, field.getDouble(data));
} else if (type == Boolean.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setBoolean(iii.value, (Boolean) tmp);
}
} else if (type == boolean.class) {
ps.setBoolean(iii.value, field.getBoolean(data));
} else if (type == Timestamp.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
ps.setTimestamp(iii.value, (Timestamp) tmp);
}
} else if (type == Date.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant());
final OffsetDateTime offsetDateTime = ((Date) tmp).toInstant().atZone(ZoneId.systemDefault())
.toOffsetDateTime();
final Timestamp sqlDate = java.sql.Timestamp
.from(offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC).toInstant());
ps.setTimestamp(iii.value, sqlDate);
}
} else if (type == OffsetDateTime.class) {
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final Timestamp sqlDate = java.sql.Timestamp
.from(((OffsetDateTime) tmp).withOffsetSameInstant(ZoneOffset.UTC).toInstant());
ps.setTimestamp(iii.value, sqlDate);
}
} else if (type == Instant.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
@@ -344,7 +395,6 @@ public class DBAccessSQL extends DBAccess {
ps.setString(iii.value, sqlDate);
}
} else if (type == LocalDate.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
@@ -352,27 +402,12 @@ public class DBAccessSQL extends DBAccess {
ps.setDate(iii.value, sqlDate);
}
} else if (type == LocalTime.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.INTEGER);
} else {
final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp);
ps.setTime(iii.value, sqlDate);
}
} else if (type == String.class) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.VARCHAR);
} else {
ps.setString(iii.value, (String) tmp);
}
} else if (type.isEnum()) {
final Object tmp = field.get(data);
if (tmp == null) {
ps.setNull(iii.value, Types.VARCHAR);
} else {
ps.setString(iii.value, tmp.toString());
}
} else {
throw new DataAccessException("Unknown Field Type: " + type.getCanonicalName());
}
@@ -501,7 +536,9 @@ public class DBAccessSQL extends DBAccess {
if (rs.wasNull()) {
field.set(data, null);
} else {
field.set(data, Date.from(tmp.toInstant()));
final OffsetDateTime odt = tmp.toInstant().atOffset(ZoneOffset.UTC);
odt.atZoneSameInstant(ZoneId.systemDefault());
field.set(data, Date.from(odt.toInstant()));
countNotNull.inc();
}
} catch (final SQLException ex) {
@@ -516,6 +553,28 @@ public class DBAccessSQL extends DBAccess {
countNotNull.inc();
}
}
} else if (type == OffsetDateTime.class) {
try {
final Timestamp tmp = rs.getTimestamp(count.value);
if (rs.wasNull()) {
field.set(data, null);
} else {
final OffsetDateTime odt = tmp.toInstant().atOffset(ZoneOffset.UTC);
field.set(data, odt);
countNotNull.inc();
}
} catch (final SQLException ex) {
final String tmp = rs.getString(count.value);
LOGGER.error("Fail to parse the SQL time !!! {}", tmp);
if (rs.wasNull()) {
field.set(data, null);
} else {
final OffsetDateTime date = DateTools.parseOffsetDateTime(tmp);
LOGGER.error("Fail to parse the SQL time !!! {}", date);
field.set(data, date);
countNotNull.inc();
}
}
} else if (type == Instant.class) {
final String tmp = rs.getString(count.value);
if (rs.wasNull()) {
@@ -815,6 +874,9 @@ public class DBAccessSQL extends DBAccess {
@Override
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public <T> T insert(final T data, final QueryOption... option) throws Exception {
if (data == null) {
throw new DataAccessException("Try to check a null data ==> wrong API");
}
final Class<?> clazz = data.getClass();
final QueryOptions options = new QueryOptions(option);
@@ -1273,7 +1335,7 @@ public class DBAccessSQL extends DBAccess {
) throws Exception {
final boolean readAllfields = QueryOptions.readAllColomn(options);
final String tableName = AnnotationTools.getTableName(clazz, options);
final String primaryKey = AnnotationTools.getPrimaryKeyField(clazz).getName();
final String primaryKey = AnnotationTools.getFieldNameRaw(AnnotationTools.getPrimaryKeyField(clazz));
boolean firstField = true;
for (final Field elem : clazz.getFields()) {

View File

@@ -9,6 +9,7 @@ import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.InternalServerErrorException;
@@ -110,12 +111,6 @@ public class DataAccess {
}
}
/** @param <T>
* @param data
* @param id
* @param filterValue
* @return the affected rows.
* @throws Exception */
public static <T, ID_TYPE> long update(
final T data,
final ID_TYPE id,
@@ -189,6 +184,7 @@ public class DataAccess {
}
}
@Nullable
public static <T, ID_TYPE> T get(final Class<T> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
@@ -223,7 +219,6 @@ public class DataAccess {
/** Delete items with the specific condition and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before).
* @param clazz Data model that might remove element.
* @param condition Condition to remove elements.
* @param options (Optional) Options of the request.
* @return Number of element that is removed. */
public static long deleteWhere(final Class<?> clazz, final QueryOption... options) throws Exception {

View File

@@ -1,174 +0,0 @@
package org.kar.archidata.dataAccess.addOnMongo;
import java.lang.reflect.Field;
import java.util.List;
import org.bson.Document;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DBAccessMorphia;
import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.LazyGetter;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.validation.constraints.NotNull;
public class AddOnDataJson implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnDataJson.class);
@Override
public Class<?> getAnnotationClass() {
return DataJson.class;
}
@Override
public String getSQLFieldType(final Field elem, final QueryOptions options) throws DataAccessException {
final String fieldName = AnnotationTools.getFieldName(elem, options).inTable();
return DataFactory.convertTypeInSQL(String.class, fieldName);
}
@Override
public boolean isCompatibleField(final Field elem) {
final DataJson decorators = elem.getDeclaredAnnotation(DataJson.class);
return decorators != null;
}
@Override
public void insertData(
final DBAccessMorphia ioDb,
final Field field,
final Object rootObject,
final QueryOptions options,
final Document docSet,
final Document docUnSet) throws Exception {
/*
final Object data = field.get(rootObject);
if (data == null) {
ps.setNull(iii.value, Types.VARCHAR);
}
final ObjectMapper objectMapper = ContextGenericTools.createObjectMapper();
final String dataString = objectMapper.writeValueAsString(data);
ps.setString(iii.value, dataString);
iii.inc();
*/
}
@Override
public boolean canInsert(final Field field) {
return true;
}
@Override
public boolean isInsertAsync(final Field field) {
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) throws Exception {
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(name);
count.inc();
return;
}
@Override
public void fillFromDoc(
final DBAccessMorphia ioDb,
final Document doc,
final Field field,
final Object data,
final QueryOptions options,
final List<LazyGetter> lazyCall) throws Exception {
/*
final String fieldName = AnnotationTools.getFieldName(field);
if (!doc.containsKey(fieldName)) {
field.set(data, null);
return;
}
final String jsonData = rs.getString(count.value);
if (!rs.wasNull()) {
final ObjectMapper objectMapper = ContextGenericTools.createObjectMapper();
if (field.getType() == List.class) {
final ParameterizedType listType = (ParameterizedType) field.getGenericType();
final Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];
if (listClass == Long.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Long>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == Float.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Float>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == Double.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Double>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == Integer.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Integer>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == Short.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<Short>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == String.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<String>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
if (listClass == UUID.class) {
final Object dataParsed = objectMapper.readValue(jsonData, new TypeReference<List<UUID>>() {});// field.getType());
field.set(data, dataParsed);
return;
}
LOGGER.warn("Maybe fail to translate Model in datajson list: List<{}>", listClass.getCanonicalName());
}
final Object dataParsed = objectMapper.readValue(jsonData, field.getType());
field.set(data, dataParsed);
}
*/
}
@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,
final QueryOptions options) throws Exception {
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, JsonValue.class, options);
}
}

View File

@@ -173,34 +173,6 @@ public class AddOnManyToMany implements DataAccessAddOn {
count.inc();
}
@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) throws Exception {
if (field.getType() != List.class) {
return;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options);
}
final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class);
if (decorators == null) {
return;
}
if (objectClass == decorators.targetEntity()) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options);
}
}
@Override
public void fillFromDoc(
final DBAccessMorphia ioDb,

View File

@@ -5,9 +5,9 @@ import java.util.List;
import java.util.UUID;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DBAccessMorphia;
import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.LazyGetter;
@@ -16,7 +16,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
public class AddOnManyToOne implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
@@ -52,7 +51,7 @@ public class AddOnManyToOne implements DataAccessAddOn {
final Document docUnSet) throws Exception {
final FieldName fieldName = AnnotationTools.getFieldName(field, options);
final Object data = field.get(rootObject);
if (field.get(data) == null) {
if (data == null) {
docUnSet.append(fieldName.inTable(), "");
return;
} else if (field.getType() == Long.class) {
@@ -70,6 +69,9 @@ public class AddOnManyToOne implements DataAccessAddOn {
} else if (field.getType() == UUID.class) {
final UUID dataTyped = (UUID) data;
docSet.append(fieldName.inTable(), dataTyped);
} else if (field.getType() == ObjectId.class) {
final ObjectId dataTyped = (ObjectId) data;
docSet.append(fieldName.inTable(), dataTyped);
} else {
final Field idField = AnnotationTools.getFieldOfId(field.getType());
final Object uid = idField.get(data);
@@ -84,7 +86,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
@Override
public boolean canInsert(final Field field) {
if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class
|| field.getType() == String.class || field.getType() == UUID.class) {
|| field.getType() == String.class || field.getType() == UUID.class
|| field.getType() == ObjectId.class) {
return true;
}
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
@@ -103,7 +106,7 @@ public class AddOnManyToOne implements DataAccessAddOn {
public boolean canRetrieve(final Field field) {
final Class<?> classType = field.getType();
if (classType == Long.class || classType == Integer.class || classType == Short.class
|| classType == String.class || classType == UUID.class) {
|| classType == String.class || classType == UUID.class || classType == ObjectId.class) {
return true;
}
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
@@ -113,36 +116,6 @@ public class AddOnManyToOne implements DataAccessAddOn {
return false;
}
@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) throws Exception {
if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class
|| field.getType() == String.class || field.getType() == UUID.class) {
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(name);
count.inc();
return;
}
final ManyToOne decorators = field.getDeclaredAnnotation(ManyToOne.class);
if (field.getType() == decorators.targetEntity()) {
// no eager possible for no sql
querySelect.append(" ");
querySelect.append(tableName);
querySelect.append(".");
querySelect.append(name);
count.inc();
}
}
@Override
public void fillFromDoc(
final DBAccessMorphia ioDb,
@@ -159,7 +132,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
}
// local field to manage no remote object to retrieve.
if (field.getType() == Long.class || field.getType() == Integer.class || field.getType() == Short.class
|| field.getType() == String.class || field.getType() == UUID.class) {
|| field.getType() == String.class || field.getType() == UUID.class
|| field.getType() == ObjectId.class) {
ioDb.setValueFromDoc(field.getType(), data, field, doc, lazyCall, options);
return;
}
@@ -174,9 +148,8 @@ public class AddOnManyToOne implements DataAccessAddOn {
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 ...
final Long foreignKey = doc.getLong(fieldName);
final Long foreignKey = doc.getLong(fieldName.inTable());
if (foreignKey != null) {
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.get(decorators.targetEntity(), foreignKey);
@@ -189,9 +162,22 @@ public class AddOnManyToOne implements DataAccessAddOn {
}
} 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 = doc.get(fieldName, UUID.class);
final UUID foreignKey = doc.get(fieldName.inTable(), UUID.class);
if (foreignKey != null) {
final LazyGetter lambda = () -> {
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.get(decorators.targetEntity(), foreignKey);
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
} else if (remotePrimaryKeyType == ObjectId.class) {
// here we have the field, the data and the the remote value ==> can create callback that generate the update of the value ...
final ObjectId foreignKey = doc.get(fieldName.inTable(), ObjectId.class);
if (foreignKey != null) {
// In the lazy mode, the request is done in asynchronous mode, they will be done after...
final LazyGetter lambda = () -> {
// TODO: update to have get with abstract types ....
final Object foreignData = ioDb.get(decorators.targetEntity(), foreignKey);
@@ -221,7 +207,7 @@ public class AddOnManyToOne implements DataAccessAddOn {
final QueryOptions options) throws Exception {
final Class<?> classType = field.getType();
if (classType == Long.class || classType == Integer.class || classType == Short.class
|| classType == String.class || classType == UUID.class) {
|| classType == String.class || classType == UUID.class || classType == ObjectId.class) {
DataFactory.createTablesSpecificType(tableName, primaryField, field, mainTableBuilder, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, classType, options);
} else {

View File

@@ -2,62 +2,28 @@ package org.kar.archidata.dataAccess.addOnMongo;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DBAccessMorphia;
import org.kar.archidata.dataAccess.LazyGetter;
import org.kar.archidata.dataAccess.QueryCondition;
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.LoggerFactory;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull;
public class AddOnOneToMany implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnOneToMany.class);
static final String SEPARATOR_LONG = "-";
/** 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("-"));
}
/** extract a list of "-" separated element from a SQL input data.
* @param rs Result Set of the BDD
* @param iii Id in the result set
* @return The list of Long value
* @throws SQLException if an error is generated in the sql request. */
protected static List<Long> getListOfIds(final ResultSet rs, final int iii) throws SQLException {
final String trackString = rs.getString(iii);
if (rs.wasNull()) {
return null;
}
final List<Long> out = new ArrayList<>();
final String[] elements = trackString.split("-");
for (final String elem : elements) {
final Long tmp = Long.parseLong(elem);
out.add(tmp);
}
return out;
}
@Override
public Class<?> getAnnotationClass() {
return OneToMany.class;
@@ -98,7 +64,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) {
if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
return true;
}
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
@@ -111,104 +77,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
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 FieldName remoteTablePrimaryKeyName = AnnotationTools
.getFieldName(AnnotationTools.getPrimaryKeyField(targetEntity), options);
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.inTable());
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
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) throws Exception {
if (field.getType() != List.class) {
return;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.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));
}
}
}
// in first implementation we did not keep the data in the 2 Objects, bun we will do it after to have a faster table interactions.
@Override
public void fillFromDoc(
final DBAccessMorphia ioDb,
@@ -222,11 +91,12 @@ public class AddOnOneToMany implements DataAccessAddOn {
return;
}
final String fieldName = AnnotationTools.getFieldName(field, options).inTable();
if (!doc.containsKey(fieldName)) {
field.set(data, null);
return;
}
final FieldName fieldName = AnnotationTools.getFieldName(field, options);
// in step 1 the fields are not stored in the local element
// if (!doc.containsKey(fieldName.inTable())) {
// field.set(data, null);
// return;
// }
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
@@ -234,62 +104,46 @@ public class AddOnOneToMany implements DataAccessAddOn {
if (decorators == null) {
return;
}
if (objectClass == Long.class || objectClass == UUID.class) {
final Object value = doc.get(fieldName, field.getType());
field.set(data, value);
if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
if (true) {
// DEVELOPMENT step 1 we search all the element in the list:
// get the curentObject primary key
final Field primaryField = AnnotationTools.getPrimaryKeyField(data.getClass());
final String primaryKeyName = AnnotationTools.getFieldNameRaw(primaryField);
final Object primaryKey = doc.get(primaryKeyName, primaryField.getType());
// get the remotes objects
final List<?> returnValue = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(decorators.mappedBy(), "=", primaryKey)));
// extract the primary key of the remote objects
final Field remotePrimaryField = AnnotationTools.getPrimaryKeyField(decorators.targetEntity());
final String remotePrimaryKeyName = AnnotationTools.getFieldNameRaw(remotePrimaryField);
final List<Object> listOfRemoteKeys = new ArrayList<>();
for (final var item : returnValue) {
listOfRemoteKeys.add(remotePrimaryField.get(item));
}
// inject in the current data field
if (listOfRemoteKeys.size() != 0) {
field.set(data, listOfRemoteKeys);
}
} else {
// DEVELOPMENT In step 2 this will work well:
final Object value = doc.get(fieldName.inTable(), field.getType());
field.set(data, value);
}
return;
}
if (objectClass == decorators.targetEntity()) {
Long parentIdTmp = null;
UUID parendUuidTmp = null;
try {
final Object value = doc.get(fieldName);
if (value instanceof final Long valueCasted) {
parentIdTmp = valueCasted;
} else if (value instanceof final UUID valueCasted) {
parendUuidTmp = valueCasted;
}
} catch (final Exception ex) {
LOGGER.error("fail to find the correct type... {}", ex.getMessage());
}
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 (parentId != null) {
final LazyGetter lambda = () -> {
@SuppressWarnings("unchecked")
final Object foreignData = ioDb.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 = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendUuid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
// Maybe in a second step we do not like this but this way is efficient too.
// get the curentObject primary key
final Field primaryField = AnnotationTools.getPrimaryKeyField(data.getClass());
final String primaryKeyName = AnnotationTools.getFieldNameRaw(primaryField);
final Object primaryKey = doc.get(primaryKeyName, primaryField.getType());
// get the remotes objects
final List<?> returnValue = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(decorators.mappedBy(), "=", primaryKey)));
// inject in the current data field
if (returnValue.size() != 0) {
field.set(data, returnValue);
}
}
}

View File

@@ -5,13 +5,10 @@ import java.sql.SQLException;
import java.util.List;
import org.bson.Document;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DBAccessMorphia;
import org.kar.archidata.dataAccess.LazyGetter;
import org.kar.archidata.dataAccess.QueryOptions;
import jakarta.validation.constraints.NotNull;
public interface DataAccessAddOn {
/** Get the Class of the declaration annotation
* @return The annotation class */
@@ -29,12 +26,6 @@ public interface DataAccessAddOn {
* @return True of the field is manage by the current Add-on. */
boolean isCompatibleField(Field elem);
/** Insert data in the specific field (the field must be in the current db, otherwiise it does not work at all.
* @param ps DB statement interface.
* @param data The date to inject.
* @param iii The index of injection
* @return the new index of injection in case of multiple value management
* @throws SQLException */
void insertData(
final DBAccessMorphia ioDb,
final Field field,
@@ -57,16 +48,6 @@ public interface DataAccessAddOn {
return false;
}
void generateQuery(
@NotNull String tableName,
@NotNull final String primaryKey,
@NotNull Field field,
@NotNull final StringBuilder querySelect,
@NotNull final StringBuilder query,
@NotNull String name,
@NotNull CountInOut count,
QueryOptions options) throws Exception;
// Return the number of colomn read
void fillFromDoc(
final DBAccessMorphia ioDb,
@@ -78,14 +59,7 @@ public interface DataAccessAddOn {
throws Exception, SQLException, IllegalArgumentException, IllegalAccessException;
/** Create associated table of the specific element.
* @param tableName
* @param elem
* @param mainTableBuilder
* @param ListOtherTables
* @param createIfNotExist
* @param createDrop
* @param fieldId
* @throws Exception */
*/
void createTables(
String tableName,
final Field primaryField,

View File

@@ -16,7 +16,6 @@ import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DBAccessMorphia;
import org.kar.archidata.dataAccess.DBAccessSQL;
import org.kar.archidata.dataAccess.DataFactory;
import org.kar.archidata.dataAccess.LazyGetter;
@@ -220,68 +219,62 @@ public class AddOnDataJson implements DataAccessAddOn {
public static void addLink(
final DBAccess ioDb,
final Class<?> clazz,
final String columnId,
final Object id,
final String columnList,
final Object remoteKey) throws Exception {
final String clazzPrimaryKeyName,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToAdd) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final QueryOptions options = new QueryOptions(new OverrideTableName(tableName),
new OptionSpecifyType("id", id.getClass()),
new OptionSpecifyType("covers", remoteKey.getClass(), true));
if (columnId != null && !columnId.equals("id")) {
options.add(new OptionRenameColumn("id", columnId));
new OptionSpecifyType("id", clazzPrimaryKeyValue.getClass()),
new OptionSpecifyType("covers", valueToAdd.getClass(), true));
if (clazzPrimaryKeyName != null && !clazzPrimaryKeyName.equals("id")) {
options.add(new OptionRenameColumn("id", clazzPrimaryKeyName));
}
if (columnList != null && !columnList.equals("covers")) {
options.add(new OptionRenameColumn("covers", columnList));
if (fieldNameToUpdate != null && !fieldNameToUpdate.equals("covers")) {
options.add(new OptionRenameColumn("covers", fieldNameToUpdate));
}
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, id, options.getAllArray());
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, clazzPrimaryKeyValue, options.getAllArray());
if (data.covers == null) {
data.covers = new ArrayList<>();
}
for (final Object elem : data.covers) {
if (elem.equals(remoteKey)) {
if (elem.equals(valueToAdd)) {
return;
}
}
data.covers.add(remoteKey);
data.covers.add(valueToAdd);
ioDb.update(data, data.id, List.of("covers"), options.getAllArray());
}
public static void removeLink(
final DBAccess ioDb,
final Class<?> clazz,
final String columnId,
final Object id,
final String columnList,
final Object remoteKey) throws Exception {
if (ioDb instanceof final DBAccessSQL daSQL) {
final String tableName = AnnotationTools.getTableName(clazz);
final QueryOptions options = new QueryOptions(new OverrideTableName(tableName),
new OptionSpecifyType("id", id.getClass()),
new OptionSpecifyType("covers", remoteKey.getClass(), true));
if (columnId != null && !columnId.equals("id")) {
options.add(new OptionRenameColumn("id", columnId));
}
if (columnList != null && !columnList.equals("covers")) {
options.add(new OptionRenameColumn("covers", columnList));
}
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, id, options.getAllArray());
if (data.covers == null) {
return;
}
final List<Object> newList = new ArrayList<>();
for (final Object elem : data.covers) {
if (elem.equals(remoteKey)) {
continue;
}
newList.add(elem);
}
data.covers = newList;
ioDb.update(data, data.id, List.of("covers"), options.getAllArray());
} else if (ioDb instanceof final DBAccessMorphia dam) {
} else {
throw new DataAccessException("DataAccess Not managed");
final String clazzPrimaryKeyName,
final Object clazzPrimaryKeyValue,
final String fieldNameToUpdate,
final Object valueToRemove) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final QueryOptions options = new QueryOptions(new OverrideTableName(tableName),
new OptionSpecifyType("id", clazzPrimaryKeyValue.getClass()),
new OptionSpecifyType("covers", valueToRemove.getClass(), true));
if (clazzPrimaryKeyName != null && !clazzPrimaryKeyName.equals("id")) {
options.add(new OptionRenameColumn("id", clazzPrimaryKeyName));
}
if (fieldNameToUpdate != null && !fieldNameToUpdate.equals("covers")) {
options.add(new OptionRenameColumn("covers", fieldNameToUpdate));
}
final TableCoversGeneric data = ioDb.get(TableCoversGeneric.class, clazzPrimaryKeyValue, options.getAllArray());
if (data.covers == null) {
return;
}
final List<Object> newList = new ArrayList<>();
for (final Object elem : data.covers) {
if (elem.equals(valueToRemove)) {
continue;
}
newList.add(elem);
}
data.covers = newList;
ioDb.update(data, data.id, List.of("covers"), options.getAllArray());
}
}

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.dataAccess.CountInOut;
@@ -110,7 +111,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
if (objectClass == Long.class || objectClass == UUID.class) {
if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
return true;
}
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
@@ -201,7 +202,7 @@ public class AddOnOneToMany implements DataAccessAddOn {
return;
}
// TODO: manage better the eager and lazy !!
if (objectClass == Long.class || objectClass == UUID.class) {
if (objectClass == Long.class || objectClass == UUID.class || objectClass == ObjectId.class) {
generateConcatQuery(tableName, primaryKey, field, querySelect, query, name, count, options,
decorators.targetEntity(), decorators.mappedBy());
return;
@@ -230,81 +231,115 @@ public class AddOnOneToMany implements DataAccessAddOn {
final CountInOut count,
final QueryOptions options,
final List<LazyGetter> lazyCall) throws Exception {
if (field.getType() != List.class) {
LOGGER.error("Can not OneToMany with other than List Model: {}", field.getType().getCanonicalName());
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 = ioDb.getListOfIds(rs, count.value, SEPARATOR_LONG);
field.set(data, idList);
count.inc();
return;
} else if (objectClass == UUID.class) {
final List<UUID> idList = ioDb.getListOfRawUUIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
}
if (objectClass == decorators.targetEntity()) {
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 = ioDb.getListOfRawUUIDs(rs, count.value);
parendUuidTmp = idList.get(0);
count.inc();
try {
if (field.getType() != List.class) {
LOGGER.error("Can not OneToMany with other than List Model: {}", field.getType().getCanonicalName());
return;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType())
.getActualTypeArguments()[0];
final OneToMany decorators = field.getDeclaredAnnotation(OneToMany.class);
if (decorators == null) {
return;
}
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);
final List<Long> idList = ioDb.getListOfIds(rs, count.value, SEPARATOR_LONG);
field.set(data, idList);
count.inc();
return;
} else if (objectClass == UUID.class) {
LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList);
final List<UUID> idList = ioDb.getListOfRawUUIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
} else if (objectClass == ObjectId.class) {
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
field.set(data, idList);
count.inc();
return;
}
if (objectClass == decorators.targetEntity()) {
if (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 = ioDb.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 = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendUuid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
final String destinationField = decorators.mappedBy();
final Field typeDestination = AnnotationTools.getFieldNamed(objectClass, destinationField);
final Class<?> destinationClass = typeDestination.getType();
Long parentIdTmp = null;
UUID parendUuidTmp = null;
ObjectId parendOidTmp = null;
if (destinationClass == Long.class) {
final String modelData = rs.getString(count.value);
parentIdTmp = Long.valueOf(modelData);
count.inc();
} else if (destinationClass == UUID.class) {
final List<UUID> idList = ioDb.getListOfRawUUIDs(rs, count.value);
parendUuidTmp = idList.get(0);
count.inc();
} else if (destinationClass == ObjectId.class) {
final List<ObjectId> idList = ioDb.getListOfRawOIDs(rs, count.value);
parendOidTmp = idList.get(0);
count.inc();
}
final Long parentId = parentIdTmp;
final UUID parendUuid = parendUuidTmp;
final ObjectId parendOid = parendOidTmp;
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;
} else if (objectClass == ObjectId.class) {
LOGGER.error("Need to retreive all primary key of all elements");
//field.set(data, idList);
return;
}
if (objectClass == decorators.targetEntity()) {
if (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 = ioDb.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 = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendUuid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
} else if (parendOid != null) {
final LazyGetter lambda = () -> {
@SuppressWarnings("unchecked")
final Object foreignData = ioDb.getsWhere(decorators.targetEntity(),
new Condition(new QueryCondition(mappingKey, "=", parendOid)));
if (foreignData == null) {
return;
}
field.set(data, foreignData);
};
lazyCall.add(lambda);
}
}
}
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Fail to parse remote {}", ex.getMessage());
}
}

View File

@@ -31,10 +31,9 @@ public interface DataAccessAddOn {
boolean isCompatibleField(Field elem);
/** Insert data in the specific field (the field must be in the current db, otherwiise it does not work at all.
* @param ps DB statement interface.
* @param ioDb DB statement interface.
* @param data The date to inject.
* @param iii The index of injection
* @return the new index of injection in case of multiple value management
* @throws SQLException */
void insertData(final DBAccessSQL ioDb, PreparedStatement ps, final Field field, Object data, CountInOut iii)
throws Exception, SQLException, IllegalArgumentException, IllegalAccessException;
@@ -76,9 +75,11 @@ public interface DataAccessAddOn {
/** Create associated table of the specific element.
* @param tableName
* @param elem
* @param primaryField
* @param field
* @param mainTableBuilder
* @param ListOtherTables
* @param preActionList
* @param postActionList
* @param createIfNotExist
* @param createDrop
* @param fieldId

View File

@@ -9,7 +9,7 @@ import org.kar.archidata.dataAccess.QueryOptions;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckFunctionInterface {
/** 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 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. */

View File

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

View File

@@ -76,7 +76,7 @@ public class Condition extends QueryOption {
}
final List<Bson> filter = new ArrayList<>();
if (exclude_deleted && deletedFieldName != null) {
filter.add(Filters.eq(deletedFieldName, false));
filter.add(Filters.or(Filters.eq(deletedFieldName, false), Filters.exists(deletedFieldName, false)));
}
// Check if we have a condition to generate
if (this.condition != null) {

View File

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

View File

@@ -16,6 +16,6 @@ public class InputException extends Exception {
public InputException(final String variable, final String message) {
super(message);
this.missingVariable = variable;
this.status = Response.Status.NOT_ACCEPTABLE;
this.status = Response.Status.BAD_REQUEST;
}
}

View File

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

View File

@@ -151,45 +151,6 @@ public class DotClassElement {
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();

View File

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

View File

@@ -7,11 +7,11 @@ 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 org.kar.archidata.annotation.apiGenerator.ApiInputOptional;
import org.kar.archidata.annotation.apiGenerator.ApiAsyncType;
import org.kar.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.kar.archidata.annotation.method.ARCHIVE;
import org.kar.archidata.annotation.method.RESTORE;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.Consumes;
@@ -53,7 +53,7 @@ public class ApiTool {
}
public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class);
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiTypeScriptProgress.class);
if (annotation.length == 0) {
return false;
}
@@ -159,8 +159,8 @@ public class ApiTool {
return ((QueryParam) annotation[0]).value();
}
public static boolean apiAnnotationGetFormDataOptional(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataOptional.class);
public static boolean apiAnnotationGetApiInputOptional(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiInputOptional.class);
if (annotation.length == 0) {
return false;
}
@@ -176,19 +176,19 @@ public class ApiTool {
}
public static Class<?>[] apiAnnotationGetAsyncType(final Parameter element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiAsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
return ((ApiAsyncType) annotation[0]).value();
}
public static Class<?>[] apiAnnotationGetAsyncType(final Method element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class);
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ApiAsyncType.class);
if (annotation.length == 0) {
return null;
}
return ((AsyncType) annotation[0]).value();
return ((ApiAsyncType) annotation[0]).value();
}
public static List<String> apiAnnotationGetConsumes(final Method element) throws Exception {

View File

@@ -6,11 +6,14 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ClassEnumModel extends ClassModel {
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.tools.AnnotationCreator;
public class ClassEnumModel extends ClassModel {
protected ClassEnumModel(final Class<?> clazz) {
this.originClasses = clazz;
this.noWriteSpecificMode = true;
this.apiGenerationMode = AnnotationCreator.createAnnotation(ApiGenerationMode.class, "readable", true,
"creatable", false, "updatable", false);
}
@Override

View File

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

View File

@@ -11,13 +11,23 @@ import java.util.List;
import java.util.Set;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.annotation.apiGenerator.ApiNotNull;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.AnnotationCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class ClassObjectModel extends ClassModel {
@@ -25,6 +35,10 @@ public class ClassObjectModel extends ClassModel {
public ClassObjectModel(final Class<?> clazz) {
this.originClasses = clazz;
final ApiGenerationMode tmp = AnnotationTools.get(clazz, ApiGenerationMode.class);
if (tmp != null) {
this.apiGenerationMode = tmp;
}
}
@Override
@@ -61,44 +75,59 @@ public class ClassObjectModel extends ClassModel {
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,
Size stringSize, // String Size
Min min, // number min value
Max max, // number max value
DecimalMin decimalMin,
DecimalMax decimalMax,
Pattern pattern,
Email email,
ApiAccessLimitation accessLimitation,
Boolean notNull,
ApiNotNull apiNotNull,
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) {
public FieldProperty(//
final String name, //
final ClassModel model, //
final ClassModel linkClass, //
final String comment, //
final Size stringSize, //
final Min min, //
final Max max, //
final DecimalMin decimalMin, //
final DecimalMax decimalMax, //
final Pattern pattern, //
final Email email, //
final ApiAccessLimitation accessLimitation, //
final Boolean notNull, //
final ApiNotNull apiNotNull, //
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.stringSize = stringSize;
this.decimalMin = decimalMin;
this.decimalMax = decimalMax;
this.pattern = pattern;
this.email = email;
this.min = min;
this.max = max;
this.readOnly = readOnly;
if (accessLimitation == null) {
this.accessLimitation = AnnotationCreator.createAnnotation(ApiAccessLimitation.class);
} else {
this.accessLimitation = accessLimitation;
}
this.notNull = notNull;
this.apiNotNull = apiNotNull;
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) {
@@ -137,12 +166,16 @@ public class ClassObjectModel extends ClassModel {
ClassModel.getModel(field.getGenericType(), previous), //
getSubModelIfExist(field, previous), //
AnnotationTools.getSchemaDescription(field), //
getStringMinSize(field), //
getStringMaxSize(field), //
AnnotationTools.getConstraintsSize(field), //
AnnotationTools.getConstraintsMin(field), //
AnnotationTools.getConstraintsMax(field), //
AnnotationTools.getSchemaReadOnly(field), //
AnnotationTools.getConstraintsDecimalMin(field), //
AnnotationTools.getConstraintsDecimalMax(field), //
AnnotationTools.getConstraintsPattern(field), //
AnnotationTools.getConstraintsEmail(field), //
AnnotationTools.get(field, ApiAccessLimitation.class), //
AnnotationTools.getConstraintsNotNull(field), //
AnnotationTools.get(field, ApiNotNull.class), //
AnnotationTools.getColumnNotNull(field), //
AnnotationTools.getNullable(field));
}
@@ -187,7 +220,6 @@ public class ClassObjectModel extends ClassModel {
}
this.analyzeDone = true;
final Class<?> clazz = this.originClasses;
this.noWriteSpecificMode = AnnotationTools.getNoWriteSpecificMode(clazz);
this.isPrimitive = clazz.isPrimitive();
if (this.isPrimitive) {
return;
@@ -254,15 +286,43 @@ public class ClassObjectModel extends ClassModel {
}
@Override
public List<String> getReadOnlyField() {
public List<String> getReadOnlyFields() {
final List<String> out = new ArrayList<>();
for (final FieldProperty field : this.fields) {
if (field.readOnly()) {
if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) {
out.add(field.name);
}
}
if (this.extendsClass != null) {
out.addAll(this.extendsClass.getReadOnlyField());
out.addAll(this.extendsClass.getReadOnlyFields());
}
return out;
}
@Override
public List<String> getCreateFields() {
final List<String> out = new ArrayList<>();
for (final FieldProperty field : this.fields) {
if (field.accessLimitation().creatable()) {
out.add(field.name);
}
}
if (this.extendsClass != null) {
out.addAll(this.extendsClass.getReadOnlyFields());
}
return out;
}
@Override
public List<String> getUpdateFields() {
final List<String> out = new ArrayList<>();
for (final FieldProperty field : this.fields) {
if (field.accessLimitation().updatable()) {
out.add(field.name);
}
}
if (this.extendsClass != null) {
out.addAll(this.extendsClass.getReadOnlyFields());
}
return out;
}

View File

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

View File

@@ -6,8 +6,12 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.kar.archidata.externalRestApi.model.ClassEnumModel;
import org.kar.archidata.externalRestApi.model.ClassListModel;
@@ -27,6 +31,9 @@ public class TsClassElement {
NORMAL // Normal Object to interpret.
}
public static final String MODEL_TYPE_UPDATE = "Update";
public static final String MODEL_TYPE_CREATE = "Create";
public List<ClassModel> models;
public String zodName;
public String tsTypeName;
@@ -138,7 +145,7 @@ public class TsClassElement {
out.append(this.tsTypeName);
out.append(");\n");
}
out.append(generateExportCheckFunctionWrite(""));
out.append(generateExportCheckFunctionAppended(""));
return out.toString();
}
@@ -168,28 +175,53 @@ public class TsClassElement {
return out.toString();
}
private String generateExportCheckFunctionWrite(final String writeString) {
return generateExportCheckFunction(this.tsCheckType + writeString, this.tsTypeName + writeString,
this.zodName + writeString);
private String generateExportCheckFunctionAppended(final String appendString) {
return generateExportCheckFunction(this.tsCheckType + appendString, this.tsTypeName + appendString,
this.zodName + appendString);
}
public String generateImports(final List<ClassModel> depModels, final TsClassElementGroup tsGroup)
public String generateImports(final Set<ClassModel> depModels, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
final Set<TsClassElement> typeScriptModelAlreadyImported = new HashSet<>();
final Map<String, String> mapOutput = new TreeMap<>();
for (final ClassModel depModel : depModels) {
final StringBuilder inputStream = new StringBuilder();
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");
if (typeScriptModelAlreadyImported.contains(tsModel)) {
LOGGER.trace("Model alredy imported for typescript");
continue;
}
typeScriptModelAlreadyImported.add(tsModel);
if (tsModel.nativeType != DefinedPosition.NATIVE) {
inputStream.append("import {");
if (tsModel.nativeType != DefinedPosition.NORMAL
|| tsModel.models.get(0).getApiGenerationMode().read()) {
inputStream.append(tsModel.zodName);
}
if (tsModel.nativeType == DefinedPosition.NORMAL
&& tsModel.models.get(0).getApiGenerationMode().update()) {
inputStream.append(", ");
inputStream.append(tsModel.zodName);
inputStream.append(MODEL_TYPE_UPDATE);
inputStream.append(" ");
}
if (tsModel.nativeType == DefinedPosition.NORMAL
&& tsModel.models.get(0).getApiGenerationMode().create()) {
inputStream.append(", ");
inputStream.append(tsModel.zodName);
inputStream.append(MODEL_TYPE_CREATE);
inputStream.append(" ");
}
inputStream.append("} from \"./");
inputStream.append(tsModel.fileName);
inputStream.append("\";\n");
mapOutput.put(tsModel.fileName.toString().toLowerCase(), inputStream.toString());
}
}
final StringBuilder out = new StringBuilder();
for (final Entry<String, String> elem : mapOutput.entrySet()) {
out.append(elem.getValue());
}
return out.toString();
@@ -222,6 +254,9 @@ public class TsClassElement {
}
public boolean isOptionalTypeZod(final FieldProperty field) {
if (field.apiNotNull() != null) {
return !field.apiNotNull().value();
}
// Common checking element (apply to List, Map, ...)
if (field.nullable()) {
return true;
@@ -259,28 +294,61 @@ public class TsClassElement {
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.stringSize() != null) {
if (field.stringSize().min() > 0) {
// A string size can not be lower at 0
builder.append(".min(");
builder.append(field.stringSize().min());
builder.append(")");
}
if (field.stringSize().max() != Integer.MAX_VALUE) {
builder.append(".max(");
builder.append(field.stringSize().max());
builder.append(")");
}
}
if (field.sizeMax() > 0) {
builder.append(".max(");
builder.append(field.sizeMax());
/* Must be tested before
if (field.pattern() != null) {
builder.append(".regex((");
builder.append(field.pattern().regexp());
builder.append(")");
}
}*/
/* Must be tested before
if (field.email() != null) {
builder.append(".regex((");
builder.append(field.email().regexp());
builder.append(")");
}*/
}
if (clazz == short.class || clazz == Short.class || clazz == int.class || clazz == Integer.class
|| 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());
if (field.min() != null) {
builder.append(".gte(");
builder.append(field.min().value());
builder.append(")");
}
if (field.max() != null && field.max() > 0) {
builder.append(".max(");
builder.append(field.max());
if (field.max() != null) {
builder.append(".lte(");
builder.append(field.max().value());
builder.append(")");
}
if (field.decimalMax() != null) {
if (field.decimalMax().inclusive()) {
builder.append(".lte(");
} else {
builder.append(".lt(");
}
builder.append(field.decimalMax().value());
builder.append(")");
}
if (field.decimalMin() != null) {
if (field.decimalMin().inclusive()) {
builder.append(".gte(");
} else {
builder.append(".gt(");
}
builder.append(field.decimalMin().value());
builder.append(")");
}
}
@@ -288,7 +356,7 @@ public class TsClassElement {
}
public String readOnlyZod(final FieldProperty field) {
if (field.readOnly()) {
if (!field.accessLimitation().creatable() && !field.accessLimitation().updatable()) {
return ".readonly()";
}
return "";
@@ -310,27 +378,48 @@ public class TsClassElement {
public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException {
final StringBuilder out = new StringBuilder();
out.append(getBaseHeader());
out.append(generateImports(model.getDependencyModels(), tsGroup));
out.append("\n");
// ------------------------------------------------------------------------
// -- Generate read mode
// ------------------------------------------------------------------------
if (model.getApiGenerationMode().read()) {
out.append(generateObjectRead(model, tsGroup));
}
if (model.getApiGenerationMode().update()) {
out.append(generateObjectUpdate(model, tsGroup));
}
if (model.getApiGenerationMode().create()) {
out.append(generateObjectCreate(model, tsGroup));
}
return out.toString();
}
public String generateObjectRead(final ClassObjectModel model, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
out.append(generateComment(model));
out.append("export const ");
out.append(this.zodName);
out.append(" = ");
// Check if the object is empty:
final boolean isEmpty = model.getFields().size() == 0;
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName);
out.append(".extend({");
if (!isEmpty) {
out.append(".extend({\n");
}
} else {
out.append("zod.object({");
out.append("zod.object({\n");
}
out.append("\n");
for (final FieldProperty field : model.getFields()) {
// remove all readOnly field
if (!field.accessLimitation().readable()) {
continue;
}
final ClassModel fieldModel = field.model();
if (field.comment() != null) {
out.append("\t/**\n");
@@ -356,89 +445,138 @@ public class TsClassElement {
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");
}
if (model.getExtendsClass() != null && isEmpty) {
out.append(";\n");
} else {
out.append("\n});\n");
out.append(generateZodInfer(this.tsTypeName + "Write", this.zodName + "Write"));
// Check only the input value ==> no need of the output
out.append(generateExportCheckFunctionWrite("Write"));
// Generate the Write Type associated.
/*
out.append("\nexport const ");
out.append(this.zodName);
out.append("Write = ");
out.append(this.zodName);
if (omitField.size() != 0) {
out.append(".omit({\n");
for (final String elem : omitField) {
out.append("\t");
out.append(elem);
out.append(": true,\n");
}
out.append("\n})");
}
out.append(".partial();\n");
*/
}
out.append(generateZodInfer(this.tsTypeName, this.zodName));
out.append(generateExportCheckFunctionAppended(""));
return out.toString();
}
public String generateObjectUpdate(final ClassObjectModel model, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
final String modeleType = MODEL_TYPE_UPDATE;
out.append("export const ");
out.append(this.zodName);
out.append(modeleType);
out.append(" = ");
// Check if at minimum One fiend is updatable to generate the local object
final boolean isEmpty = model.getFields().stream().filter(field -> field.accessLimitation().updatable())
.count() == 0;
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName);
out.append(modeleType);
if (!isEmpty) {
out.append(".extend({\n");
}
} else {
out.append("zod.object({\n");
}
for (final FieldProperty field : model.getFields()) {
// remove all readOnly field
if (!field.accessLimitation().updatable()) {
continue;
}
final ClassModel fieldModel = field.model();
if (field.comment() != null) {
out.append("\t/**\n");
out.append("\t * ");
out.append(field.comment());
out.append("\n\t */\n");
}
out.append("\t");
out.append(field.name());
out.append(": ");
if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) {
final TsClassElement tsFieldModel = tsGroup.find(fieldModel);
out.append(tsFieldModel.zodName);
} else if (fieldModel instanceof final ClassListModel fieldListModel) {
final String data = generateTsList(fieldListModel, tsGroup);
out.append(data);
} else if (fieldModel instanceof final ClassMapModel fieldMapModel) {
final String data = generateTsMap(fieldMapModel, tsGroup);
out.append(data);
}
out.append(maxSizeZod(field));
out.append(optionalWriteTypeZod(field));
out.append(optionalTypeZod(field));
out.append(",\n");
}
if (model.getExtendsClass() != null && isEmpty) {
out.append(";\n");
} else {
out.append("\n});\n");
}
out.append(generateZodInfer(this.tsTypeName + modeleType, this.zodName + modeleType));
// Check only the input value ==> no need of the output
out.append(generateExportCheckFunctionAppended(modeleType));
return out.toString();
}
public String generateObjectCreate(final ClassObjectModel model, final TsClassElementGroup tsGroup)
throws IOException {
final StringBuilder out = new StringBuilder();
final String modeleType = MODEL_TYPE_CREATE;
out.append("export const ");
out.append(this.zodName);
out.append(modeleType);
out.append(" = ");
final boolean isEmpty = model.getFields().stream().filter(field -> field.accessLimitation().creatable())
.count() == 0;
if (model.getExtendsClass() != null) {
final ClassModel parentClass = model.getExtendsClass();
final TsClassElement tsParentModel = tsGroup.find(parentClass);
out.append(tsParentModel.zodName);
out.append(modeleType);
if (!isEmpty) {
out.append(".extend({\n");
}
} else {
out.append("zod.object({\n");
}
for (final FieldProperty field : model.getFields()) {
// remove all readOnly field
if (!field.accessLimitation().creatable()) {
continue;
}
final ClassModel fieldModel = field.model();
if (field.comment() != null) {
out.append("\t/**\n");
out.append("\t * ");
out.append(field.comment());
out.append("\n\t */\n");
}
out.append("\t");
out.append(field.name());
out.append(": ");
if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) {
final TsClassElement tsFieldModel = tsGroup.find(fieldModel);
out.append(tsFieldModel.zodName);
} else if (fieldModel instanceof final ClassListModel fieldListModel) {
final String data = generateTsList(fieldListModel, tsGroup);
out.append(data);
} else if (fieldModel instanceof final ClassMapModel fieldMapModel) {
final String data = generateTsMap(fieldMapModel, tsGroup);
out.append(data);
}
out.append(maxSizeZod(field));
out.append(optionalWriteTypeZod(field));
out.append(optionalTypeZod(field));
out.append(",\n");
}
if (model.getExtendsClass() != null && isEmpty) {
out.append(";\n");
} else {
out.append("\n});\n");
}
out.append(generateZodInfer(this.tsTypeName + modeleType, this.zodName + modeleType));
// Check only the input value ==> no need of the output
out.append(generateExportCheckFunctionAppended(modeleType));
return out.toString();
}

View File

@@ -4,7 +4,6 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
// https://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey/45814178#45814178
@@ -246,7 +245,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
final Object rowRight = ret.getClaim("right");
if (rowRight != null) {
LOGGER.info("Detect right in Authentication Filter: {}", rowRight);
user.right = (Map<String, Map<String, Object>>) ret.getClaim("right");
user.right = RightSafeCaster.safeCastAndTransform(ret.getClaim("right"));
/*
if (rights.containsKey(this.applicationName)) {
user.right = rights.get(this.applicationName);

View File

@@ -26,7 +26,7 @@ public class MySecurityContext implements SecurityContext {
return this.contextPrincipale;
}
public Object getRightOfRoleInGroup(final String group, final String role) {
public PartRight getRightOfRoleInGroup(final String group, final String role) {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.getRight(group, role);
}
@@ -67,21 +67,14 @@ public class MySecurityContext implements SecurityContext {
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()) {
final PartRight rightPart = getRightOfRoleInGroup(group, role);
if (PartRight.READ_WRITE.equals(rightPart)) {
return true;
}
if (!needRead && needWrite && dataRight == PartRight.WRITE.getValue()) {
if (!needRead && needWrite && PartRight.WRITE.equals(rightPart)) {
return true;
}
if (needRead && !needWrite && dataRight == PartRight.READ.getValue()) {
if (needRead && !needWrite && PartRight.READ.equals(rightPart)) {
return true;
}
return false;

View File

@@ -1,6 +1,8 @@
package org.kar.archidata.filter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,7 +20,12 @@ public class OptionFilter implements ContainerRequestFilter {
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
LOGGER.warn("Receive message from: [{}] {}", requestContext.getMethod(), requestContext.getUriInfo().getPath());
LOGGER.warn("Receive message from: [{}] '{}'", requestContext.getMethod(),
requestContext.getUriInfo().getPath());
final Map<String, List<String>> queryParams = requestContext.getUriInfo().getQueryParameters();
for (final Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
LOGGER.warn("queryParam: '{}' => '{}'", entry.getKey(), entry.getValue());
}
if (requestContext.getMethod().contentEquals("OPTIONS")) {
requestContext.abortWith(Response.status(Response.Status.NO_CONTENT).build());
}

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ public interface MigrationInterface {
* @param log Stored data in the BDD for the migration progression.
* @param migration Migration post data on each step...
* @return true if migration is finished. */
boolean applyMigration(DBAccess entry, StringBuilder log, Migration model) throws Exception;
boolean applyMigration(DBAccess entry, StringBuilder log, Migration migration) throws Exception;
/** Remove a migration the system to the previous version.
* @param entry DB interface for the migration.

View File

@@ -1,12 +1,14 @@
package org.kar.archidata.model;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
@Table(name = "data")
@DataIfNotExists
@@ -14,11 +16,16 @@ import jakarta.persistence.Table;
public class Data extends OIDGenericDataSoftDelete {
@Column(length = 128, nullable = false)
@Schema(description = "Sha512 of the data")
@Size(max = 512)
@ApiAccessLimitation(creatable = false, updatable = false)
public String sha512;
@Column(length = 128, nullable = false)
@Schema(description = "Mime -type of the media")
@Size(max = 512)
@ApiAccessLimitation(creatable = false, updatable = false)
public String mimeType;
@Column(nullable = false)
@Schema(description = "Size in Byte of the data")
@ApiAccessLimitation(creatable = false, updatable = false)
public Long size;
}

View File

@@ -1,15 +1,24 @@
package org.kar.archidata.model;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.annotation.apiGenerator.ApiNotNull;
import org.kar.archidata.annotation.checker.ReadOnlyField;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@ApiGenerationMode(create = true, update = true)
public class GenericData extends GenericTiming {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, unique = true)
@Schema(description = "Unique Id of the object", required = false, readOnly = true, example = "123456")
@Schema(description = "Unique Id of the object", example = "123456")
@ReadOnlyField
@ApiNotNull
@ApiAccessLimitation(creatable = false, updatable = false)
public Long id = null;
}

View File

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

View File

@@ -5,6 +5,9 @@ import java.util.Date;
import org.kar.archidata.annotation.CreationTimestamp;
import org.kar.archidata.annotation.DataNotRead;
import org.kar.archidata.annotation.UpdateTimestamp;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.annotation.checker.ReadOnlyField;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -12,20 +15,24 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
@ApiGenerationMode(create = true, update = true)
public class GenericTiming {
@DataNotRead
@CreationTimestamp
@Column(nullable = false)
@Schema(description = "Create time of the object", required = false, example = "2000-01-23T01:23:45.678+01:00", readOnly = true)
@Column(nullable = false, insertable = false, updatable = false)
@Schema(description = "Create time of the object", example = "2000-01-23T01:23:45.678+01:00")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
@Nullable
@ReadOnlyField
@ApiAccessLimitation(creatable = false, updatable = false)
public Date createdAt = null;
@DataNotRead
@UpdateTimestamp
@Column(nullable = false)
@Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true)
@Column(nullable = false, insertable = false, updatable = false)
@Schema(description = "When update the object", example = "2000-01-23T00:23:45.678Z")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
// public Instant updatedAt = null;
@Nullable
@ReadOnlyField
@ApiAccessLimitation(creatable = false, updatable = false)
public Date updatedAt = null;
}

View File

@@ -1,17 +1,23 @@
package org.kar.archidata.model;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.annotation.apiGenerator.ApiNotNull;
import org.kar.archidata.annotation.checker.ReadOnlyField;
import dev.morphia.annotations.Id;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
@ApiGenerationMode(create = true, update = true)
public class OIDGenericData extends GenericTiming {
@Id
@jakarta.persistence.Id
@Column(nullable = false, unique = true, name = "_id")
@Schema(description = "Unique ObjectID of the object", required = false, readOnly = true, example = "65161616841351")
@NotNull
@Schema(description = "Unique ObjectID of the object", example = "65161616841351")
@ReadOnlyField
@ApiNotNull
@ApiAccessLimitation(creatable = false, updatable = false)
public ObjectId oid = null;
}

View File

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

View File

@@ -2,17 +2,24 @@ package org.kar.archidata.model;
import java.util.UUID;
import org.kar.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.kar.archidata.annotation.apiGenerator.ApiNotNull;
import org.kar.archidata.annotation.checker.ReadOnlyField;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.DefaultValue;
@ApiGenerationMode(create = true, update = true)
public class UUIDGenericData extends GenericTiming {
@Id
@DefaultValue("(UUID_TO_BIN(UUID(), TRUE))")
@Column(nullable = false, unique = true)
@Schema(description = "Unique UUID of the object", required = false, readOnly = true, example = "e6b33c1c-d24d-11ee-b616-02420a030102")
@NotNull
@Schema(description = "Unique UUID of the object", example = "e6b33c1c-d24d-11ee-b616-02420a030102")
@ReadOnlyField
@ApiNotNull
@ApiAccessLimitation(creatable = false, updatable = false)
public UUID uuid = null;
}

View File

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

View File

@@ -20,6 +20,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -36,6 +37,7 @@ import jakarta.ws.rs.DefaultValue;
@Table(name = "user")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class User extends GenericDataSoftDelete {
@NotNull
@Column(length = 128)
@@ -51,6 +53,7 @@ public class User extends GenericDataSoftDelete {
@Nullable
public Boolean blocked = false;
@Column(length = 512)
@Size(max = 512)
public String blockedReason;
@Schema(description = "List of Id of the specific covers")

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
package org.kar.archidata.tools;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
public class AnnotationCreator {
@SuppressWarnings("unchecked")
public static <A extends Annotation> A createAnnotation(final Class<A> annotationClass, final Object... values) {
return (A) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class<?>[] { annotationClass },
new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ("annotationType".equals(method.getName())) {
return annotationClass;
}
if ("toString".equals(method.getName())) {
return "@" + annotationClass.getName() + values;
}
for (int i = 0; i < values.length; i += 2) {
if (method.getName().equals(values[i])) {
return values[i + 1];
}
}
return method.getDefaultValue();
}
});
}
public static void main(final String[] args) {
final ApiGenerationMode myAnnotation = AnnotationCreator.createAnnotation(ApiGenerationMode.class, "readable",
true, "creatable", false, "updatable", false);
System.out.println("readable: " + myAnnotation.read()); // Output: example
System.out.println("creatable: " + myAnnotation.create()); // Output: 100
System.out.println("updatable: " + myAnnotation.update()); // Output: 100
final ApiGenerationMode myAnnotation2 = AnnotationCreator.createAnnotation(ApiGenerationMode.class);
System.out.println("readable: " + myAnnotation2.read()); // Output: example
System.out.println("creatable: " + myAnnotation2.create()); // Output: 100
System.out.println("updatable: " + myAnnotation2.update()); // Output: 100
}
}

View File

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

View File

@@ -12,11 +12,11 @@ public class ContextGenericTools {
public static ObjectMapper createObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
// Configure Jackson for dates and times
objectMapper.registerModule(new JavaTimeModule()); // Module for Java 8+ Date and Time API
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// configure the local serialization modules
objectMapper.registerModule(JacksonModules.getAllModules());
// Add java time module at the end to prevent use it in first but in backup
objectMapper.registerModule(new JavaTimeModule()); // Module for Java 8+ Date and Time API
return objectMapper;
}

View File

@@ -5,6 +5,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@@ -17,13 +18,17 @@ import java.util.List;
import org.apache.tika.Tika;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.AnnotationTools.FieldName;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.ReadAllColumn;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.model.Data;
@@ -391,6 +396,13 @@ public class DataTools {
}
// Fist step: retrieve all the Id of each parents:...
LOGGER.info("Find typeNode");
AddOnDataJson.addLink(ioDb, clazz, null, id, null, data.oid);
final Field idField = AnnotationTools.getIdField(clazz);
if (idField == null) {
throw new DataAccessException(
"The class have no annotation @Id ==> can not determine the default type searching");
}
final FieldName fieldName = AnnotationTools.getFieldName(idField, new QueryOptions());
AddOnDataJson.addLink(ioDb, clazz, fieldName.inTable(), id, null, data.oid);
}
}

View File

@@ -1,53 +1,170 @@
package org.kar.archidata.tools;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DateTools {
static private List<SimpleDateFormat> knownPatterns = new ArrayList<>();
{
// SYSTEM mode
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss.SSS'Z'"));
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"));
// Human mode
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss.SSS"));
// date mode
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd"));
// time mode
DateTools.knownPatterns.add(new SimpleDateFormat("HH:mm:ss"));
DateTools.knownPatterns.add(new SimpleDateFormat("HH:mm:ss.SSS"));
private final static Logger LOGGER = LoggerFactory.getLogger(DateTools.class);
// List of supported parsers for flexible date string parsing.
// Includes patterns with optional parts, slashes, and ISO standard formats.
static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("[yyyy[-MM[-dd]]]['T'][' '][HH[:mm[:ss['.'nnnnnnnnn]]]]XXXXX"),
DateTimeFormatter.ofPattern("[yyyy[/MM[/dd]]]['T'][' '][HH[:mm[:ss['.'nnnnnnnnn]]]]XXXXX"),
DateTimeFormatter.ISO_OFFSET_DATE_TIME, // e.g., 2025-04-04T15:30:00+02:00
DateTimeFormatter.ISO_ZONED_DATE_TIME, // e.g., 2025-04-04T15:30:00+02:00[Europe/Paris]
DateTimeFormatter.ISO_INSTANT // e.g., 2025-04-04T13:30:00Z
);
/**
* Attempts to parse a date string into an OffsetDateTime using a flexible list of patterns.
* Supports ISO 8601 formats, optional zone, and fallback to LocalDate or LocalTime if needed.
*
* @param dateString the date string to parse
* @return OffsetDateTime representation of the parsed input
* @throws IOException if no supported format matches the input
*/
public static OffsetDateTime parseOffsetDateTime(final String dateString) throws IOException {
return parseOffsetDateTime(dateString, false);
}
public static Date parseDate(final String inputDate, final String pattern) throws ParseException {
final SimpleDateFormat format = new SimpleDateFormat(pattern);
return format.parse(inputDate);
}
public static Date parseDate(final String inputDate) throws ParseException {
for (final SimpleDateFormat pattern : DateTools.knownPatterns) {
/**
* Attempts to parse a date string into an OffsetDateTime using a flexible list of patterns.
* Supports ISO 8601 formats, optional zone, and fallback to LocalDate or LocalTime if needed.
*
* @param dateString the date string to parse
* @param missingAsUTC Parse date when missing the time zone consider it as a UTC Date-time
* @return OffsetDateTime representation of the parsed input
* @throws IOException if no supported format matches the input
*/
public static OffsetDateTime parseOffsetDateTime(final String dateString, final boolean missingAsUTC)
throws IOException {
if (dateString == null) {
return null;
}
for (final DateTimeFormatter formatter : FORMATTERS) {
try {
return pattern.parse(inputDate);
} catch (final ParseException e) {
continue;
return OffsetDateTime.parse(dateString, formatter);
} catch (final DateTimeParseException ex) {
if (missingAsUTC) {
// If the date string is missing a zone, try appending "Z"
try {
if (dateString.endsWith("Z") || dateString.endsWith("z")) {
continue;
}
return OffsetDateTime.parse(dateString + "Z", formatter);
} catch (final DateTimeParseException ex2) {
// Still failed, try next pattern
}
}
}
}
throw new ParseException("Can not parse the date-time format: '" + inputDate + "'", 0);
throw new IOException("Unrecognized DATE format: '" + dateString + "' supported format ISO8601");
}
public static String formatDate(final Date date, final String requiredDateFormat) {
final SimpleDateFormat df = new SimpleDateFormat(requiredDateFormat);
final String outputDateFormatted = df.format(date);
return outputDateFormatted;
/**
* Parses a flexible date string and returns a java.util.Date,
* using system default time-zone for conversion.
*
* @param dateString the input string to parse
* @return The parsed Date
* @throws IOException if parsing fails.
*/
public static Date parseDate(final String dateString) throws IOException {
final OffsetDateTime dateTime = parseOffsetDateTime(dateString, true);
dateTime.atZoneSameInstant(ZoneId.systemDefault());
return Date.from(dateTime.toInstant());
}
public static String formatDate(final Date date) {
return formatDate(date, "yyyy-MM-dd'T'HH:mm.ss.SSS'Z'");
/**
* Formatter for date-time with milliseconds and original timezone offset (e.g., 2025-04-06T15:00:00.123+02:00)
*/
public static final DateTimeFormatter PATTERN_MS_TIME_WITH_ZONE = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'SSSXXX");
/**
* Serializes an OffsetDateTime to a string including milliseconds and the original timezone offset.
* Example output: 2025-04-06T15:00:00.123+02:00
*/
public static String serializeMilliWithOriginalTimeZone(final OffsetDateTime offsetDateTime) {
if (offsetDateTime == null) {
return null;
}
return offsetDateTime.format(PATTERN_MS_TIME_WITH_ZONE);
}
/**
* Converts a java.util.Date to OffsetDateTime using the system's default timezone,
* then serializes it to a string with milliseconds and original timezone offset.
*/
public static String serializeMilliWithOriginalTimeZone(final Date date) {
if (date == null) {
return null;
}
return serializeMilliWithOriginalTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
}
/**
* Formatter for date-time with milliseconds in UTC offset (e.g., 2025-04-06T13:00:00.123Z)
*/
public static final DateTimeFormatter PATTERN_MS_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'SSSX");
/**
* Serializes an OffsetDateTime to a string with milliseconds in UTC.
* The offset is explicitly changed to UTC before formatting.
*/
public static String serializeMilliWithUTCTimeZone(final OffsetDateTime offsetDateTime) {
if (offsetDateTime == null) {
return null;
}
return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC).format(PATTERN_MS_TIME);
}
/**
* Converts a java.util.Date to OffsetDateTime in the system's default timezone,
* then serializes it with milliseconds in UTC.
*/
public static String serializeMilliWithUTCTimeZone(final Date date) {
if (date == null) {
return null;
}
return serializeMilliWithUTCTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
}
/**
* Formatter for date-time with nanoseconds in UTC offset (e.g., 2025-04-06T13:00:00.123456789Z)
*/
public static final DateTimeFormatter PATTERN_NS_TIME = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'nnnnnnnnnX");
/**
* Serializes an OffsetDateTime to a string with nanosecond precision in UTC.
*/
public static String serializeNanoWithUTCTimeZone(final OffsetDateTime offsetDateTime) {
if (offsetDateTime == null) {
return null;
}
return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC).format(PATTERN_NS_TIME);
}
/**
* Converts a java.util.Date to OffsetDateTime in the system's default timezone,
* then serializes it with nanoseconds in UTC.
*/
public static String serializeNanoWithUTCTimeZone(final Date date) {
if (date == null) {
return null;
}
return serializeNanoWithUTCTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
}
}

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