Compare commits

...

11 Commits

Author SHA1 Message Date
d5c2a5f813 [FIX] correct some generic code generation 2025-03-17 19:48:30 +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
27 changed files with 709 additions and 63 deletions

View File

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

View File

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

View File

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

59
pom.xml
View File

@ -4,16 +4,6 @@
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.24.1-SNAPSHOT</version> <version>0.24.1-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version>
<jersey.version>3.1.5</jersey.version>
<jaxb.version>2.3.1</jaxb.version>
<istack.version>4.1.1</istack.version>
</properties>
<repositories> <repositories>
<repository> <repository>
<id>gitea</id> <id>gitea</id>
@ -35,7 +25,7 @@
<dependency> <dependency>
<groupId>org.glassfish.jersey</groupId> <groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId> <artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version> <version>3.1.10</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -58,7 +48,7 @@
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId> <artifactId>imageio-webp</artifactId>
<version>3.11.0</version> <version>3.12.0</version>
</dependency> </dependency>
<!-- Decode JPEG image --> <!-- Decode JPEG image -->
<dependency> <dependency>
@ -95,10 +85,19 @@
<groupId>org.glassfish.jersey.containers</groupId> <groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId> <artifactId>jersey-container-grizzly2-http</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version> <version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>4.0.0-M4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.glassfish.jaxb</groupId> <groupId>org.glassfish.jaxb</groupId>
@ -110,15 +109,10 @@
<artifactId>jakarta.ws.rs-api</artifactId> <artifactId>jakarta.ws.rs-api</artifactId>
<version>4.0.0</version> <version>4.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.sun.istack</groupId> <groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId> <artifactId>istack-commons-runtime</artifactId>
<version>${istack.version}</version> <version>4.1.2</version>
</dependency> </dependency>
<!-- continu to be needed ??? --> <!-- continu to be needed ??? -->
<dependency> <dependency>
@ -168,7 +162,7 @@
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>9.41.1</version> <version>10.0.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.persistence</groupId> <groupId>jakarta.persistence</groupId>
@ -198,13 +192,13 @@
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>4.3.0</version> <version>5.1.4</version>
</dependency> </dependency>
<!-- Bean Validation (JSR 303 / 380) --> <!-- Bean Validation (JSR 303 / 380) -->
<dependency> <dependency>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
<version>7.0.0.Final</version> <version>8.0.2.Final</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>javax.validation</groupId>
@ -236,7 +230,7 @@
<dependency> <dependency>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version> <version>3.6.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
@ -256,18 +250,17 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version> <version>3.14.0</version>
<configuration> <configuration>
<source>${maven.compiler.source}</source> <source>21</source>
<target>${maven.compiler.target}</target> <target>21</target>
<!--<encoding>${project.build.sourceEncoding}</encoding>-->
</configuration> </configuration>
</plugin> </plugin>
<!-- Create the source bundle --> <!-- Create the source bundle -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version> <version>3.3.1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@ -295,7 +288,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.2.5</version>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
@ -314,7 +307,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>private</show> <show>private</show>
<nohelp>true</nohelp> <nohelp>true</nohelp>
@ -324,7 +317,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version> <version>3.6.0</version>
<configuration> <configuration>
<configLocation>CheckStyle.xml</configLocation> <configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput> <consoleOutput>true</consoleOutput>
@ -336,7 +329,7 @@
<plugin> <plugin>
<groupId>net.revelc.code.formatter</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version> <version>2.24.1</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding> <lineEnding>LF</lineEnding>
@ -385,7 +378,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>public</show> <show>public</show>
</configuration> </configuration>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,11 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -20,7 +26,22 @@ public class ConstraintViolationExceptionCatcher implements ExceptionMapper<Cons
} }
private RestErrorResponse build(final ConstraintViolationException exception) { private RestErrorResponse build(final ConstraintViolationException exception) {
return new RestErrorResponse(Response.Status.BAD_REQUEST, "Constraint Violation", exception.getMessage()); 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,10 +1,12 @@
package org.kar.archidata.catcher; package org.kar.archidata.catcher;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
@ -27,6 +29,19 @@ public class RestErrorResponse {
@Column(length = 0) @Column(length = 0)
final public String statusMessage; 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, public RestErrorResponse(final Response.Status status, final String time, final String error,
final String message) { final String message) {
this.time = time; this.time = time;
@ -34,6 +49,7 @@ public class RestErrorResponse {
this.message = message; this.message = message;
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
public RestErrorResponse(final Response.Status status, final String error, final String message) { public RestErrorResponse(final Response.Status status, final String error, final String message) {
@ -42,6 +58,7 @@ public class RestErrorResponse {
this.message = message; this.message = message;
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
public RestErrorResponse(final Response.Status status) { public RestErrorResponse(final Response.Status status) {
@ -50,6 +67,7 @@ public class RestErrorResponse {
this.time = Instant.now().toString(); this.time = Instant.now().toString();
this.status = status.getStatusCode(); this.status = status.getStatusCode();
this.statusMessage = status.getReasonPhrase(); this.statusMessage = status.getReasonPhrase();
this.inputError = null;
} }
} }

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@ -36,6 +37,7 @@ import jakarta.ws.rs.DefaultValue;
@Table(name = "user") @Table(name = "user")
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class User extends GenericDataSoftDelete { public class User extends GenericDataSoftDelete {
@NotNull @NotNull
@Column(length = 128) @Column(length = 128)

View File

@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
@ -170,14 +171,21 @@ public class RESTApi {
final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(), final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(),
RESTErrorResponseException.class); RESTErrorResponseException.class);
throw out; throw out;
} catch (final InvalidDefinitionException ex) {
ex.printStackTrace();
LOGGER.error("body: {}", httpResponse.body());
throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
+ httpResponse.statusCode() + "] " + httpResponse.body());
} catch (final MismatchedInputException ex) { } catch (final MismatchedInputException ex) {
throw new IOException( ex.printStackTrace();
"Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); LOGGER.error("body: {}", httpResponse.body());
throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
+ httpResponse.statusCode() + "] " + httpResponse.body());
} catch (final JsonParseException ex) { } catch (final JsonParseException ex) {
ex.printStackTrace(); ex.printStackTrace();
LOGGER.error("body: {}", httpResponse.body()); LOGGER.error("body: {}", httpResponse.body());
throw new IOException( throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " ["
"Fail to get the ERROR data [" + httpResponse.statusCode() + "] " + httpResponse.body()); + httpResponse.statusCode() + "] " + httpResponse.body());
} }
} }
if (clazz == Void.class || clazz == void.class) { if (clazz == Void.class || clazz == void.class) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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