Compare commits

...

332 Commits
v0.8.3 ... main

Author SHA1 Message Date
Edouard DUPIN
ec762cfb96 [RELEASE] Release v0.29.0 2025-04-22 14:14:54 +02:00
Edouard DUPIN
31d353de40 [FEAT] update personal email 2025-04-22 12:29:26 +02:00
21d7eace4c [FIX] cover upload API 2025-04-22 12:27:24 +02:00
b530cb629b [FEAT] add capability to use @ManyToManyLocal that is an implementation compatible with NoSql MAny-ToMany link
This feature manage to update the 2 side of the local stored of the data
2025-04-22 11:30:58 +02:00
0cf718d415 [FIX] throw in @ManyToMany when not a list instead of log a simple error 2025-04-22 11:29:04 +02:00
963f53c2ad [FEAT] add capability to load previous data befor call updaete od soem fields 2025-04-22 11:27:42 +02:00
7f5987338a [FEAT] update naming of the TableCoversGeneric to be generic 2025-04-22 11:24:19 +02:00
70a9be86b2 [FIX] timestamp check for Github test 2025-04-22 11:23:04 +02:00
11b49ea95d [FIX] the code generation does not manage corectly the field name 2025-04-19 18:55:16 +02:00
18329659c8 [FEAT] update path structure of the project 2025-04-19 17:35:42 +02:00
74278041d6 [VERSION] update dev tag version 2025-04-18 08:31:53 +02:00
d45e07450f [RELEASE] Release v0.28.4 2025-04-18 08:31:50 +02:00
8c46d9b441 [FIX] Error 403 instead of 401 when no role found in token. 2025-04-18 08:31:17 +02:00
106e64a90c [FIX] @ManyToMany auto link generation
Not functionnal di to the bijectivity model ==> need fix it
2025-04-18 08:14:36 +02:00
8a9567d730 [VERSION] update dev tag version 2025-04-15 20:37:23 +02:00
edfbc5da21 [RELEASE] Release v0.28.2 2025-04-15 20:37:20 +02:00
95a37fc7de [FIX] order the inputs in typescript generation 2025-04-15 20:36:10 +02:00
2ba7d47b10 [VERSION] update dev tag version 2025-04-14 13:43:46 +02:00
2a2599f35c [RELEASE] Release v0.28.0 2025-04-14 13:43:42 +02:00
3fba44a041 [FIX] build 2025-04-14 13:33:34 +02:00
dependabot[bot]
6d10f564eb [DEV-OPS] (dependabot) Bump org.apache.maven.plugins:maven-gpg-plugin
Bumps [org.apache.maven.plugins:maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 1.6 to 3.2.7.
- [Release notes](https://github.com/apache/maven-gpg-plugin/releases)
- [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-1.6...maven-gpg-plugin-3.2.7)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-gpg-plugin
  dependency-version: 3.2.7
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 12:51:32 +02:00
dependabot[bot]
d608e534c3 [DEV-OPS] (dependabot) Bump dev.morphia.morphia:morphia-core
Bumps [dev.morphia.morphia:morphia-core](https://github.com/MorphiaOrg/morphia) from 2.4.15 to 2.4.16.
- [Release notes](https://github.com/MorphiaOrg/morphia/releases)
- [Changelog](https://github.com/MorphiaOrg/morphia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MorphiaOrg/morphia/compare/v2.4.15...v2.4.16)

---
updated-dependencies:
- dependency-name: dev.morphia.morphia:morphia-core
  dependency-version: 2.4.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 12:51:27 +02:00
dependabot[bot]
5a03f338c4 [DEV-OPS] (dependabot) Bump net.revelc.code.formatter:formatter-maven-plugin
Bumps net.revelc.code.formatter:formatter-maven-plugin from 2.24.1 to 2.26.0.

---
updated-dependencies:
- dependency-name: net.revelc.code.formatter:formatter-maven-plugin
  dependency-version: 2.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 12:50:39 +02:00
dependabot[bot]
bf5a0e5ed0
[DEV-OPS] (dependabot) Bump com.nimbusds:nimbus-jose-jwt
Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.0.2 to 10.2.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.2..10.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 10:32:00 +00:00
32d7be23e9 [FEAT] update @ManyToMany Tests 2025-04-13 15:50:25 +02:00
7a7ca29081 [FIX] update of the @ManyToMany values 2025-04-13 15:49:58 +02:00
464f844eed [FEAT,API] review the manyToMany link table name to support better automatic models
- change link table name
  - nupport reverse add link, support table name with max size of 64 with hash reduce
2025-04-11 01:42:11 +02:00
401bd8318a [FEAT] add capability to select the frmat of thumbnail generate 2025-04-09 12:52:19 +02:00
05a51ad87a [FEAT] move in the correct domain org.kar -> org.atriasoft 2025-04-08 21:09:38 +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
1abbac944d [FIX] correct the bug of multiple access on the add-on list 2025-01-11 10:42:29 +01:00
cef06889ee [DOC] add comment 2025-01-11 10:42:00 +01:00
1e05e8361a [DEV] update UUID error in ObjectId to be simple 2025-01-08 19:54:08 +01:00
4adc097c6b [DEV] rename RESTErrorResponseExeption in RESTErrorResponseException 2025-01-08 19:53:27 +01:00
69e076e991 [VERSION] update dev tag version 2025-01-06 23:47:01 +01:00
5e478e3caa [RELEASE] Release v0.20.4 2025-01-06 23:46:58 +01:00
5a97f794f6 [Fix] name of the UUID data 2025-01-06 23:46:17 +01:00
ac59e17de8 [VERSION] update dev tag version 2025-01-05 22:49:51 +01:00
1d5d6fdbef [RELEASE] Release v0.20.2 2025-01-05 22:49:48 +01:00
2dd82fe247 [FIX] path of the stored data 2025-01-05 22:48:53 +01:00
4c1cee2077 [VERSION] update dev tag version 2025-01-05 21:56:38 +01:00
713f84e2da [RELEASE] Release v0.20.0 2025-01-05 21:56:35 +01:00
d3a7db4f21 [FEAT] update ObjectId for data 2025-01-05 21:41:19 +01:00
ea8e2cc452 [FIX] system.out removing to use all time LOGGER 2025-01-05 21:40:39 +01:00
ee831283d3 [FIX] dynamc modification type for DataJson 2025-01-02 23:06:27 +01:00
6b12d26a8b [FIX] remove comment 2025-01-02 23:05:47 +01:00
59e40a40d4 [FIX] simple correction of typing 2025-01-02 23:05:08 +01:00
b6bf7acd79 [FEAT] use a generic ObjectMapper factory to support the same suset of the tools 2025-01-02 23:04:10 +01:00
b581702df4 [FIX] corrent the search of primary key to support Morphia 2025-01-02 23:01:13 +01:00
645c8b1364 [FEAT] add capability to change the name of the field (not full tested but base is working) 2025-01-02 18:01:02 +01:00
54d3f52bd3 [FEAT] support ManyToMany agnostic primary keys 2025-01-01 18:21:41 +01:00
71f69fb7cf [FEAT] Simplify interface of the ManyToMany (internal) add capability to overwrite the type of an object 2025-01-01 18:06:50 +01:00
f5d25380c7 [FEAT] add basic element to support ObjectId as native primary Key (not tested) 2025-01-01 11:18:47 +01:00
61dde0f0ed [FEAT] add capability to check the JPA fields to prevent refacto erors 2024-12-31 16:14:35 +01:00
e3dd58910e [FEAT] add DataJson(checker ...) availlable in JPA 2024-12-31 15:59:14 +01:00
841514935c [FIX] missing model test class 2024-12-31 10:41:14 +01:00
5f682da13e [FIX] variable access 2024-12-31 10:40:50 +01:00
01560cd285 [FEAT] support native ISO8601 Time serialisation in the tool rest APi and the data
need to call the ContextGenericTools.addJsr310(rc); to register it
2024-12-29 18:21:31 +01:00
b6464b7962 [FEAT] update all the interface to support MONGO DB 2024-12-29 17:12:28 +01:00
83977e060c [FEAT] remove some deprecated throws 2024-12-29 17:11:20 +01:00
359f235274 [FEAT] add dependency with morphia (mongo db interface wrapper) 2024-12-29 17:11:00 +01:00
b0d565b014 [FEAT] remove static from DataAPI 2024-12-29 16:22:20 +01:00
fe84af5d96 [FEAT] add some annoatation tools 2024-12-29 16:22:03 +01:00
add346cf29 [FEAT] add get userID 2024-12-23 23:56:12 +01:00
8d63a758f7 [VERSION] update dev tag version 2024-12-12 09:16:21 +01:00
b9eb17e5c6 [RELEASE] Release v0.19.0 2024-12-12 09:16:17 +01:00
6d05b3444c [FEAT] update role management 2024-12-12 08:46:54 +01:00
dependabot[bot]
7b5e034ac2 [DEV-OPS] (dependabot) Bump actions/setup-java from 3 to 4
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

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

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

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

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

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

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

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

View File

@ -1,31 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="test/src">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry exported="true" kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="test/resources">
<attributes>
<attribute name="test" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
@ -35,11 +15,31 @@
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<classpathentry excluding="**" kind="src" output="target/classes" path="src/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/testResources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes" path="src/main">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

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

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

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

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

@ -0,0 +1,35 @@
---
name: "Check PR title"
on:
pull_request:
types:
- opened
- edited
- synchronize
- ready_for_review
- reopened
- labeled
- unlabeled
jobs:
check-title:
runs-on: ubuntu-latest
steps:
- name: "Check title"
uses: Slashgear/action-check-pr-title@v4.3.0
with:
regexp: "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-/_]+\\))? [A-Za-z0-9: ,.'\\-!/_]+$"
helpMessage: |
Title of the PR MUST respect format: "[{TYPE}] clear description without typos in english" with {TYPE}:
* [API] Change API that permit to access on the application (un-compatibility only). This one can specifically added with [API,{TYPE}]
* [DEV-OPS] Update automatic build system, method to deliver application/packages, ...
* [DOC] Update or add some documentation.
* [FEAT] Develop a new feature
* [FIX] When fixing issue
* [FIX-CI] When the CI fail to build and we apply a correction to set it work again.
* [STYLE] Update of the style tools/checker, or add/remove rules.
Examples:
[FEAT] My beautiful feature
[API,FIX] Change API to fix typo
[FIX] (module) Correct part of ...

View File

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

View File

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

View File

@ -28,4 +28,4 @@ def getEnvName(branchName) {
return "dev";
}
return "other";
}
}

View File

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

View File

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

202
doc/SQL_wrapper.md Normal file
View File

@ -0,0 +1,202 @@
Wrapping over the DB
====================
In the current version we consider 2 DB access: MySQL and SQLite, We commonly use the SQLite in test mode to permit to test is a real case.
Generic Access:
---------------
Some generic function are available to permit you to simply request the DB
### Create a new Value:
```java
MyDataModel dataToInsert = ...;
MyDataModel dataCreated = DataAccess.insert(dataToInsert);
```
### Get a full table:
```java
List<MyDataModel> data = DataAccess.gets(MyDataModel.class);
```
### Get a single element in the DB:
```java
UUID id = ...;
MyDataModel data = DataAccess.get(MyDataModel.class, id);
```
> **_Note:_** The Id type fully managed are UUID and Long
### Removing the Data:
```java
UUID id = ...;
DataAccess.delete(MyDataModel.class, id);
```
> **_Note:_** Removing the data automatically detect if it is a software remove or definitive remove
### Updating the Data:
The update of the data can be managed by 2 way:
- Direct update of the Object with direct injection (Good for `PUT`)
- Update with input json (Good for `PATCH`)
The second way is preferable for some reasons
- When jakarta transform the data in you object, we can not detect the element set at null or not set (We consider user of `Optional` il all data class will create a too bug amount of unneeded code in all dev parts)
- Throw in the jakarta parsing are not well catch when we will generate generic error
- The Check will permit to explain what is wrong better than a generic Json parser.
Updating with JSON:
```java
UUID id = ...;
String jsonRequest = "{...}";
DataAccess.updateWithJson(MyDataModel.class, id, jsonRequest);
```
Updating with direct data:
```java
UUID id = ...;
MyDataModel dataToUpdate = ...;
// This update all fields:
DataAccess.update(dataToUpdate, id);
// Select the field to update:
DataAccess.update(dataToUpdate, id, List.of("field1","field2"));
```
Generic option of the request:
------------------------------
Many API have a generic multiple option available like:
```java
public static <T> List<T> getsWhere(final Class<T> clazz, final QueryOption... option) throws Exception
```
You just need to add your options in the list of `option`.
Filter the list of field read:
```java
public FilterValue(final String... filterValue)
public FilterValue(final List<String> filterValues)
// example:
new newFilterValue("field1", "field2");
```
Add a condition [more detail](#condition-models)
```java
public Condition(final QueryItem items)
```
Order the request:
```java
public OrderBy(final OrderItem... childs);
// example:
new OrderBy(new OrderItem("name", Order.DESC));
```
Limit the :
```java
public Limit(final long limit)
// example:
new Limit(50);
```
Read all column like update date and create date or delete field
```java
public ReadAllColumn()
```
Condition models:
-----------------
Creating a condition independent of the DB model use need to have a little abstraction of the query model:
For this we propose some condition that update with the internal "auto" condition that is added (like the soft delete...)
### default generic comparator
This is the base of the comparison tool. It compare a column with a value
```java
public QueryCondition(final String key, final String comparator, final Object value);
```
Simple DB comparison element. Note the injected object is injected in the statement and not in the query directly.
Example:
```java
String nameToCheck = "plop";
new QueryCondition("fieldName", "=", nameToCheck);
// OR:
UUID uuid = ...;
new QueryCondition("uuid", "=", uuid);
```
### List comparator
It permit to check a column is (NOT) in a list of value
```java
public QueryInList(final String key, final List<T> value)
public QueryInList(final String key, final T... value)
```
and his opposite:
```java
public QueryNotInList(final String key, final List<T> value)
public QueryNotInList(final String key, final T... value)
```
example
```java
new QueryInList("uuid", List.of(uuid1, uuid2));
```
### NULL and NOT NULL checker
This permit to check an element is `NULL` or `not NULL`
```java
public QueryNull(final String key);
public QueryNotNull(final String key);
```
### The group condition:
The generic `OR` group:
```java
public QueryOr(final List<QueryItem> child);
public QueryOr(final QueryItem... child);
```
Or the generic `AND group:
```java
public QueryAnd(final List<QueryItem> child);
public QueryAnd(final QueryItem... child);
```
### Full example:
```java
List<MyDataModel> result = DataAccess.getsWhere(MyDataModel.class,
new Limit(50),
new OrderBy(new OrderItem("name", Order.DESC)),
new Condition(
new QueryAnd(
QueryNull("Field3")
new QueryOr(
new QueryInList("Field4", 5, 55, 65, 62, 27, 84),
new QueryCondition("cityID", ">", 78000);
)
)
)
);
```

View File

@ -0,0 +1,3 @@
Basic tutorial
==============

16
doc/data_model.md Normal file
View File

@ -0,0 +1,16 @@
Data Model
==========
The data model have 2 parts: the model of the DB and the model of the API. It is important to consider the 2 part due to the fact annotation will be manage by by a part, ot the other. Here we will study the Checking of the Data.
DB data model
=============
API data model
==============
Checking of the Data
====================

16
doc/index.md Normal file
View File

@ -0,0 +1,16 @@
Archi-data documentation
========================
Archidata is base on generic Jakarta, grizzly and SQL or SQLite service.
It permit to simplify the interface of the server to concentrate on the API design.
Archi-data is a group of tools that manage some parts:
- [Wrapping over the DB](SQL_wrapper.md)
- [Data Model](data_model.md)
- [TypeScript Api generation](typescript_api_generation.md)
- [Migration tool](migration.md)
Some initialization tutorials:
- [Basic tutorial](TUTORIAL_01_first_install.md)

7
doc/migration.md Normal file
View File

@ -0,0 +1,7 @@
Migration tool
==============
Migration is a big part of the Server environment, it permit to migrate an old DB to a new one, and some tool permit the opposite.
Archi-data does not permit to do a reverse migration. I implement some code for it, but is was never tested. On my professional activity, we migrate only forward (Request if if really needed).

View File

@ -0,0 +1,5 @@
TypeScript Api generation
=========================

262
pom.xml
View File

@ -1,40 +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.8.3</version>
<properties>
<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.29.0</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>edouard.dupin@proton.me</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>
@ -45,14 +44,38 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
<version>2.1.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
<version>2.1.0-alpha1</version>
<scope>test</scope>
</dependency>
<!-- Decode webP images -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Decode JPEG image -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Encode file in webp -->
<dependency>
<groupId>com.github.gotson</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.2.2</version>
</dependency>
<!-- Detect type of a file with mime type -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
@ -71,35 +94,20 @@
<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>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.4</version>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.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>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
<version>4.0.0</version>
</dependency>
<!-- Serialize and un-serialize request in JSON-->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
@ -107,52 +115,79 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.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.16.1</version>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.16.1</version>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0-M1</version>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Interface for My-sql & sqlite DB -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
<version>9.2.0</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.40.0.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.37.1</version>
<version>10.2</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0-M1</version>
<version>3.2.0</version>
</dependency>
<!-- Swagger dependencies -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-jakarta</artifactId>
<version>2.1.10</version>
<version>2.2.29</version>
</dependency>
<!-- spotbug tooling -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.9.3</version>
<scope>compile</scope>
</dependency>
<!-- Morphia -->
<dependency>
<groupId>dev.morphia.morphia</groupId>
<artifactId>morphia-core</artifactId>
<version>2.4.16</version>
</dependency>
<!-- MongoDB Java Driver -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<!--<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>9.0.0.CR1</version>
</dependency>
<!--
************************************************************
@ -162,61 +197,59 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</version>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.26.0</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<sourceDirectory>src/main</sourceDirectory>
<resources>
<resource>
<directory>${basedir}/src/resources</directory>
</resource>
</resources>
<testSourceDirectory>test/src</testSourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
<testResources>
<testResource>
<directory>${basedir}/test/resources</directory>
<directory>${basedir}/src/testResources</directory>
</testResource>
</testResources>
<plugins>
<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>
@ -239,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>
@ -258,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>
@ -280,7 +317,7 @@
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
<version>2.26.0</version>
<configuration>
<encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding>
@ -304,19 +341,46 @@
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- Generate Java-docs As Part Of Project Reports -->
<reporting>
<plugins>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.5.0</version>
<configuration>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
<!--<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.12.0</version>
</plugin>
</plugins>
-->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</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>

311
pom.xml.versionsBackup Normal file
View File

@ -0,0 +1,311 @@
<?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>
<artifactId>archidata</artifactId>
<version>0.6.1</version>
<properties>
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.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>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0-M1</version>
<scope>provided</scope>
</dependency>
<!-- Interface for My-sql & sqlite DB -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.40.0.0</version>
</dependency>
<!-- Interface for JWT token -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.37.1</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0-M1</version>
</dependency>
<!-- Swagger dependencies -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-jakarta</artifactId>
<version>2.1.10</version>
</dependency>
<!--
************************************************************
** TEST dependency **
************************************************************
-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test/src</testSourceDirectory>
<testResources>
<testResource>
<directory>${basedir}/test/resources</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<!--<encoding>${project.build.sourceEncoding}</encoding>-->
</configuration>
</plugin>
<!-- Create the source bundle -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- For dependabot plugin -->
<plugin>
<groupId>org.apache.servicemix.tooling</groupId>
<artifactId>depends-maven-plugin</artifactId>
<version>1.5.0</version>
<executions>
<execution>
<id>generate-depends-file</id>
<goals>
<goal>generate-depends-file</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- junit results -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>fully.qualified.MainClass</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- Java-doc generation for stand-alone site -->
<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>
</plugin>
<!-- Check the style of the code -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<failsOnError>true</failsOnError>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
<configuration>
<encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding>
<configFile>Formatter.xml</configFile>
<directories>
<directory>src/</directory>
<directory>test/src</directory>
</directories>
<includes>
<include>**/*.java</include>
</includes>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</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>
<configuration>
<show>public</show>
</configuration>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -0,0 +1,126 @@
package sample.archidata.basic;
import java.net.URI;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.atriasoft.archidata.GlobalConfiguration;
import org.atriasoft.archidata.UpdateJwtPublicKey;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.catcher.ExceptionCatcher;
import org.atriasoft.archidata.catcher.FailExceptionCatcher;
import org.atriasoft.archidata.catcher.InputExceptionCatcher;
import org.atriasoft.archidata.catcher.SystemExceptionCatcher;
import org.atriasoft.archidata.db.DBConfig;
import org.atriasoft.archidata.filter.CORSFilter;
import org.atriasoft.archidata.filter.OptionFilter;
import org.atriasoft.archidata.migration.MigrationEngine;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import sample.archidata.basic.api.Front;
import sample.archidata.basic.api.HealthCheck;
import sample.archidata.basic.api.MediaResource;
import sample.archidata.basic.api.SeasonResource;
import sample.archidata.basic.api.SeriesResource;
import sample.archidata.basic.api.TypeResource;
import sample.archidata.basic.api.UserMediaAdvancementResource;
import sample.archidata.basic.api.UserResource;
import sample.archidata.basic.filter.KarideoAuthenticationFilter;
import sample.archidata.basic.migration.Initialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.UriBuilder;
public class WebLauncher {
final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class);
public static DBConfig dbConfig;
protected UpdateJwtPublicKey keyUpdater = null;
protected HttpServer server = null;
public WebLauncher() {
ConfigBaseVariable.bdDatabase = "sample_archidata_basic";
}
private static URI getBaseURI() {
return UriBuilder.fromUri(ConfigBaseVariable.getlocalAddress()).build();
}
public void migrateDB() throws Exception {
WebLauncher.LOGGER.info("Create migration engine");
final MigrationEngine migrationEngine = new MigrationEngine();
WebLauncher.LOGGER.info("Add initialization");
migrationEngine.setInit(new Initialization());
//WebLauncher.LOGGER.info("Add migration since last version");
//migrationEngine.add(new Migration20230810());
WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(GlobalConfiguration.dbConfig);
WebLauncher.LOGGER.info("Migrate the DB [STOP]");
}
public static void main(final String[] args) throws Exception {
WebLauncher.LOGGER.info("[START] application wake UP");
final WebLauncher launcher = new WebLauncher();
launcher.migrateDB();
launcher.process();
WebLauncher.LOGGER.info("end-configure the server & wait finish process:");
Thread.currentThread().join();
WebLauncher.LOGGER.info("STOP the REST server");
}
public void process() throws InterruptedException {
// ===================================================================
// Configure resources
// ===================================================================
final ResourceConfig rc = new ResourceConfig();
// Permit to accept OPTION request
rc.register(OptionFilter.class);
// remove cors ==> not obviously needed ...
rc.register(CORSFilter.class);
// register exception catcher (this permit to format return error with a normalized JSON)
rc.register(InputExceptionCatcher.class);
rc.register(SystemExceptionCatcher.class);
rc.register(FailExceptionCatcher.class);
rc.register(ExceptionCatcher.class);
// add default resource:
rc.register(MyModelResource.class);
// add jackson to be discover when we are in stand-alone server
rc.register(JacksonFeature.class);
this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc);
final HttpServer serverLink = this.server;
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
WebLauncher.LOGGER.info("Stopping server..");
serverLink.shutdownNow();
}
}, "shutdownHook"));
// ===================================================================
// run JERSEY
// ===================================================================
try {
this.server.start();
LOGGER.info("Jersey app started at {}", getBaseURI());
} catch (final Exception e) {
LOGGER.error("There was an error while starting Grizzly HTTP server.");
e.printStackTrace();
}
}
// This is used for TEST (See it later)
public void stop() {
if (this.server != null) {
this.server.shutdownNow();
this.server = null;
}
}
}

View File

@ -0,0 +1,45 @@
package sample.archidata.basic;
import java.util.List;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DataFactoryTsApi;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebLauncherLocal extends WebLauncher {
private final static Logger LOGGER = LoggerFactory.getLogger(WebLauncherLocal.class);
private WebLauncherLocal() {}
public static void main(final String[] args) throws Exception {
final WebLauncherLocal launcher = new WebLauncherLocal();
launcher.process();
LOGGER.info("end-configure the server & wait finish process:");
Thread.currentThread().join();
LOGGER.info("STOP the REST server:");
}
@Override
public void process() throws InterruptedException {
if (true) {
// for local test:
ConfigBaseVariable.apiAddress = "http://0.0.0.0:9000/sample/api/";
ConfigBaseVariable.dbPort = "3906";
}
try {
super.migrateDB();
} catch (final Exception e) {
e.printStackTrace();
while (true) {
LOGGER.error("============================================================================");
LOGGER.error("== Migration fail ==> waiting intervention of administrator...");
LOGGER.error("============================================================================");
Thread.sleep(60 * 60 * 1000);
}
}
super.process();
}
}

View File

@ -0,0 +1,79 @@
package sample.archidata.basic.api;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.atriasoft.archidata.annotation.AsyncType;
import org.atriasoft.archidata.annotation.TypeScriptProgress;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.addOn.AddOnDataJson;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.exception.InputException;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.tools.DataTools;
import sample.archidata.basic.model.MyModel;
import sample.archidata.basic.model.Season;
import sample.archidata.basic.model.Series;
import sample.archidata.basic.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MyModelType;
@Path("/media")
@Produces(MyModelType.APPLICATION_JSON)
public class MyModelResource {
@GET
@PermitAll
public List<MyModel> gets() throws Exception {
return DataAccess.gets(MyModel.class);
}
@POST
@PermitAll
@Consumes(MyModelType.APPLICATION_JSON)
public MyModel create(final MyModel data) throws Exception {
return DataAccess.insert(data);
}
@GET
@Path("{id}")
@PermitAll
public MyModel get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(MyModel.class, id);
}
@PATCH
@Path("{id}")
@PermitAll
@Consumes(MyModelType.APPLICATION_JSON)
public MyModel patch(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(MyModel.class, id, jsonRequest);
return DataAccess.get(MyModel.class, id);
}
@DELETE
@Path("{id}")
@PermitAll
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(MyModel.class, id);
}
}

View File

@ -0,0 +1,28 @@
package sample.archidata.basic.migration;
import java.util.List;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import sample.archidata.basic.model.MyModel;
public class Initialization extends MigrationSqlStep {
public static final List<Class<?>> CLASSES_BASE = List.of(MyModel.class);
@Override
public String getName() {
return "Initialization";
}
@Override
public void generateStep() throws Exception {
for(final Class<?> clazz : CLASSES_BASE) {
addClass(clazz);
}
addAction("""
ALTER TABLE `MyModel` AUTO_INCREMENT = 1000;
""",
// Only MySql support this request (fail in SQLite)
"mysql");
}
}

View File

@ -0,0 +1,36 @@
package sample.archidata.basic.model;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.DataJson;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
// needed for Swagger interface
@Entity
// Do not generate Null in Json serialization ==> prefer undefined
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MyModel extends GenericDataSoftDelete {
// Can not be NULL and max length is 300 (note default is 255)
@Column(nullable = false, length = 300)
public String name;
// Can be null and no max length
@Column(length = 0)
public String description;
@Override
public String toString() {
return "MyModel [name=" + this.name + ", description=" + this.description + "]";
}
}

View File

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

View File

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

View File

@ -1,15 +1,18 @@
package org.kar.archidata;
package org.atriasoft.archidata;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.JWTWrapper;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.JWTWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UpdateJwtPublicKey extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateJwtPublicKey.class);
boolean kill = false;
@Override
public void run() {
if (ConfigBaseVariable.getSSOAddress() == null) {
System.out.println("SSO INTERFACE is not provided ==> work alone.");
LOGGER.warn("SSO INTERFACE is not provided ==> work alone.");
// No SO provided, kill the thread.
return;
}
@ -19,7 +22,7 @@ public class UpdateJwtPublicKey extends Thread {
JWTWrapper.initLocalTokenRemote(ConfigBaseVariable.getSSOAddress(), "archidata");
} catch (final Exception e1) {
e1.printStackTrace();
System.out.println("Can not retreive the basic tocken");
LOGGER.error("Can not retreive the basic tocken");
return;
}
try {

View File

@ -1,22 +1,37 @@
package org.kar.archidata.annotation;
package org.atriasoft.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.dataAccess.QueryOptions;
import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.atriasoft.archidata.annotation.checker.Checker;
import org.atriasoft.archidata.annotation.checker.CollectionItemNotNull;
import org.atriasoft.archidata.annotation.checker.CollectionItemUnique;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.dataAccess.QueryOptions;
import org.atriasoft.archidata.dataAccess.options.OptionRenameColumn;
import org.atriasoft.archidata.dataAccess.options.OverrideTableName;
import org.atriasoft.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
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;
import jakarta.validation.constraints.NotNull;
@ -27,7 +42,62 @@ import jakarta.ws.rs.DefaultValue;
public class AnnotationTools {
static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class);
public static String getTableName(final Class<?> clazz, final QueryOptions options) throws Exception {
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) {
final List<OverrideTableName> data = options.get(OverrideTableName.class);
if (data.size() == 1) {
@ -37,15 +107,13 @@ public class AnnotationTools {
return AnnotationTools.getTableName(clazz);
}
public static String getTableName(final Class<?> element) throws Exception {
// For SQL declaration table Name
public static String getTableName(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class);
if (annotation.length == 0) {
// when no annotation is detected, then the table name is the class name
return element.getSimpleName();
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Table on " + element.getClass().getCanonicalName());
}
final String tmp = ((Table) annotation[0]).name();
if (tmp == null) {
return element.getSimpleName();
@ -53,158 +121,130 @@ public class AnnotationTools {
return tmp;
}
public static boolean getSchemaReadOnly(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return false;
public static String getCollectionName(final Class<?> clazz, final QueryOptions options) {
if (options != null) {
// TODO: maybe change OverrideTableName with OverrideCollectionName
final List<OverrideTableName> data = options.get(OverrideTableName.class);
if (data.size() == 1) {
return data.get(0).getName();
}
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
}
return ((Schema) annotation[0]).readOnly();
return AnnotationTools.getCollectionName(clazz);
}
public static String getSchemaExample(final Class<?> element) throws Exception {
// For No-SQL Table/Collection Name
public static String getCollectionName(final Class<?> clazz) {
final Annotation[] annotation = clazz.getDeclaredAnnotationsByType(Entity.class);
if (annotation.length == 0) {
// when no annotation is detected, then the table name is the class name
return clazz.getSimpleName();
}
final String tmp = ((Entity) annotation[0]).value();
if (tmp == null || tmp.length() == 0 || Mapper.IGNORED_FIELDNAME.equals(tmp)) {
return clazz.getSimpleName();
}
return tmp;
}
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) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
}
return ((Schema) annotation[0]).example();
}
public static String getSchemaDescription(final Class<?> element) throws Exception {
public static String getSchemaDescription(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
}
return ((Schema) annotation[0]).description();
}
public static String getSchemaDescription(final Field element) throws Exception {
public static String getSchemaDescription(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Schema on " + element.getClass().getCanonicalName());
}
return ((Schema) annotation[0]).description();
}
public static String getComment(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataComment.class);
if (annotation.length == 0) {
return getSchemaDescription(element);
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @DataComment on " + element.getClass().getCanonicalName());
}
return ((DataComment) annotation[0]).value();
}
public static String getDefault(final Field element) throws Exception {
public static String getDefault(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DefaultValue.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @DataDefault on " + element.getClass().getCanonicalName());
}
return ((DefaultValue) annotation[0]).value();
}
public static ManyToOne getManyToOne(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(ManyToOne.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName());
}
return (ManyToOne) annotation[0];
public static ManyToOne getManyToOne(final Field element) {
return get(element, ManyToOne.class);
}
public static DataJson getDataJson(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(DataJson.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @ManyToOne on " + element.getClass().getCanonicalName());
}
return (DataJson) annotation[0];
public static ManyToMany getManyToMany(final Field element) {
return get(element, ManyToMany.class);
}
public static Long getConstraintsMax(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Max.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
}
return ((Max) annotation[0]).value();
public static OneToMany getOneToMany(final Field element) {
return get(element, OneToMany.class);
}
public static Long getConstraintsMin(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Min.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
}
return ((Min) annotation[0]).value();
public static DataJson getDataJson(final Field element) {
return get(element, DataJson.class);
}
public static int getLimitSize(final Field element) throws Exception {
public static Checker[] getConstraintsCheckers(final Field element) {
return gets(element, Checker.class);
}
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) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return 255;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
final int length = ((Column) annotation[0]).length();
return length <= 0 ? 0 : length;
}
public static Size getConstraintsSize(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Size.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception("Must not have more than 1 element @Size on " + element.getClass().getCanonicalName());
}
return (Size) annotation[0];
public static Size getConstraintsSize(final Field element) {
return get(element, Size.class);
}
public static String getConstraintsPattern(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Pattern.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Pattern on " + element.getClass().getCanonicalName());
}
return ((Pattern) annotation[0]).regexp();
public static Pattern getConstraintsPattern(final Field element) {
return get(element, Pattern.class);
}
public static Email getConstraintsEmail(final Field element) {
return get(element, Email.class);
}
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {
@ -230,15 +270,11 @@ public class AnnotationTools {
return false;
}
public static String getFieldName(final Field element) throws Exception {
public static String getFieldNameRaw(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return element.getName();
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
final String name = ((Column) annotation[0]).name();
if (name.isBlank()) {
return element.getName();
@ -246,31 +282,51 @@ public class AnnotationTools {
return name;
}
public static boolean getColumnNotNull(final Field element) throws Exception {
public record FieldName(
String inStruct,
String inTable) {};
public static FieldName getFieldName(final Field element, final QueryOptions options) {
final String inStructName = getFieldNameRaw(element);
String inTableName = inStructName;
if (options != null) {
final List<OptionRenameColumn> renamesColumn = options.get(OptionRenameColumn.class);
for (final OptionRenameColumn rename : renamesColumn) {
if (rename.columnName.equals(inStructName)) {
inTableName = rename.colomnNewName;
LOGGER.trace("Detect overwrite of column name '{}' => '{}'", inStructName, inTableName);
break;
}
}
}
return new FieldName(inStructName, inTableName);
}
public static boolean getColumnNotNull(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return false;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return !((Column) annotation[0]).nullable();
}
public static boolean getConstraintsNotNull(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(NotNull.class);
public static boolean getNullable(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Nullable.class);
if (annotation.length == 0) {
return false;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @NotNull on " + element.getClass().getCanonicalName());
}
return true;
}
public static Field getPrimaryKeyField(final Class<?> clazz) throws Exception {
public static boolean getConstraintsNotNull(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(NotNull.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static Field getPrimaryKeyField(final Class<?> clazz) {
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
@ -283,59 +339,59 @@ public class AnnotationTools {
return null;
}
public static boolean isPrimaryKey(final Field element) throws Exception {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Id.class);
if (annotation.length == 0) {
return false;
public static boolean isPrimaryKey(final Field element) {
final Annotation[] annotationSQL = element.getDeclaredAnnotationsByType(Id.class);
if (annotationSQL.length > 0) {
return true;
}
return true;
final Annotation[] annotationMongo = element.getDeclaredAnnotationsByType(dev.morphia.annotations.Id.class);
if (annotationMongo.length > 0) {
return true;
}
return false;
}
public static boolean isUnique(final Field element) throws Exception {
public static boolean isUnique(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
if (annotation.length == 0) {
return false;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return ((Column) annotation[0]).unique();
}
public static GenerationType getStrategy(final Field element) throws Exception {
public static GenerationType getStrategy(final Field element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(GeneratedValue.class);
if (annotation.length == 0) {
return null;
}
if (annotation.length > 1) {
throw new Exception(
"Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
}
return ((GeneratedValue) annotation[0]).strategy();
}
public static boolean isDeletedField(final Field element) throws Exception {
public static boolean isDeletedField(final Field element) {
return element.getDeclaredAnnotationsByType(DataDeleted.class).length != 0;
}
public static boolean isCreatedAtField(final Field element) throws Exception {
public static boolean isCreatedAtField(final Field element) {
return element.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
}
public static boolean isUpdateAtField(final Field element) throws Exception {
public static boolean isUpdateAtField(final Field element) {
return element.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
}
public static boolean isdefaultNotRead(final Field element) throws Exception {
public static boolean isDefaultNotRead(final Field element) {
return element.getDeclaredAnnotationsByType(DataNotRead.class).length != 0;
}
public static boolean isIdField(final Field element) throws Exception {
return element.getDeclaredAnnotationsByType(Id.class).length != 0;
public static boolean isIdField(final Field element) {
if (element.getDeclaredAnnotationsByType(Id.class).length != 0) {
return true;
}
return element.getDeclaredAnnotationsByType(dev.morphia.annotations.Id.class).length != 0;
}
public static String getDeletedFieldName(final Class<?> clazz) throws Exception {
// Note: delete field can not be renamed with OptionRenameColumn
public static String getDeletedFieldName(final Class<?> clazz) {
try {
for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
@ -343,7 +399,7 @@ public class AnnotationTools {
continue;
}
if (AnnotationTools.isDeletedField(elem)) {
return AnnotationTools.getFieldName(elem);
return AnnotationTools.getFieldNameRaw(elem);
}
}
} catch (final Exception ex) {
@ -352,7 +408,8 @@ public class AnnotationTools {
return null;
}
public static String getUpdatedFieldName(final Class<?> clazz) throws Exception {
// Note: update field can not be renamed with OptionRenameColumn
public static String getUpdatedFieldName(final Class<?> clazz) {
try {
for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
@ -360,7 +417,7 @@ public class AnnotationTools {
continue;
}
if (AnnotationTools.isUpdateAtField(elem)) {
return AnnotationTools.getFieldName(elem);
return AnnotationTools.getFieldNameRaw(elem);
}
}
} catch (final Exception ex) {
@ -386,15 +443,28 @@ public class AnnotationTools {
return null;
}
public static List<String> getFieldsNames(final Class<?> clazz) throws Exception {
public static boolean hasFieldsName(final Class<?> clazz, final String name) {
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;
}
if (field.getName().equals(name)) {
return true;
}
}
return false;
}
public static List<String> getFieldsNames(final Class<?> clazz) {
return getFieldsNamesFilter(clazz, false);
}
public static List<String> getAllFieldsNames(final Class<?> clazz) throws Exception {
public static List<String> getAllFieldsNames(final Class<?> clazz) {
return getFieldsNamesFilter(clazz, true);
}
private static List<String> getFieldsNamesFilter(final Class<?> clazz, final boolean full) throws Exception {
private static List<String> getFieldsNamesFilter(final Class<?> clazz, final boolean full) {
final List<String> out = new ArrayList<>();
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
@ -404,17 +474,17 @@ public class AnnotationTools {
if (!full && AnnotationTools.isGenericField(field)) {
continue;
}
out.add(AnnotationTools.getFieldName(field));
out.add(AnnotationTools.getFieldNameRaw(field));
}
return out;
}
public static boolean isGenericField(final Field elem) throws Exception {
public static boolean isGenericField(final Field elem) {
return AnnotationTools.isPrimaryKey(elem) || AnnotationTools.isCreatedAtField(elem)
|| AnnotationTools.isUpdateAtField(elem) || AnnotationTools.isDeletedField(elem);
}
public static Field getFieldOfId(final Class<?> clazz) throws Exception {
public static Field getFieldOfId(final Class<?> clazz) {
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
@ -427,13 +497,13 @@ public class AnnotationTools {
return null;
}
public static Field getFieldNamed(final Class<?> clazz, final String name) throws Exception {
public static Field getFieldNamed(final Class<?> clazz, final String name) {
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;
}
if (AnnotationTools.getFieldName(field).equals(name)) {
if (AnnotationTools.getFieldNameRaw(field).equals(name)) {
return field;
}
}

View File

@ -0,0 +1,45 @@
package org.atriasoft.archidata.annotation;
import java.lang.annotation.ElementType;
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

@ -0,0 +1,46 @@
package org.atriasoft.archidata.annotation;
import java.lang.annotation.ElementType;
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

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

View File

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

View File

@ -0,0 +1,43 @@
package org.atriasoft.archidata.annotation;
import java.lang.annotation.ElementType;
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

@ -0,0 +1,27 @@
package org.atriasoft.archidata.annotation;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* In NoSql entity the relation is stored in the 2 part of the entity,
* then it is needed to define the field that store the relation data value in the remote elements.
*/
@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface ManyToManyLocal {
/**
* The entity class that is the target of the
* association.
*/
Class<?> targetEntity();
/**
* The field that owns the revert value. empty if the relationship is unidirectional.
*/
String remoteField() default "";
}

View File

@ -0,0 +1,44 @@
package org.atriasoft.archidata.annotation;
import java.lang.annotation.ElementType;
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

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

View File

@ -0,0 +1,26 @@
package org.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.archidata.annotation.checker;
import org.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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.atriasoft.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

@ -0,0 +1,19 @@
package org.atriasoft.archidata.annotation.method;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.ws.rs.HttpMethod;
/**
* Indicates that the annotated method responds to HTTP ARCHIVE requests.
*
* @author Edouard DUPIN
* @see HttpMethod
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("ARCHIVE")
public @interface ARCHIVE {}

View File

@ -0,0 +1,19 @@
package org.atriasoft.archidata.annotation.method;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.ws.rs.HttpMethod;
/**
* Indicates that the annotated method responds to HTTP RESTORE requests.
*
* @author Edouard DUPIN
* @see HttpMethod
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("RESTORE")
public @interface RESTORE {}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.annotation.security;
package org.atriasoft.archidata.annotation.security;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

View File

@ -1,10 +1,11 @@
package org.kar.archidata.api;
package org.atriasoft.archidata.api;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -20,15 +21,18 @@ import java.util.UUID;
import javax.imageio.ImageIO;
import org.atriasoft.archidata.annotation.apiGenerator.ApiInputOptional;
import org.atriasoft.archidata.annotation.security.PermitTokenInURI;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.filter.GenericContext;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.model.Data;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -84,18 +88,7 @@ public class DataResource {
return filePath;
}
public static String getFileDataOld(final long tmpFolderId) {
final String filePath = ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator
+ "data";
try {
createFolder(ConfigBaseVariable.getMediaDataFolder() + File.separator + tmpFolderId + File.separator);
} catch (final IOException e) {
e.printStackTrace();
}
return filePath;
}
public static String getFileData(final UUID uuid) {
public static String getFileDataOld(final UUID uuid) {
final String stringUUID = uuid.toString();
final String part1 = stringUUID.substring(0, 2);
final String part2 = stringUUID.substring(2, 4);
@ -112,11 +105,37 @@ public class DataResource {
return filePath;
}
public static String getFileMetaData(final UUID uuid) {
return getFileData(uuid) + ".json";
public static String getFileData(final ObjectId oid) {
final String stringOid = oid.toHexString();
String dir1 = stringOid.substring(0, 2);
String dir2 = stringOid.substring(2, 4);
String dir3 = stringOid.substring(4, 6);
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hashBytes = digest.digest(oid.toByteArray());
dir1 = String.format("%02x", hashBytes[0]);
dir2 = String.format("%02x", hashBytes[1]);
dir3 = String.format("%02x", hashBytes[2]);
} catch (final NoSuchAlgorithmException ex) {
LOGGER.error("Fail to generate the hash of the objectId ==> ise direct value ... {}", ex.getMessage());
}
final String finalPath = dir1 + File.separator + dir2 + File.separator + dir3;
String filePath = ConfigBaseVariable.getMediaDataFolder() + "_oid" + File.separator + finalPath
+ File.separator;
try {
createFolder(filePath);
} catch (final IOException e) {
e.printStackTrace();
}
filePath += stringOid;
return filePath;
}
public static Data getWithSha512(final String sha512) {
public static String getFileMetaData(final ObjectId oid) {
return getFileData(oid) + ".json";
}
public Data getWithSha512(final String sha512) {
LOGGER.info("find sha512 = {}", sha512);
try {
return DataAccess.getWhere(Data.class, new Condition(new QueryCondition("sha512", "=", sha512)));
@ -127,7 +146,7 @@ public class DataResource {
return null;
}
public static Data getWithId(final long id) {
public Data getWithId(final long id) {
LOGGER.info("find id = {}", id);
try {
return DataAccess.get(Data.class, id);
@ -138,13 +157,8 @@ public class DataResource {
return null;
}
public static Data createNewData(final long tmpUID, final String originalFileName, final String sha512)
throws IOException {
// determine mime type:
Data injectedData = new Data();
String mimeType = "";
final String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
mimeType = switch (extension.toLowerCase()) {
protected String getMimeType(final String extension) throws IOException {
return switch (extension.toLowerCase()) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "webp" -> "image/webp";
@ -153,6 +167,15 @@ public class DataResource {
case "webm" -> "video/webm";
default -> throw new IOException("Can not find the mime type of data input: '" + extension + "'");
};
}
public Data createNewData(final long tmpUID, final String originalFileName, final String sha512)
throws IOException {
// determine mime type:
Data injectedData = new Data();
String mimeType = "";
final String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
mimeType = getMimeType(extension);
injectedData.mimeType = mimeType;
injectedData.sha512 = sha512;
final String tmpPath = getTmpFileInData(tmpUID);
@ -164,7 +187,7 @@ public class DataResource {
e.printStackTrace();
return null;
}
final String mediaPath = getFileData(injectedData.uuid);
final String mediaPath = getFileData(injectedData.oid);
LOGGER.info("src = {}", tmpPath);
LOGGER.info("dst = {}", mediaPath);
Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE);
@ -172,9 +195,9 @@ public class DataResource {
return injectedData;
}
public static void modeFileOldModelToNewModel(final long id, final UUID uuid) throws IOException {
String mediaCurentPath = getFileDataOld(id);
String mediaDestPath = getFileData(uuid);
public static void modeFileOldModelToNewModel(final UUID uuid, final ObjectId oid) throws IOException {
String mediaCurentPath = getFileDataOld(uuid);
String mediaDestPath = getFileData(oid);
LOGGER.info("src = {}", mediaCurentPath);
LOGGER.info("dst = {}", mediaDestPath);
if (Files.exists(Paths.get(mediaCurentPath))) {
@ -191,7 +214,8 @@ public class DataResource {
LOGGER.info("Move done");
}
public static String saveTemporaryFile(final InputStream uploadedInputStream, final long idData) {
public static String saveTemporaryFile(final InputStream uploadedInputStream, final long idData)
throws FailException {
return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData));
}
@ -208,35 +232,35 @@ public class DataResource {
}
// save uploaded file to a defined location on the server
static String saveFile(final InputStream uploadedInputStream, final String serverLocation) {
static String saveFile(final InputStream uploadedInputStream, final String serverLocation) throws FailException {
String out = "";
try {
OutputStream outpuStream = new FileOutputStream(new File(serverLocation));
int read = 0;
final byte[] bytes = new byte[CHUNK_SIZE_IN];
final MessageDigest md = MessageDigest.getInstance("SHA-512");
outpuStream = new FileOutputStream(new File(serverLocation));
while ((read = uploadedInputStream.read(bytes)) != -1) {
// logger.info("write {}", read);
md.update(bytes, 0, read);
outpuStream.write(bytes, 0, read);
}
LOGGER.info("Flush input stream ... {}", serverLocation);
System.out.flush();
outpuStream.flush();
outpuStream.close();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
uploadedInputStream.close();
MessageDigest md = null;
try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
md = MessageDigest.getInstance("SHA-512");
} catch (final IOException ex) {
LOGGER.info("Can not write in temporary file ... ");
ex.printStackTrace();
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
} catch (final NoSuchAlgorithmException ex) {
LOGGER.info("Can not find sha512 algorithms");
ex.printStackTrace();
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not find sha512 algorithms", ex);
}
if (md != null) {
try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
int read = 0;
final byte[] bytes = new byte[CHUNK_SIZE_IN];
while ((read = uploadedInputStream.read(bytes)) != -1) {
// logger.info("write {}", read);
md.update(bytes, 0, read);
outpuStream.write(bytes, 0, read);
}
LOGGER.info("Flush input stream ... {}", serverLocation);
outpuStream.flush();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
uploadedInputStream.close();
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
}
}
return out;
}
@ -249,9 +273,9 @@ public class DataResource {
return sb.toString();
}
public Data getSmall(final UUID id) {
public Data getSmall(final ObjectId oid) {
try {
return DataAccess.get(Data.class, id);
return DataAccess.get(Data.class, oid);
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
@ -267,7 +291,7 @@ public class DataResource {
public void uploadFile(
@Context final SecurityContext sc,
@FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
@FormDataParam("file") final FormDataContentDisposition fileMetaData) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.info("===================================================");
LOGGER.info("== DATA uploadFile {}", (gc == null ? "null" : gc.userByToken));
@ -277,14 +301,15 @@ public class DataResource {
final String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++;
try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (final IOException e) {
e.printStackTrace();
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR,
"Impossible to create the folder in the server", ex);
}
saveFile(fileInputStream, filePath);
}
@GET
@Path("{uuid}")
@Path("{oid}")
@PermitTokenInURI
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ -293,20 +318,25 @@ public class DataResource {
@Context final SecurityContext sc,
@QueryParam(HttpHeaders.AUTHORIZATION) final String token,
@HeaderParam("Range") final String range,
@PathParam("uuid") final UUID uuid) throws Exception {
@PathParam("oid") final ObjectId oid) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("===================================================");
LOGGER.info("== DATA retrieveDataId ? id={} user={}", uuid, (gc == null ? "null" : gc.userByToken));
LOGGER.info("== DATA retrieveDataId ? oid={} user={}", oid, (gc == null ? "null" : gc.userByToken));
// logger.info("===================================================");
final Data value = getSmall(uuid);
final Data value = getSmall(oid);
if (value == null) {
Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
try {
return buildStream(getFileData(oid), range,
value.mimeType == null ? "application/octet-stream" : value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
}
return buildStream(getFileData(uuid), range, value.mimeType);
}
@GET
@Path("thumbnail/{uuid}")
@Path("thumbnail/{oid}")
@RolesAllowed("USER")
@PermitTokenInURI
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ -316,27 +346,34 @@ public class DataResource {
@Context final SecurityContext sc,
@QueryParam(HttpHeaders.AUTHORIZATION) final String token,
@HeaderParam("Range") final String range,
@PathParam("uuid") final UUID uuid) throws Exception {
// GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("===================================================");
// logger.info("== DATA retrieveDataThumbnailId ? {}", (gc==null?"null":gc.user));
// logger.info("===================================================");
final Data value = getSmall(uuid);
@PathParam("oid") final ObjectId oid) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.info("===================================================");
LOGGER.info("== DATA retrieveDataThumbnailId ? {}", (gc == null ? "null" : gc.userByToken));
LOGGER.info("===================================================");
final Data value = getSmall(oid);
if (value == null) {
return Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
final String filePathName = getFileData(uuid);
final String filePathName = getFileData(oid);
final File inputFile = new File(filePathName);
if (!inputFile.exists()) {
return Response.status(404).entity("{\"error\":\"media Does not exist: " + uuid + "\"}")
return Response.status(404).entity("{\"error\":\"media Does not exist: " + oid + "\"}")
.type("application/json").build();
}
if (value.mimeType.contentEquals("image/jpeg") || value.mimeType.contentEquals("image/png")
// || value.mimeType.contentEquals("image/webp")
) {
// reads input image
final BufferedImage inputImage = ImageIO.read(inputFile);
final int scaledWidth = 250;
BufferedImage inputImage;
try {
inputImage = ImageIO.read(inputFile);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to READ the image", ex);
}
LOGGER.info("input size image: {}x{} type={}", inputImage.getWidth(), inputImage.getHeight(),
inputImage.getType());
final int scaledWidth = ConfigBaseVariable.getThumbnailWidth();
final int scaledHeight = (int) ((float) inputImage.getHeight() / (float) inputImage.getWidth()
* scaledWidth);
// creates output image
@ -344,23 +381,34 @@ public class DataResource {
// scales the input image to the output image
final Graphics2D g2d = outputImage.createGraphics();
LOGGER.info("output size image: {}x{}", scaledWidth, scaledHeight);
g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose();
// create the output stream:
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// TODO: check how to remove buffer file !!! here, it is not needed at all...
ImageIO.write(outputImage, "JPG", baos);
ImageIO.write(outputImage, ConfigBaseVariable.getThumbnailFormat(), baos);
} catch (final IOException e) {
e.printStackTrace();
return Response.status(500).entity("Internal Error: resize fail: " + e.getMessage()).type("text/plain")
.build();
}
final byte[] imageData = baos.toByteArray();
// Response.ok(new ByteArrayInputStream(imageData)).build();
LOGGER.info("output length {}", imageData.length);
if (imageData.length == 0) {
LOGGER.error("Fail to convert image... Availlable format:");
for (final String data : ImageIO.getWriterFormatNames()) {
LOGGER.error(" - {}", data);
}
}
final Response.ResponseBuilder out = Response.ok(imageData).header(HttpHeaders.CONTENT_LENGTH,
imageData.length);
out.type("image/jpeg");
try {
out.type(getMimeType(ConfigBaseVariable.getThumbnailFormat()));
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR,
"Fail to convert mime type of " + ConfigBaseVariable.getThumbnailFormat(), ex);
}
// TODO: move this in a decorator !!!
final CacheControl cc = new CacheControl();
cc.setMaxAge(3600);
@ -368,11 +416,15 @@ public class DataResource {
out.cacheControl(cc);
return out.build();
}
return buildStream(filePathName, range, value.mimeType);
try {
return buildStream(filePathName, range, value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
}
}
@GET
@Path("{uuid}/{name}")
@Path("{oid}/{name}")
@PermitTokenInURI
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ -380,26 +432,29 @@ public class DataResource {
public Response retrieveDataFull(
@Context final SecurityContext sc,
@QueryParam(HttpHeaders.AUTHORIZATION) final String token,
@HeaderParam("Range") final String range,
@PathParam("uuid") final UUID uuid,
@ApiInputOptional @HeaderParam("Range") final String range,
@PathParam("oid") final ObjectId oid,
@PathParam("name") final String name) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("===================================================");
LOGGER.info("== DATA retrieveDataFull ? id={} user={}", uuid, (gc == null ? "null" : gc.userByToken));
LOGGER.info("== DATA retrieveDataFull ? id={} user={}", oid, (gc == null ? "null" : gc.userByToken));
// logger.info("===================================================");
final Data value = getSmall(uuid);
final Data value = getSmall(oid);
if (value == null) {
Response.status(404).entity("media NOT FOUND: " + uuid).type("text/plain").build();
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
return buildStream(getFileData(uuid), range, value.mimeType);
return buildStream(getFileData(oid), range,
value.mimeType == null ? "application/octet-stream" : value.mimeType);
}
/** Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541
*
* @param range range header
* @return Streaming output
* @throws FileNotFoundException
* @throws Exception IOException if an error occurs in streaming. */
private Response buildStream(final String filename, final String range, final String mimeType) throws Exception {
private Response buildStream(final String filename, final String range, final String mimeType)
throws FailException {
final File file = new File(filename);
// logger.info("request range : {}", range);
// range not requested : Firefox does not send range headers
@ -445,23 +500,29 @@ public class DataResource {
to = file.length() - 1;
}
final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length());
// logger.info("responseRange: {}", responseRange);
final RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(from);
// LOGGER.info("responseRange: {}", responseRange);
try {
final RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(from);
final long len = to - from + 1;
final MediaStreamer streamer = new MediaStreamer(len, raf);
final Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT)
.header("Accept-Ranges", "bytes").header("Content-Range", responseRange)
.header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth())
.header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
if (mimeType != null) {
out.type(mimeType);
final long len = to - from + 1;
final MediaStreamer streamer = new MediaStreamer(len, raf);
final Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT)
.header("Accept-Ranges", "bytes").header("Content-Range", responseRange)
.header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth())
.header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
if (mimeType != null) {
out.type(mimeType);
}
return out.build();
} catch (final FileNotFoundException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to find the required file.", ex);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to access to the required file.", ex);
}
return out.build();
}
public static void undelete(final Long id) throws Exception {
public void undelete(final Long id) throws Exception {
DataAccess.unsetDelete(Data.class, id);
}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.api;
package org.atriasoft.archidata.api;
import java.io.File;
import java.util.List;

View File

@ -1,4 +1,4 @@
package org.kar.archidata.api;
package org.atriasoft.archidata.api;
import java.io.IOException;
import java.io.OutputStream;
@ -58,5 +58,4 @@ public class MediaStreamer implements StreamingOutput {
public long getLenth() {
return this.length;
}
}

View File

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

View File

@ -1,4 +1,4 @@
package org.kar.archidata.api;
package org.atriasoft.archidata.api;
import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource;
import io.swagger.v3.oas.annotations.Operation;

View File

@ -1,4 +1,4 @@
package org.kar.archidata.backup;
package org.atriasoft.archidata.backup;
import java.util.ArrayList;
import java.util.List;

View File

@ -0,0 +1,47 @@
package org.atriasoft.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

@ -1,4 +1,4 @@
package org.kar.archidata.catcher;
package org.atriasoft.archidata.catcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -14,15 +14,15 @@ public class ExceptionCatcher implements ExceptionMapper<Exception> {
public Response toResponse(final Exception exception) {
LOGGER.warn("Catch exception (not managed...):");
final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid);
LOGGER.error("Error OID={}", ret.oid);
exception.printStackTrace();
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ret).type(MediaType.APPLICATION_JSON)
.build();
}
private RestErrorResponse build(final Exception exception) {
return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Catch Unknown Exception",
exception.getMessage());
return new RestErrorResponse(Response.Status.INTERNAL_SERVER_ERROR,
"Catch Unknown Exception: " + exception.getClass().getCanonicalName(), exception.getMessage());
}
}

View File

@ -1,6 +1,6 @@
package org.kar.archidata.catcher;
package org.atriasoft.archidata.catcher;
import org.kar.archidata.exception.FailException;
import org.atriasoft.archidata.exception.FailException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,11 +13,12 @@ public class FailExceptionCatcher implements ExceptionMapper<FailException> {
@Override
public Response toResponse(final FailException exception) {
LOGGER.warn("Catch FailException:");
LOGGER.warn("Catch FailException: {}", exception.getLocalizedMessage());
final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid);
// Not display backtrace ==> this may be a normal case ...
// exception.printStackTrace();
LOGGER.error("Error OID={}", ret.oid);
if (exception.exception != null) {
exception.exception.printStackTrace();
}
return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
}

View File

@ -0,0 +1,26 @@
package org.atriasoft.archidata.catcher;
import org.glassfish.jersey.server.ResourceConfig;
public class GenericCatcher {
/**
* Add All the the generic catcher to standardize returns.
* @param rc Resource exception model.
*/
public static void addAll(final ResourceConfig rc) {
// Generic Json parsing error
rc.register(JacksonExceptionCatcher.class);
// 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

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

View File

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

View File

@ -0,0 +1,34 @@
package org.atriasoft.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,20 +1,50 @@
package org.kar.archidata.catcher;
package org.atriasoft.archidata.catcher;
import java.time.Instant;
import java.util.UUID;
import java.util.List;
import org.kar.archidata.tools.UuidUtils;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.bson.types.ObjectId;
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;
@ApiGenerationMode
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RestErrorResponse {
public UUID uuid = UuidUtils.nextUUID();
public ObjectId oid = new ObjectId();
@NotNull
@Column(length = 0)
public String name; // Mandatory for TS generic error
@NotNull
@Column(length = 0)
public String message; // Mandatory for TS generic error
@NotNull
@Column(length = 0)
public String time;
@NotNull
final public int status;
@NotNull
@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;
@ -22,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) {
@ -30,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) {
@ -38,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.atriasoft.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

@ -1,6 +1,6 @@
package org.kar.archidata.catcher;
package org.atriasoft.archidata.catcher;
import org.kar.archidata.exception.SystemException;
import org.atriasoft.archidata.exception.SystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,7 +15,7 @@ public class SystemExceptionCatcher implements ExceptionMapper<SystemException>
public Response toResponse(final SystemException exception) {
LOGGER.warn("Catch SystemException:");
final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid);
LOGGER.error("Error OID={}", ret.oid);
exception.printStackTrace();
return Response.status(exception.status).entity(ret).type(MediaType.APPLICATION_JSON).build();
}

View File

@ -1,25 +1,26 @@
package org.kar.archidata.catcher;
package org.atriasoft.archidata.catcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class FailException404API implements ExceptionMapper<ClientErrorException> {
private static final Logger LOGGER = LoggerFactory.getLogger(FailException404API.class);
public class WebApplicationExceptionCatcher implements ExceptionMapper<WebApplicationException> {
private static final Logger LOGGER = LoggerFactory.getLogger(WebApplicationExceptionCatcher.class);
@Override
public Response toResponse(final ClientErrorException exception) {
public Response toResponse(final WebApplicationException exception) {
final RestErrorResponse ret = build(exception);
LOGGER.error("Error UUID={}", ret.uuid);
LOGGER.error("Error OID={}", ret.oid);
return Response.status(exception.getResponse().getStatusInfo().toEnum()).entity(ret)
.type(MediaType.APPLICATION_JSON).build();
}
private RestErrorResponse build(final ClientErrorException exception) {
private RestErrorResponse build(final WebApplicationException exception) {
exception.printStackTrace();
return new RestErrorResponse(exception.getResponse().getStatusInfo().toEnum(), "Catch system exception",
exception.getMessage());
}

View File

@ -0,0 +1,898 @@
package org.atriasoft.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.atriasoft.archidata.annotation.AnnotationTools;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.Checker;
import org.atriasoft.archidata.annotation.checker.CollectionItemNotNull;
import org.atriasoft.archidata.annotation.checker.CollectionItemUnique;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.QueryOptions;
import org.atriasoft.archidata.dataAccess.options.CheckFunctionInterface;
import org.atriasoft.archidata.dataAccess.options.CheckFunctionVoid;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.ConditionChecker;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.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.atriasoft.archidata.converter.Jakarta;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Date;
import org.atriasoft.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.atriasoft.archidata.converter.Jakarta;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import org.atriasoft.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.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import java.util.Date;
import org.atriasoft.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.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import java.util.Date;
import org.atriasoft.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

@ -0,0 +1,21 @@
package org.atriasoft.archidata.converter.jackson;
import java.time.OffsetDateTime;
import java.util.Date;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class JacksonModules {
public static SimpleModule getAllModules() {
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,16 @@
package org.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
@Override
public ObjectId deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
return new ObjectId(p.getValueAsString());
}
}

View File

@ -0,0 +1,17 @@
package org.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
@Override
public void serialize(final ObjectId value, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeString(value.toHexString());
}
}

View File

@ -0,0 +1,19 @@
package org.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import java.time.OffsetDateTime;
import org.atriasoft.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.atriasoft.archidata.converter.jackson;
import java.io.IOException;
import java.time.OffsetDateTime;
import org.atriasoft.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

@ -0,0 +1,34 @@
package org.atriasoft.archidata.converter.morphia;
import java.sql.Timestamp;
import org.bson.BsonReader;
import org.bson.BsonType;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
public class SqlTimestampCodec implements Codec<Timestamp> {
@Override
public void encode(
final BsonWriter writer,
final Timestamp value,
final org.bson.codecs.EncoderContext encoderContext) {
writer.writeDateTime(value.getTime());
}
@Override
public Timestamp decode(final BsonReader reader, final org.bson.codecs.DecoderContext decoderContext) {
final BsonType bsonType = reader.getCurrentBsonType();
if (bsonType == BsonType.DATE_TIME) {
return new Timestamp(reader.readDateTime());
} else {
throw new IllegalArgumentException("Expected a DATE_TIME but found " + bsonType);
}
}
@Override
public Class<Timestamp> getEncoderClass() {
return Timestamp.class;
}
}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
/** Java does not permit to set return data (eg: integer) in the function parameter. This class permit to update a value as in/out function parameters. */
public class CountInOut {

View File

@ -0,0 +1,350 @@
package org.atriasoft.archidata.dataAccess;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.atriasoft.archidata.annotation.AnnotationTools;
import org.atriasoft.archidata.annotation.AnnotationTools.FieldName;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.FilterValue;
import org.atriasoft.archidata.dataAccess.options.Limit;
import org.atriasoft.archidata.dataAccess.options.OptionSpecifyType;
import org.atriasoft.archidata.dataAccess.options.QueryOption;
import org.atriasoft.archidata.dataAccess.options.TransmitKey;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.db.DbIo;
import org.atriasoft.archidata.db.DbIoFactory;
import org.atriasoft.archidata.db.DbIoMorphia;
import org.atriasoft.archidata.db.DbIoSql;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ContextGenericTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.InternalServerErrorException;
/* TODO list:
- Manage to group of SQL action to permit to commit only at the end.
*/
/** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite
* back-end. */
public abstract class DBAccess implements Closeable {
final static Logger LOGGER = LoggerFactory.getLogger(DBAccess.class);
public static final DBAccess createInterface()
throws InternalServerErrorException, IOException, DataAccessException {
return DBAccess.createInterface(DbIoFactory.create());
}
public static final DBAccess createInterface(final DbConfig config)
throws InternalServerErrorException, IOException {
return DBAccess.createInterface(DbIoFactory.create(config));
}
public static final DBAccess createInterface(final DbIo io) throws InternalServerErrorException {
if (io instanceof final DbIoMorphia ioMorphia) {
try {
return new DBAccessMorphia(ioMorphia);
} catch (final IOException e) {
e.printStackTrace();
throw new InternalServerErrorException("Fail to create DB interface.");
}
} else if (io instanceof final DbIoSql ioSQL) {
try {
return new DBAccessSQL(ioSQL);
} catch (final IOException e) {
e.printStackTrace();
throw new InternalServerErrorException("Fail to create DB interface.");
}
}
throw new InternalServerErrorException("unknow DB interface ... ");
}
public boolean isDBExist(final String name, final QueryOption... option) throws InternalServerErrorException {
throw new InternalServerErrorException("Can Not manage the DB-access");
}
public boolean createDB(final String name) {
throw new InternalServerErrorException("Can Not manage the DB-access");
}
public boolean deleteDB(final String name) {
throw new InternalServerErrorException("Can Not manage the DB-access");
}
public boolean isTableExist(final String name, final QueryOption... option) throws InternalServerErrorException {
throw new InternalServerErrorException("Can Not manage the DB-access");
}
public <ID_TYPE> QueryCondition getTableIdCondition(
final Class<?> clazz,
final ID_TYPE idKey,
final QueryOptions options) throws DataAccessException {
// Find the ID field type ....
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");
}
// check the compatibility of the id and the declared ID
Class<?> typeClass = idField.getType();
if (idKey == null) {
throw new DataAccessException("Try to identify the ID type and object was null.");
}
final FieldName fieldName = AnnotationTools.getFieldName(idField, options);
final List<OptionSpecifyType> specificTypes = options.get(OptionSpecifyType.class);
if (typeClass == Object.class) {
for (final OptionSpecifyType specify : specificTypes) {
if (specify.name.equals(fieldName.inStruct())) {
typeClass = specify.clazz;
LOGGER.trace("Detect overwrite of typing ... '{}' => '{}'", typeClass.getCanonicalName(),
specify.clazz.getCanonicalName());
break;
}
}
}
if (idKey.getClass() != typeClass) {
if (idKey.getClass() == Condition.class) {
throw new DataAccessException(
"Try to identify the ID type on a condition 'close' internal API error use xxxWhere(...) instead.");
}
throw new DataAccessException("Request update with the wrong type ...");
}
return new QueryCondition(fieldName.inTable(), "=", idKey);
}
// TODO: manage insert batch...
public <T> List<T> insertMultiple(final List<T> data, final QueryOption... options) throws Exception {
final List<T> out = new ArrayList<>();
for (final T elem : data) {
final T tmp = insert(elem, options);
out.add(tmp);
}
return out;
}
abstract public <T> T insert(final T data, final QueryOption... option) throws Exception;
// seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated...
public <T> T insertWithJson(final Class<T> clazz, final String jsonData) throws Exception {
final ObjectMapper mapper = ContextGenericTools.createObjectMapper();
// parse the object to be sure the data are valid:
final T data = mapper.readValue(jsonData, clazz);
return insert(data);
}
/** Update an object with the inserted json data
*
* @param <T> Type of the object to insert
* @param <ID_TYPE> Master key on the object manage with @Id
* @param clazz Class reference of the insertion model
* @param id Key to insert data
* @param jsonData Json data (partial) values to update
* @return the number of object updated
* @throws Exception */
public <T, ID_TYPE> long updateWithJson(
final Class<T> clazz,
final ID_TYPE id,
final String jsonData,
final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
options.add(new TransmitKey(id));
return updateWhereWithJson(clazz, jsonData, options.getAllArray());
}
public <T> long updateWhereWithJson(final Class<T> clazz, final String jsonData, final QueryOption... option)
throws Exception {
final QueryOptions options = new QueryOptions(option);
if (options.get(Condition.class).size() == 0) {
throw new DataAccessException("request a updateWhereWithJson without any condition");
}
final ObjectMapper mapper = ContextGenericTools.createObjectMapper();
// parse the object to be sure the data are valid:
final T data = mapper.readValue(jsonData, clazz);
// Read the tree to filter injection of data:
final JsonNode root = mapper.readTree(jsonData);
final List<String> keys = new ArrayList<>();
final var iterator = root.fieldNames();
iterator.forEachRemaining(e -> keys.add(e));
options.add(new FilterValue(keys));
return updateWhere(data, options.getAllArray());
}
public <T, ID_TYPE> long update(final T data, final ID_TYPE id) throws Exception {
return update(data, id, AnnotationTools.getFieldsNames(data.getClass()));
}
/** @param <T>
* @param data
* @param id
* @param updateColomn
* @param option
* @return the affected rows.
* @throws Exception */
public <T, ID_TYPE> long update(
final T data,
final ID_TYPE id,
final List<String> updateColomn,
final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(data.getClass(), id, options)));
options.add(new FilterValue(updateColomn));
options.add(new TransmitKey(id));
return updateWhere(data, options);
}
public <T> long updateWhere(final T data, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return updateWhere(data, options);
}
public abstract <T> long updateWhere(final T data, QueryOptions options) throws Exception;
public <T> T getWhere(final Class<T> clazz, final QueryOptions options) throws Exception {
options.add(new Limit(1));
final List<T> values = getsWhere(clazz, options);
if (values.size() == 0) {
return null;
}
return values.get(0);
}
public <T> T getWhere(final Class<T> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return getWhere(clazz, options);
}
public <T> List<T> getsWhere(final Class<T> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return getsWhere(clazz, options);
}
public Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty)
throws DataAccessException {
if (options == null) {
return new Condition();
}
final List<Condition> conditions = options.get(Condition.class);
if (conditions.size() == 0) {
if (throwIfEmpty) {
throw new DataAccessException("request a gets without any condition");
} else {
return new Condition();
}
}
Condition condition = null;
if (conditions.size() == 1) {
condition = conditions.get(0);
} else {
final QueryAnd andCondition = new QueryAnd();
for (final Condition cond : conditions) {
andCondition.add(cond.condition);
}
condition = new Condition(andCondition);
}
return condition;
}
abstract public <T> List<T> getsWhere(final Class<T> clazz, final QueryOptions options)
throws DataAccessException, IOException;
public <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return countWhere(clazz, options);
}
public long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return countWhere(clazz, options);
}
public abstract long countWhere(final Class<?> clazz, final QueryOptions options) throws Exception;
public <T, ID_TYPE> T get(final Class<T> clazz, final ID_TYPE id, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return getWhere(clazz, options.getAllArray());
}
public <T> List<T> gets(final Class<T> clazz) throws Exception {
return getsWhere(clazz);
}
public <T> List<T> gets(final Class<T> clazz, final QueryOption... option) throws Exception {
return getsWhere(clazz, option);
}
/** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before).
* @param <ID_TYPE> Type of the reference @Id
* @param clazz Data model that might remove element
* @param id Unique Id of the model
* @param options (Optional) Options of the request
* @return Number of element that is removed. */
public <ID_TYPE> long delete(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
final String hasDeletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
if (hasDeletedFieldName != null) {
return deleteSoft(clazz, id, options);
} else {
return deleteHard(clazz, id, options);
}
}
/** 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 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);
if (hasDeletedFieldName != null) {
return deleteSoftWhere(clazz, option);
} else {
return deleteHardWhere(clazz, option);
}
}
public <ID_TYPE> long deleteHard(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return deleteHardWhere(clazz, options.getAllArray());
}
public abstract long deleteHardWhere(final Class<?> clazz, final QueryOption... option) throws Exception;
public <ID_TYPE> long deleteSoft(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return deleteSoftWhere(clazz, options.getAllArray());
}
public abstract long deleteSoftWhere(final Class<?> clazz, final QueryOption... option) throws Exception;
public <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id) throws DataAccessException {
return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id, new QueryOptions())));
}
public <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws DataAccessException {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return unsetDeleteWhere(clazz, options.getAllArray());
}
public abstract long unsetDeleteWhere(final Class<?> clazz, final QueryOption... option) throws DataAccessException;
public abstract void drop(final Class<?> clazz, final QueryOption... option) throws Exception;
public abstract void cleanAll(final Class<?> clazz, final QueryOption... option) throws Exception;
}

View File

@ -0,0 +1,970 @@
package org.atriasoft.archidata.dataAccess;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.AnnotationTools;
import org.atriasoft.archidata.annotation.AnnotationTools.FieldName;
import org.atriasoft.archidata.annotation.CreationTimestamp;
import org.atriasoft.archidata.annotation.UpdateTimestamp;
import org.atriasoft.archidata.dataAccess.addOnMongo.AddOnManyToOne;
import org.atriasoft.archidata.dataAccess.addOnMongo.AddOnOneToMany;
import org.atriasoft.archidata.dataAccess.addOnMongo.DataAccessAddOn;
import org.atriasoft.archidata.dataAccess.options.CheckFunction;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.FilterValue;
import org.atriasoft.archidata.dataAccess.options.Limit;
import org.atriasoft.archidata.dataAccess.options.OptionSpecifyType;
import org.atriasoft.archidata.dataAccess.options.OrderBy;
import org.atriasoft.archidata.dataAccess.options.QueryOption;
import org.atriasoft.archidata.db.DbIoMorphia;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.UuidUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.ReturnDocument;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.client.result.UpdateResult;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.InternalServerErrorException;
/* TODO list:
- Manage to group of SQL action to permit to commit only at the end.
*/
/** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite
* back-end. */
public class DBAccessMorphia extends DBAccess {
static final Logger LOGGER = LoggerFactory.getLogger(DBAccessMorphia.class);
// by default we manage some add-on that permit to manage non-native model (like json serialization, List of external key as String list...)
static final List<DataAccessAddOn> addOn = new ArrayList<>();
static {
//addOn.add(new AddOnManyToMany());
addOn.add(new AddOnManyToOne());
addOn.add(new AddOnOneToMany());
// no need, native support in mango .... addOn.add(new AddOnDataJson());
}
/** Add a new add-on on the current management.
* @param addOn instantiate object on the Add-on
*/
public static void addAddOn(final DataAccessAddOn addOn) {
DBAccessMorphia.addOn.add(addOn);
}
private final DbIoMorphia db;
public DBAccessMorphia(final DbIoMorphia db) throws IOException {
this.db = db;
db.open();
}
@Override
public void close() throws IOException {
this.db.close();
}
public DbIoMorphia getInterface() {
return this.db;
}
@Override
public boolean isDBExist(final String name, final QueryOption... option) throws InternalServerErrorException {
// in Mongo DB we do not need to create a DB, then we have no need to check if it exist
return true;
}
@Override
public boolean createDB(final String name) {
// in Mongo DB we do not need to create a DB
return true;
}
@Override
public boolean deleteDB(final String name) {
final MongoDatabase database = this.db.getClient().getDatabase(name);
database.drop();
return true;
}
@Override
public boolean isTableExist(final String name, final QueryOption... option) throws InternalServerErrorException {
return true;
}
public byte[][] splitIntoGroupsOf16Bytes(final byte[] input) {
final int inputLength = input.length;
final int numOfGroups = (inputLength + 15) / 16; // Calculate the number of groups needed
final byte[][] groups = new byte[numOfGroups][16];
for (int i = 0; i < numOfGroups; i++) {
final int startIndex = i * 16;
final int endIndex = Math.min(startIndex + 16, inputLength);
groups[i] = Arrays.copyOfRange(input, startIndex, endIndex);
}
return groups;
}
protected <T> void setValueToDb(
final Class<?> type,
final T data,
final Field field,
final String fieldName,
final Document docSet,
final Document docUnSet) throws Exception {
if (field.get(data) == null) {
if (docUnSet != null) {
docUnSet.append(fieldName, "");
}
return;
}
if (type == long.class) {
docSet.append(fieldName, field.getLong(data));
return;
}
if (type == int.class) {
docSet.append(fieldName, field.getInt(data));
return;
}
if (type == float.class) {
docSet.append(fieldName, field.getFloat(data));
return;
}
if (type == double.class) {
docSet.append(fieldName, field.getDouble(data));
return;
}
if (type == boolean.class) {
docSet.append(fieldName, field.getBoolean(data));
return;
}
final Object tmp = field.get(data);
if (tmp == null) {
docUnSet.append(fieldName, "");
return;
}
if (type.isEnum()) {
docSet.append(fieldName, tmp.toString());
return;
}
if (type == Long.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Integer.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Float.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Double.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Boolean.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == String.class) {
docSet.append(fieldName, tmp);
return;
}
if (type == Timestamp.class) {
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) {
/*
final Timestamp sqlDate = java.sql.Timestamp.from(((Date) tmp).toInstant());
ps.setTimestamp(iii.value, sqlDate);
*/
}
if (type == Instant.class) {
/*
final String sqlDate = ((Instant) tmp).toString();
ps.setString(iii.value, sqlDate);
*/
}
if (type == LocalDate.class) {
/*
final java.sql.Date sqlDate = java.sql.Date.valueOf((LocalDate) tmp);
ps.setDate(iii.value, sqlDate);
*/
}
if (type == LocalTime.class) {
/*
final java.sql.Time sqlDate = java.sql.Time.valueOf((LocalTime) tmp);
ps.setTime(iii.value, sqlDate);
*/
}
docSet.append(fieldName, tmp);
//throw new DataAccessException("Unknown Field Type");
}
public <T> void setValueFromDoc(
final Class<?> type,
final Object data,
final Field field,
final Document doc,
final List<LazyGetter> lazyCall,
final QueryOptions options) throws Exception {
final String fieldName = AnnotationTools.getFieldName(field, options).inTable();
if (!doc.containsKey(fieldName)) {
field.set(data, null);
return;
}
if (type == UUID.class) {
final UUID value = doc.get(fieldName, UUID.class);
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);
return;
}
if (type == Integer.class || type == int.class) {
final Integer value = doc.getInteger(fieldName);
field.set(data, value);
return;
}
if (type == Float.class || type == float.class) {
final Double value = doc.getDouble(fieldName);
field.set(data, (float) ((double) value));
return;
}
if (type == Double.class || type == double.class) {
final Double value = doc.getDouble(fieldName);
field.set(data, value);
return;
}
if (type == Boolean.class || type == boolean.class) {
final Boolean value = doc.getBoolean(fieldName);
field.set(data, value);
return;
}
if (type == Timestamp.class) {
final Date value = doc.get(fieldName, Date.class);
final Timestamp newData = new Timestamp(value.getTime());
field.set(data, newData);
return;
}
if (type == Date.class) {
final Date value = doc.get(fieldName, Date.class);
field.set(data, value);
return;
}
if (type == Instant.class) {
final Date value = doc.get(fieldName, Date.class);
final Instant newData = value.toInstant();
field.set(data, newData);
return;
}
if (type == LocalDate.class) {
final Date value = doc.get(fieldName, Date.class);
final LocalDate newData = value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
field.set(data, newData);
return;
}
if (type == LocalTime.class) {
final Long value = doc.getLong(fieldName);
final LocalTime newData = LocalTime.ofNanoOfDay(value);
field.set(data, newData);
return;
}
if (type == String.class) {
final String value = doc.getString(fieldName);
field.set(data, value);
return;
}
if (type == UUID.class) {
final Object value = doc.get(fieldName, field.getType());
field.set(data, value);
return;
}
if (type.isEnum()) {
final String value = doc.getString(fieldName);
boolean find = false;
final Object[] arr = type.getEnumConstants();
for (final Object elem : arr) {
if (elem.toString().equals(value)) {
field.set(data, elem);
find = true;
break;
}
}
if (!find) {
throw new DataAccessException("Enum value does not exist in the Model: '" + value + "'");
}
return;
}
if (List.class == field.getType()) {
final Object value = doc.get(fieldName, field.getType());
field.set(data, value);
} else {
final Object value = createObjectFromDocument(doc.get(fieldName, Document.class), field.getType(),
new QueryOptions(), lazyCall);
field.set(data, value);
}
return;
//throw new ArchiveException("wrong type of field [" + fieldName + "]: " + doc.toJson());
}
protected Object convertDefaultField(String data, final Field field) throws Exception {
if (data.startsWith("'") && data.endsWith("'")) {
data = data.substring(1, data.length() - 1);
}
final Class<?> type = field.getType();
if (type == UUID.class) {
}
if (type == Long.class || type == long.class) {
return Long.parseLong(data);
}
if (type == Integer.class || type == int.class) {
return Integer.parseInt(data);
}
if (type == Float.class || type == float.class) {
return Float.parseFloat(data);
}
if (type == Double.class || type == double.class) {
return Double.parseDouble(data);
}
if (type == Boolean.class || type == boolean.class) {
return Boolean.parseBoolean(data);
}
if (type == Timestamp.class) {}
if (type == Date.class) {}
if (type == Instant.class) {}
if (type == LocalDate.class) {}
if (type == LocalTime.class) {}
if (type == String.class) {}
if (type.isEnum()) {
final boolean find = false;
final Object[] arr = type.getEnumConstants();
for (final Object elem : arr) {
if (elem.toString().equals(data)) {
return elem;
}
}
if (!find) {
throw new DataAccessException("Enum value does not exist in the Model: '" + data + "'");
}
}
LOGGER.warn("Request default of unknow native type {} => {}", type.getCanonicalName(), data);
return null;
}
public boolean isAddOnField(final Field field) {
return findAddOnforField(field) != null;
}
public DataAccessAddOn findAddOnforField(final Field field) {
for (final DataAccessAddOn elem : addOn) {
if (elem.isCompatibleField(field)) {
return elem;
}
}
return null;
}
public long getNextSequenceLongValue(final String collectionName, String fieldName) {
if (fieldName == null || fieldName.isEmpty()) {
fieldName = "sequence_id";
}
// Collection "counters" to store the sequences if Ids
final MongoCollection<Document> countersCollection = this.db.getDatastore().getDatabase()
.getCollection("counters");
// Filter to find the specific counter for the collections
final Document filter = new Document("_id", collectionName);
// Update the field <fieldName> of 1
final Document update = new Document("$inc", new Document(fieldName, 1L));
// get the value after updated it
final FindOneAndUpdateOptions options = new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
.upsert(true); // create field if not exist
// Real creation of the unique counter.
final Document updatedCounter = countersCollection.findOneAndUpdate(filter, update, options);
// Return the new sequence value...
return updatedCounter.getLong(fieldName);
}
@Override
@SuppressWarnings("unchecked")
public <T> T insert(final T data, final QueryOption... option) throws Exception {
final Class<?> clazz = data.getClass();
final QueryOptions options = new QueryOptions(option);
// External checker of data:
final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) {
check.getChecker().check(this, "", data, AnnotationTools.getFieldsNames(clazz), options);
}
final List<Field> asyncFieldUpdate = new ArrayList<>();
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
Field primaryKeyField = null;
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 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 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;
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.inTable());
uniqueId = id;
docSet.append(tableFieldName.inTable(), id);
continue;
}
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);
if (addOn != null && !addOn.canInsert(field)) {
if (addOn.isInsertAsync(field)) {
LOGGER.error("TODO: add async objects ...");
//asyncFieldUpdate.add(field);
}
continue;
}
if (currentInsertValue == null && !field.getClass().isPrimitive()) {
final DefaultValue[] defaultValue = field.getDeclaredAnnotationsByType(DefaultValue.class);
if (defaultValue.length == 0) {
continue;
} else {
final String value = defaultValue[0].value();
if (value == null) {
continue;
}
currentInsertValue = convertDefaultField(value, field);
}
}
// 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(docSet);
// Get the Object of inserted object:
insertedId = result.getInsertedId().asObjectId().getValue();
LOGGER.info("Document inserted with ID: " + insertedId);
// Rechercher et récupérer le document inséré à partir de son ObjectId
final Document insertedDocument = collection.find(new Document("_id", insertedId)).first();
// Afficher le document récupéré
LOGGER.trace("Inserted document: " + insertedDocument);
} catch (final Exception ex) {
LOGGER.error("Fail SQL request: {}", ex.getMessage());
ex.printStackTrace();
throw new DataAccessException("Fail to Insert data in DB : " + ex.getMessage());
}
final List<LazyGetter> asyncActions = new ArrayList<>();
for (final Field field : asyncFieldUpdate) {
final DataAccessAddOn addOn = findAddOnforField(field);
if (uniqueId instanceof final Long id) {
LOGGER.error("TODO: Add on not managed .1. ");
//addOn.asyncInsert(tableName, id, field, field.get(data), asyncActions);
} else if (uniqueId instanceof final UUID uuid) {
LOGGER.error("TODO: Add on not managed .2. ");
//addOn.asyncInsert(tableName, uuid, field, field.get(data), asyncActions);
}
}
for (final LazyGetter action : asyncActions) {
action.doRequest();
}
return (T) getWhere(clazz, new Condition(new QueryCondition("_id", "=", insertedId)));
}
@Override
public <T> long updateWhere(final T data, QueryOptions options) throws Exception {
final Class<?> clazz = data.getClass();
if (options == null) {
options = new QueryOptions();
}
final Condition condition = conditionFusionOrEmpty(options, true);
final List<FilterValue> filterKeys = options != null ? options.get(FilterValue.class) : new ArrayList<>();
if (filterKeys.size() != 1) {
throw new DataAccessException("request a gets without/or with more 1 filter of values");
}
final FilterValue filterKey = filterKeys.get(0);
// External checker of data:
if (options != null) {
final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) {
check.getChecker().check(this, "", data, filterKey.getValues(), options);
}
}
final List<LazyGetter> asyncActions = new ArrayList<>();
// real add in the BDD:
try {
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
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 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.inTable(), Date.from(Instant.now()));
continue;
}
if (!filterKey.getValues().contains(fieldName.inStruct())) {
continue;
} else if (AnnotationTools.isGenericField(field)) {
continue;
}
final DataAccessAddOn addOn = findAddOnforField(field);
if (addOn != null && !addOn.canInsert(field)) {
if (addOn.isInsertAsync(field)) {
LOGGER.error("TODO: Add on not managed .3. ");
/*
final List<TransmitKey> transmitKey = options.get(TransmitKey.class);
if (transmitKey.size() != 1) {
throw new DataAccessException(
"Fail to transmit Key to update the async update... (must have only 1)");
}
addOn.asyncUpdate(tableName, transmitKey.get(0).getKey(), field, field.get(data), asyncActions);
*/
}
continue;
}
if (addOn != null) {
addOn.insertData(this, field, data, options, docSet, docUnSet);
} else {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
final Object tmp = field.get(data);
if (tmp == null && field.getDeclaredAnnotationsByType(DefaultValue.class).length != 0) {
continue;
}
}
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();
if (!docSet.isEmpty()) {
actions.append("$set", docSet);
}
if (!docUnSet.isEmpty()) {
actions.append("$unset", docUnSet);
}
LOGGER.info("updateWhere with value: {}", actions.toJson());
final UpdateResult ret = collection.updateMany(filters, actions);
return ret.getModifiedCount();
} catch (final Exception ex) {
ex.printStackTrace();
}
for (final LazyGetter action : asyncActions) {
action.doRequest();
}
return 0;
}
public List<String> generateSelectField(final Class<?> clazz, final QueryOptions options) throws Exception {
final boolean readAllfields = QueryOptions.readAllColomn(options);
final List<String> fieldsName = new ArrayList<>();
for (final Field elem : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
continue;
}
final DataAccessAddOn addOn = findAddOnforField(elem);
if (addOn != null && !addOn.canRetrieve(elem)) {
continue;
}
final boolean notRead = AnnotationTools.isDefaultNotRead(elem);
if (!readAllfields && notRead) {
continue;
}
final String name = AnnotationTools.getFieldName(elem, options).inTable();
fieldsName.add(name);
}
return fieldsName;
}
@Override
public Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty)
throws DataAccessException {
if (options == null) {
return new Condition();
}
final List<Condition> conditions = options.get(Condition.class);
if (conditions.size() == 0) {
if (throwIfEmpty) {
throw new DataAccessException("request a gets without any condition");
} else {
return new Condition();
}
}
Condition condition = null;
if (conditions.size() == 1) {
condition = conditions.get(0);
} else {
final QueryAnd andCondition = new QueryAnd();
for (final Condition cond : conditions) {
andCondition.add(cond.condition);
}
condition = new Condition(andCondition);
}
return condition;
}
@Override
@SuppressWarnings("unchecked")
public <T> List<T> getsWhere(final Class<T> clazz, final QueryOptions options)
throws DataAccessException, IOException {
final Condition condition = conditionFusionOrEmpty(options, false);
final List<LazyGetter> lazyCall = new ArrayList<>();
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final List<T> outs = new ArrayList<>();
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
try {
// Generate the filtering of the data:
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
FindIterable<Document> retFind = null;
if (filters != null) {
//LOGGER.info("getsWhere Find filter: {}", filters.toBsonDocument().toJson());
retFind = collection.find(filters);
} else {
retFind = collection.find();
}
/* Not manage right now ...
final List<GroupBy> groups = options.get(GroupBy.class);
for (final GroupBy group : groups) {
group.generateQuery(query, tableName);
}
*/
final List<OrderBy> orders = options.get(OrderBy.class);
if (orders.size() != 0) {
final Document sorts = new Document();
for (final OrderBy order : orders) {
order.generateSort(sorts);
}
retFind = retFind.sort(sorts);
}
final List<Limit> limits = options.get(Limit.class);
if (limits.size() == 1) {
retFind = retFind.limit((int) limits.get(0).getValue());
} else if (limits.size() > 1) {
throw new DataAccessException("Request with multiple 'limit'...");
}
// Select values to read
final List<String> listFields = generateSelectField(clazz, options);
listFields.add("_id");
retFind = retFind.projection(Projections.include(listFields.toArray(new String[0])));
LOGGER.info("GetsWhere ...");
final MongoCursor<Document> cursor = retFind.iterator();
try (cursor) {
while (cursor.hasNext()) {
final Document doc = cursor.next();
LOGGER.info(" - getWhere value: {}", doc.toJson());
final Object data = createObjectFromDocument(doc, clazz, options, lazyCall);
final T out = (T) data;
outs.add(out);
}
LOGGER.info("Async calls: {}", lazyCall.size());
for (final LazyGetter elem : lazyCall) {
elem.doRequest();
}
}
} catch (final Exception ex) {
ex.printStackTrace();
throw new DataAccessException("Catch an Exception: " + ex.getMessage());
}
return outs;
}
public Object createObjectFromDocument(
final Document doc,
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;
for (final Constructor<?> contructor : clazz.getConstructors()) {
if (contructor.getParameterCount() == 0) {
data = contructor.newInstance();
break;
}
}
if (data == null) {
throw new DataAccessException(
"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) {
addOn.fillFromDoc(this, doc, elem, data, options, lazyCall);
} else {
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;
}
@Override
public <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return this.countWhere(clazz, options);
}
@Override
public long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return countWhere(clazz, options);
}
@Override
public long countWhere(final Class<?> clazz, final QueryOptions options) throws Exception {
final Condition condition = conditionFusionOrEmpty(options, false);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
try {
// Generate the filtering of the data:
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
if (filters != null) {
return collection.countDocuments(filters);
}
return collection.countDocuments();
} catch (final Exception ex) {
ex.printStackTrace();
throw new DataAccessException("Catch an Exception: " + ex.getMessage());
}
}
@Override
public <T, ID_TYPE> T get(final Class<T> clazz, final ID_TYPE id, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return this.getWhere(clazz, options.getAllArray());
}
@Override
public <ID_TYPE> long deleteHard(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return deleteHardWhere(clazz, options.getAllArray());
}
@Override
public long deleteHardWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
final Condition condition = conditionFusionOrEmpty(options, true);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
DeleteResult retFind;
if (filters != null) {
retFind = collection.deleteMany(filters);
} else {
throw new DataAccessException("Too dangerout to delete element with no filter values !!!");
}
return retFind.getDeletedCount();
}
@Override
public <ID_TYPE> long deleteSoft(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return deleteSoftWhere(clazz, options.getAllArray());
}
@Override
public long deleteSoftWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
final Condition condition = conditionFusionOrEmpty(options, true);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
final Document actions = new Document("$set", new Document(deletedFieldName, true));
LOGGER.info("update some values: {}", actions.toJson());
final UpdateResult ret = collection.updateMany(filters, actions);
return ret.getModifiedCount();
}
@Override
public <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id) throws DataAccessException {
return unsetDeleteWhere(clazz, new Condition(getTableIdCondition(clazz, id, new QueryOptions())));
}
@Override
public <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
throws DataAccessException {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id, options)));
return unsetDeleteWhere(clazz, options.getAllArray());
}
@Override
public long unsetDeleteWhere(final Class<?> clazz, final QueryOption... option) throws DataAccessException {
final QueryOptions options = new QueryOptions(option);
final Condition condition = conditionFusionOrEmpty(options, true);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
if (deletedFieldName == null) {
throw new DataAccessException("The class " + clazz.getCanonicalName() + " has no deleted field");
}
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
final Bson filters = condition.getFilter(collectionName, options, deletedFieldName);
final Document actions = new Document("$set", new Document(deletedFieldName, false));
LOGGER.info("update some values: {}", actions.toJson());
final UpdateResult ret = collection.updateMany(filters, actions);
return ret.getModifiedCount();
}
@Override
public void drop(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
collection.drop();
}
@Override
public void cleanAll(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
final String collectionName = AnnotationTools.getCollectionName(clazz, options);
final MongoCollection<Document> collection = this.db.getDatastore().getDatabase().getCollection(collectionName);
collection.deleteMany(new Document());
}
}

View File

@ -0,0 +1,289 @@
package org.atriasoft.archidata.dataAccess;
import java.io.IOException;
import java.util.List;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.QueryOption;
import org.atriasoft.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;
/* TODO list:
- Manage to group of SQL action to permit to commit only at the end.
*/
/** Data access is an abstraction class that permit to access on the DB with a function wrapping that permit to minimize the SQL writing of SQL code. This interface support the SQL and SQLite
* back-end. */
public class DataAccess {
private static final Logger LOGGER = LoggerFactory.getLogger(DataAccess.class);
public DataAccess() {
}
public static boolean isDBExist(final String name, final QueryOption... options)
throws InternalServerErrorException, IOException, DataAccessException {
try (DBAccess db = DBAccess.createInterface()) {
return db.isDBExist(name, options);
}
}
public static boolean createDB(final String name)
throws IOException, InternalServerErrorException, DataAccessException {
try (DBAccess db = DBAccess.createInterface()) {
return db.createDB(name);
}
}
public static boolean isTableExist(final String name, final QueryOption... options)
throws InternalServerErrorException, IOException, DataAccessException {
try (DBAccess db = DBAccess.createInterface()) {
return db.isTableExist(name, options);
}
}
// TODO: manage insert batch...
public static <T> List<T> insertMultiple(final List<T> data, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.insertMultiple(data, options);
}
}
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public static <T> T insert(final T data, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.insert(data, options);
}
}
// seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated...
public static <T> T insertWithJson(final Class<T> clazz, final String jsonData) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.insertWithJson(clazz, jsonData);
}
}
public static <ID_TYPE> QueryCondition getTableIdCondition(
final Class<?> clazz,
final ID_TYPE idKey,
final QueryOptions options) throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.getTableIdCondition(clazz, idKey, options);
}
}
/** Update an object with the inserted json data
*
* @param <T> Type of the object to insert
* @param <ID_TYPE> Master key on the object manage with @Id
* @param clazz Class reference of the insertion model
* @param id Key to insert data
* @param jsonData Json data (partial) values to update
* @return the number of object updated
* @throws Exception */
public static <T, ID_TYPE> long updateWithJson(
final Class<T> clazz,
final ID_TYPE id,
final String jsonData,
final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.updateWithJson(clazz, id, jsonData, options);
}
}
public static <T> long updateWhereWithJson(
final Class<T> clazz,
final String jsonData,
final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.updateWhereWithJson(clazz, jsonData, options);
}
}
public static <T, ID_TYPE> long update(final T data, final ID_TYPE id) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.update(data, id);
}
}
public static <T, ID_TYPE> long update(
final T data,
final ID_TYPE id,
final List<String> updateColomn,
final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.update(data, id, updateColomn, options);
}
}
public static <T> long updateWhere(final T data, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.updateWhere(data, options);
}
}
public static <T> long updateWhere(final T data, final QueryOptions options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.updateWhere(data, options);
}
}
public static <T> T getWhere(final Class<T> clazz, final QueryOptions options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.getWhere(clazz, options);
}
}
public static <T> T getWhere(final Class<T> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.getWhere(clazz, options);
}
}
public static <T> List<T> getsWhere(final Class<T> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.getsWhere(clazz, options);
}
}
public static Condition conditionFusionOrEmpty(final QueryOptions options, final boolean throwIfEmpty)
throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.conditionFusionOrEmpty(options, throwIfEmpty);
}
}
public static <T> List<T> getsWhere(final Class<T> clazz, final QueryOptions options)
throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.getsWhere(clazz, options);
}
}
public static <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.count(clazz, id, options);
}
}
public static long countWhere(final Class<?> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.countWhere(clazz, options);
}
}
public static long countWhere(final Class<?> clazz, final QueryOptions options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.countWhere(clazz, options);
}
}
@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()) {
return db.get(clazz, id, options);
}
}
public static <T> List<T> gets(final Class<T> clazz) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.gets(clazz);
}
}
public static <T> List<T> gets(final Class<T> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.gets(clazz, options);
}
}
/** Delete items with the specific Id (cf @Id) and some options. If the Entity is manage as a softDeleted model, then it is flag as removed (if not already done before).
* @param <ID_TYPE> Type of the reference @Id
* @param clazz Data model that might remove element
* @param id Unique Id of the model
* @param options (Optional) Options of the request
* @return Number of element that is removed. */
public static <ID_TYPE> long delete(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.delete(clazz, id, options);
}
}
/** 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 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 {
try (DBAccess db = DBAccess.createInterface()) {
return db.deleteWhere(clazz, options);
}
}
public static <ID_TYPE> long deleteHard(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.deleteHard(clazz, id, options);
}
}
public static long deleteHardWhere(final Class<?> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.deleteHardWhere(clazz, options);
}
}
public static <ID_TYPE> long deleteSoft(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.deleteSoft(clazz, id, options);
}
}
public static long deleteSoftWhere(final Class<?> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
return db.deleteSoftWhere(clazz, options);
}
}
public static <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id)
throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.unsetDelete(clazz, id);
}
}
public static <ID_TYPE> long unsetDelete(final Class<?> clazz, final ID_TYPE id, final QueryOption... options)
throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.unsetDelete(clazz, id, options);
}
}
public static long unsetDeleteWhere(final Class<?> clazz, final QueryOption... options)
throws DataAccessException, IOException {
try (DBAccess db = DBAccess.createInterface()) {
return db.unsetDeleteWhere(clazz, options);
}
}
public static void drop(final Class<?> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
db.drop(clazz, options);
}
}
public static void cleanAll(final Class<?> clazz, final QueryOption... options) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
db.cleanAll(clazz, options);
}
}
}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.io.IOException;
import java.sql.PreparedStatement;
@ -16,13 +16,16 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.kar.archidata.dataAccess.exportTools.TableQuery;
import org.kar.archidata.dataAccess.exportTools.TableQueryTypes;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.DBInterfaceOption;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.DateTools;
import org.atriasoft.archidata.dataAccess.exportTools.TableQuery;
import org.atriasoft.archidata.dataAccess.exportTools.TableQueryTypes;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.GroupBy;
import org.atriasoft.archidata.dataAccess.options.Limit;
import org.atriasoft.archidata.dataAccess.options.OrderBy;
import org.atriasoft.archidata.dataAccess.options.QueryOption;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ContextGenericTools;
import org.atriasoft.archidata.tools.DateTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -242,24 +245,24 @@ public class DataExport {
}
public static TableQuery queryTable(
final DBAccessSQL ioDb,
final List<TableQueryTypes> headers,
final String query,
final List<Object> parameters,
final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return queryTable(headers, query, parameters, options);
return queryTable(ioDb, headers, query, parameters, options);
}
public static TableQuery queryTable(
final DBAccessSQL ioDb,
final List<TableQueryTypes> headers,
final String queryBase,
final List<Object> parameters,
final QueryOptions options) throws Exception {
final List<LazyGetter> lazyCall = new ArrayList<>();
// TODO ... final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
final Condition condition = DataAccess.conditionFusionOrEmpty(options, false);
final Condition condition = ioDb.conditionFusionOrEmpty(options, false);
final StringBuilder query = new StringBuilder(queryBase);
final TableQuery out = new TableQuery(headers);
// real add in the BDD:
@ -282,18 +285,18 @@ public class DataExport {
}
LOGGER.warn("generate the query: '{}'", query.toString());
// prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(),
final PreparedStatement ps = ioDb.getConnection().prepareStatement(query.toString(),
Statement.RETURN_GENERATED_KEYS);
final CountInOut iii = new CountInOut(1);
if (parameters != null) {
for (final Object elem : parameters) {
DataAccess.addElement(ps, elem, iii);
ioDb.addElement(ps, elem, iii);
}
iii.inc();
}
condition.injectQuery(ps, iii);
condition.injectQuery(ioDb, ps, iii);
if (limits.size() == 1) {
limits.get(0).injectQuery(ps, iii);
limits.get(0).injectQuery(ioDb, ps, iii);
}
// execute the request
final ResultSet rs = ps.executeQuery();
@ -328,8 +331,6 @@ public class DataExport {
throw ex;
} catch (final Exception ex) {
ex.printStackTrace();
} finally {
entry.close();
}
return out;
}
@ -391,7 +392,7 @@ public class DataExport {
}
if (MediaType.APPLICATION_JSON.equals(accept)) {
LOGGER.info("Start mapping josn");
final ObjectMapper objectMapper = new ObjectMapper();
final ObjectMapper objectMapper = ContextGenericTools.createObjectMapper();
LOGGER.info("Start find modules josn");
objectMapper.findAndRegisterModules();
LOGGER.info("Start map object");

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.lang.reflect.Field;
import java.sql.Timestamp;
@ -10,13 +10,16 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.CreationTimestamp;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.UpdateTimestamp;
import org.kar.archidata.dataAccess.options.CreateDropTable;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.annotation.AnnotationTools;
import org.atriasoft.archidata.annotation.CreationTimestamp;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.UpdateTimestamp;
import org.atriasoft.archidata.dataAccess.addOnSQL.DataAccessAddOn;
import org.atriasoft.archidata.dataAccess.options.CreateDropTable;
import org.atriasoft.archidata.dataAccess.options.OptionSpecifyType;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -25,13 +28,17 @@ import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.persistence.GenerationType;
public class DataFactory {
static final Logger LOGGER = LoggerFactory.getLogger(DataFactory.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DataFactory.class);
public static String convertTypeInSQL(final Class<?> type, final String fieldName) throws Exception {
if (!"sqlite".equals(ConfigBaseVariable.getDBType())) {
public static String convertTypeInSQL(final Class<?> type, final String fieldName) throws DataAccessException {
final String typelocal = ConfigBaseVariable.getDBType();
if ("mysql".equals(typelocal)) {
if (type == UUID.class) {
return "binary(16)";
}
if (type == ObjectId.class) {
return "binary(12)";
}
if (type == Long.class || type == long.class) {
return "bigint";
}
@ -82,10 +89,13 @@ public class DataFactory {
out.append(")");
return out.toString();
}
} else {
} else if ("sqlite".equals(typelocal)) {
if (type == UUID.class) {
return "BINARY(16)";
}
if (type == ObjectId.class) {
return "BINARY(12)";
}
if (type == Long.class || type == long.class) {
return "INTEGER";
}
@ -138,6 +148,9 @@ public class DataFactory {
out.append(" ) )");
return out.toString();
}
} else if ("mongo".equals(typelocal)) {
// no importance for mango ...
return "text";
}
throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName());
}
@ -152,8 +165,9 @@ public class DataFactory {
final boolean createIfNotExist,
final boolean createDrop,
final int fieldId,
final Class<?> classModel) throws Exception {
final String name = AnnotationTools.getFieldName(elem);
final Class<?> classModel,
final QueryOptions options) throws Exception {
final String name = AnnotationTools.getFieldName(elem, options).inTable();
final int limitSize = AnnotationTools.getLimitSize(elem);
final boolean notNull = AnnotationTools.getColumnNotNull(elem);
@ -162,10 +176,10 @@ public class DataFactory {
final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
final String comment = AnnotationTools.getComment(elem);
final String comment = AnnotationTools.getSchemaDescription(elem);
final String defaultValue = AnnotationTools.getDefault(elem);
if (fieldId == 0) {
if (mainTableBuilder.toString().length() == 0) {
mainTableBuilder.append("\n\t\t`");
} else {
mainTableBuilder.append(",\n\t\t`");
@ -299,7 +313,10 @@ public class DataFactory {
}
}
private static boolean isFieldFromSuperClass(final Class<?> model, final String filedName) {
private static boolean isFieldFromSuperClass(
final Class<?> model,
final String filedName,
final QueryOptions options) {
final Class<?> superClass = model.getSuperclass();
if (superClass == null) {
return false;
@ -307,7 +324,7 @@ public class DataFactory {
for (final Field field : superClass.getFields()) {
String name;
try {
name = AnnotationTools.getFieldName(field);
name = AnnotationTools.getFieldName(field, options).inTable();
if (filedName.equals(name)) {
return true;
}
@ -355,7 +372,7 @@ public class DataFactory {
for (final Field elem : clazz.getFields()) {
// DEtect the primary key (support only one primary key right now...
if (AnnotationTools.isPrimaryKey(elem)) {
primaryKeys.add(AnnotationTools.getFieldName(elem));
primaryKeys.add(AnnotationTools.getFieldName(elem, options).inTable());
}
}
// Here we insert the data in the reverse mode ==> the parent class add there parameter at the start (we reorder the field with the parenting).
@ -372,8 +389,8 @@ public class DataFactory {
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
continue;
}
final String dataName = AnnotationTools.getFieldName(elem);
if (isFieldFromSuperClass(currentClazz, dataName)) {
final String dataName = AnnotationTools.getFieldName(elem, options).inTable();
if (isFieldFromSuperClass(currentClazz, dataName, options)) {
LOGGER.trace(" SKIP: '{}'", elem.getName());
continue;
}
@ -382,28 +399,46 @@ public class DataFactory {
continue;
}
alreadyAdded.add(dataName);
List<OptionSpecifyType> specificTypes = new ArrayList<>();
if (options != null) {
specificTypes = options.get(OptionSpecifyType.class);
}
Class<?> basicType = elem.getType();
if (basicType == Object.class) {
for (final OptionSpecifyType specify : specificTypes) {
if (specify.name.equals(elem.getName())) {
basicType = specify.clazz;
LOGGER.trace("Detect overwrite of typing ... '{}' => '{}'",
elem.getType().getCanonicalName(), specify.clazz.getCanonicalName());
break;
}
}
}
LOGGER.trace(" + '{}'", elem.getName());
if (DataAccess.isAddOnField(elem)) {
final DataAccessAddOn addOn = DataAccess.findAddOnforField(elem);
LOGGER.trace("Create type for: {} ==> {} (ADD-ON)", AnnotationTools.getFieldName(elem),
elem.getType());
if (DBAccessSQL.isAddOnField(elem)) {
final DataAccessAddOn addOn = DBAccessSQL.findAddOnforField(elem);
LOGGER.trace("Create type for: {} ==> {} (ADD-ON)",
AnnotationTools.getFieldName(elem, options).inTable(), basicType);
if (addOn != null) {
addOn.createTables(tableName, primaryField, elem, tmpOut, preActionList, postActionList,
createIfNotExist, createDrop, fieldId);
createIfNotExist, createDrop, fieldId, options);
} else {
throw new DataAccessException("Element matked as add-on but add-on does not loaded: table:"
+ tableName + " field name=" + AnnotationTools.getFieldName(elem) + " type="
+ elem.getType());
+ tableName + " field name=" + AnnotationTools.getFieldName(elem, options).inTable()
+ " type=" + basicType);
}
} else {
LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType());
LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem, options).inTable(),
basicType);
DataFactory.createTablesSpecificType(tableName, tablePrimaryKeyField, elem, tmpOut, preActionList,
postActionList, createIfNotExist, createDrop, fieldId, elem.getType());
postActionList, createIfNotExist, createDrop, fieldId, basicType, options);
}
fieldId++;
}
final boolean dataInThisObject = tmpOut.toString().length() > 0;
if (dataInThisObject) {
LOGGER.info("Previous Object : '{}'", reverseOut.toString());
final boolean dataInPreviousObject = reverseOut.toString().length() > 0;
if (dataInPreviousObject) {
tmpOut.append(", ");
@ -438,4 +473,4 @@ public class DataFactory {
return preActionList;
}
}
}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
// Mark as deprecated while the concept is not ready ...
@Deprecated

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
public interface LazyGetter {
void doRequest() throws Exception;

View File

@ -0,0 +1,66 @@
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryAnd implements QueryItem {
protected final List<QueryItem> childs;
public QueryAnd(final List<QueryItem> child) {
this.childs = child;
}
public QueryAnd(final QueryItem... child) {
this.childs = new ArrayList<>();
Collections.addAll(this.childs, child);
}
public void add(final QueryItem... child) {
Collections.addAll(this.childs, child);
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (this.childs.size() >= 1) {
query.append(" (");
}
boolean first = true;
for (final QueryItem elem : this.childs) {
if (first) {
first = false;
} else {
query.append(" AND ");
}
elem.generateQuery(query, tableName);
}
if (this.childs.size() >= 1) {
query.append(")");
}
}
@Override
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii) throws Exception {
for (final QueryItem elem : this.childs) {
elem.injectQuery(ioDb, ps, iii);
}
}
public int size() {
return this.childs.size();
}
@Override
public void generateFilter(final List<Bson> filters) {
final List<Bson> filtersLocal = new ArrayList<>();
for (final QueryItem elem : this.childs) {
elem.generateFilter(filtersLocal);
}
filters.add(Filters.and(filtersLocal.toArray(new Bson[0])));
}
}

View File

@ -0,0 +1,67 @@
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mongodb.client.model.Filters;
public class QueryCondition implements QueryItem {
static final Logger LOGGER = LoggerFactory.getLogger(DBAccess.class);
private final String key;
private final String comparator;
private final Object value;
/**
* Simple DB comparison element. Note the injected object is injected in the statement and not in the query directly.
* @param key Field to check (the Model property name)
* @param comparator (simple comparator String)
* @param value Value that the field must be equals.
*/
public QueryCondition(final String key, final String comparator, final Object value) {
this.key = key;
this.comparator = comparator;
this.value = value;
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (tableName != null) {
query.append(tableName);
query.append(".");
}
query.append(this.key);
query.append(" ");
query.append(this.comparator);
query.append(" ?");
}
@Override
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii) throws Exception {
ioDb.addElement(ps, this.value, iii);
iii.inc();
}
@Override
public void generateFilter(final List<Bson> filters) {
if ("=".equals(this.comparator)) {
filters.add(Filters.eq(this.key, this.value));
} else if ("!=".equals(this.comparator)) {
filters.add(Filters.ne(this.key, this.value));
} else if (">".equals(this.comparator)) {
filters.add(Filters.gt(this.key, this.value));
} else if (">=".equals(this.comparator)) {
filters.add(Filters.gte(this.key, this.value));
} else if ("<".equals(this.comparator)) {
filters.add(Filters.lt(this.key, this.value));
} else if ("<=".equals(this.comparator)) {
filters.add(Filters.lte(this.key, this.value));
} else {
LOGGER.error("Not manage comparison: '{}'", this.key);
}
}
}

View File

@ -1,8 +1,12 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryInList<T> implements QueryItem {
protected final String key;
protected final String comparator;
@ -44,10 +48,15 @@ public class QueryInList<T> implements QueryItem {
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii) throws Exception {
for (final Object elem : this.value) {
DataAccess.addElement(ps, elem, iii);
ioDb.addElement(ps, elem, iii);
iii.inc();
}
}
@Override
public void generateFilter(final List<Bson> filters) {
filters.add(Filters.in(this.key, this.value));
}
}

View File

@ -0,0 +1,17 @@
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
public interface QueryItem {
// For SQL mode query construction
void generateQuery(StringBuilder query, String tableName);
// For SQL mode query injection
void injectQuery(DBAccessSQL ioDb, PreparedStatement ps, CountInOut iii) throws Exception;
// For No-SQL mode filter creation
void generateFilter(List<Bson> filters);
}

View File

@ -1,4 +1,4 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.util.List;

View File

@ -1,6 +1,11 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryNotNull implements QueryItem {
private final String key;
@ -20,5 +25,11 @@ public class QueryNotNull implements QueryItem {
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {}
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii)
throws Exception {}
@Override
public void generateFilter(final List<Bson> filters) {
filters.add(Filters.exists(this.key));
}
}

View File

@ -0,0 +1,36 @@
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryNull implements QueryItem {
private final String key;
public QueryNull(final String key) {
this.key = key;
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (tableName != null) {
query.append(tableName);
query.append(".");
}
query.append(this.key);
query.append(" IS NULL");
}
@Override
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii)
throws Exception {}
@Override
public void generateFilter(final List<Bson> filters) {
// Not sure of the result ... maybe check it ...
filters.add(Filters.eq(this.key, null));
}
}

View File

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

View File

@ -1,8 +1,13 @@
package org.kar.archidata.dataAccess;
package org.atriasoft.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryOr implements QueryItem {
protected final List<QueryItem> childs;
@ -34,9 +39,18 @@ public class QueryOr implements QueryItem {
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
public void injectQuery(final DBAccessSQL ioDb, final PreparedStatement ps, final CountInOut iii) throws Exception {
for (final QueryItem elem : this.childs) {
elem.injectQuery(ps, iii);
elem.injectQuery(ioDb, ps, iii);
}
}
@Override
public void generateFilter(final List<Bson> filters) {
final List<Bson> filtersLocal = new ArrayList<>();
for (final QueryItem elem : this.childs) {
elem.generateFilter(filtersLocal);
}
filters.add(Filters.or(filtersLocal.toArray(new Bson[0])));
}
}

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