diff --git a/pom.xml b/pom.xml index f7ebc14..7466fae 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,10 @@ org.glassfish.jersey.containers jersey-container-grizzly2-http + + org.glassfish.jersey.ext + jersey-bean-validation + javax.xml.bind jaxb-api @@ -246,7 +250,7 @@ org.apache.maven.plugins maven-compiler-plugin - 4.0.0-beta-2 + 3.14.0 21 21 diff --git a/src/org/kar/archidata/annotation/checker/CheckForeignKey.java b/src/org/kar/archidata/annotation/checker/CheckForeignKey.java index b1550ea..cc393af 100644 --- a/src/org/kar/archidata/annotation/checker/CheckForeignKey.java +++ b/src/org/kar/archidata/annotation/checker/CheckForeignKey.java @@ -5,8 +5,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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) public @interface CheckForeignKey { Class target(); + + String message() default "Foreign-key does not exist in the DB"; + + Class[] groups() default {}; + + Class[] payload() default {}; } diff --git a/src/org/kar/archidata/annotation/checker/CheckForeignKeyValidator.java b/src/org/kar/archidata/annotation/checker/CheckForeignKeyValidator.java new file mode 100644 index 0000000..4ba36b2 --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/CheckForeignKeyValidator.java @@ -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 { + 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; + } +} \ No newline at end of file diff --git a/src/org/kar/archidata/annotation/checker/CollectionItemNotNull.java b/src/org/kar/archidata/annotation/checker/CollectionItemNotNull.java index 77b9c2c..bf2589c 100644 --- a/src/org/kar/archidata/annotation/checker/CollectionItemNotNull.java +++ b/src/org/kar/archidata/annotation/checker/CollectionItemNotNull.java @@ -5,8 +5,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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) public @interface CollectionItemNotNull { + String message() default "Collection can not contain NULL item"; + Class[] groups() default {}; + + Class[] payload() default {}; } diff --git a/src/org/kar/archidata/annotation/checker/CollectionItemNotNullValidator.java b/src/org/kar/archidata/annotation/checker/CollectionItemNotNullValidator.java new file mode 100644 index 0000000..a6fe8b3 --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/CollectionItemNotNullValidator.java @@ -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 { + + @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; + } +} \ No newline at end of file diff --git a/src/org/kar/archidata/annotation/checker/CollectionItemUnique.java b/src/org/kar/archidata/annotation/checker/CollectionItemUnique.java index 3b905e2..7a4c5de 100644 --- a/src/org/kar/archidata/annotation/checker/CollectionItemUnique.java +++ b/src/org/kar/archidata/annotation/checker/CollectionItemUnique.java @@ -5,8 +5,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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) public @interface CollectionItemUnique { + String message() default "Cannot insert multiple times the same elements"; + + Class[] groups() default {}; + + Class[] payload() default {}; } diff --git a/src/org/kar/archidata/annotation/checker/CollectionItemUniqueValidator.java b/src/org/kar/archidata/annotation/checker/CollectionItemUniqueValidator.java new file mode 100644 index 0000000..a93701a --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/CollectionItemUniqueValidator.java @@ -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 { + + @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 uniqueValues = new HashSet<>(tmpCollection); + if (uniqueValues.size() != tmpCollection.size()) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/org/kar/archidata/annotation/checker/CollectionNotEmpty.java b/src/org/kar/archidata/annotation/checker/CollectionNotEmpty.java index 9560363..85dc039 100644 --- a/src/org/kar/archidata/annotation/checker/CollectionNotEmpty.java +++ b/src/org/kar/archidata/annotation/checker/CollectionNotEmpty.java @@ -5,8 +5,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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) public @interface CollectionNotEmpty { + String message() default "Collection can not be empty"; + + Class[] groups() default {}; + + Class[] payload() default {}; } diff --git a/src/org/kar/archidata/annotation/checker/CollectionNotEmptyValidator.java b/src/org/kar/archidata/annotation/checker/CollectionNotEmptyValidator.java new file mode 100644 index 0000000..a55d359 --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/CollectionNotEmptyValidator.java @@ -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 { + + @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; + } +} \ No newline at end of file diff --git a/src/org/kar/archidata/annotation/checker/ReadOnlyField.java b/src/org/kar/archidata/annotation/checker/ReadOnlyField.java new file mode 100644 index 0000000..d935496 --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/ReadOnlyField.java @@ -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[] payload() default {}; +} \ No newline at end of file diff --git a/src/org/kar/archidata/annotation/checker/ReadOnlyFieldValidator.java b/src/org/kar/archidata/annotation/checker/ReadOnlyFieldValidator.java new file mode 100644 index 0000000..b86ce12 --- /dev/null +++ b/src/org/kar/archidata/annotation/checker/ReadOnlyFieldValidator.java @@ -0,0 +1,20 @@ +package org.kar.archidata.annotation.checker; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class ReadOnlyFieldValidator implements ConstraintValidator { + + @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; + } +} \ No newline at end of file diff --git a/src/org/kar/archidata/catcher/ConstraintViolationExceptionCatcher.java b/src/org/kar/archidata/catcher/ConstraintViolationExceptionCatcher.java index 6e3f520..2978a70 100644 --- a/src/org/kar/archidata/catcher/ConstraintViolationExceptionCatcher.java +++ b/src/org/kar/archidata/catcher/ConstraintViolationExceptionCatcher.java @@ -1,5 +1,11 @@ package org.kar.archidata.catcher; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +26,22 @@ public class ConstraintViolationExceptionCatcher implements ExceptionMapper 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); } } diff --git a/src/org/kar/archidata/catcher/RestErrorResponse.java b/src/org/kar/archidata/catcher/RestErrorResponse.java index 5dd7043..68182f4 100644 --- a/src/org/kar/archidata/catcher/RestErrorResponse.java +++ b/src/org/kar/archidata/catcher/RestErrorResponse.java @@ -1,6 +1,7 @@ package org.kar.archidata.catcher; import java.time.Instant; +import java.util.List; import org.bson.types.ObjectId; import org.kar.archidata.annotation.apiGenerator.ApiGenerationMode; @@ -27,6 +28,18 @@ public class RestErrorResponse { @Column(length = 0) final public String statusMessage; + final public List inputError; + + public RestErrorResponse(final Response.Status status, final String time, final String error, final String message, + final List inputError) { + this.time = time; + this.name = error; + this.message = message; + this.status = status.getStatusCode(); + this.statusMessage = status.getReasonPhrase(); + this.inputError = inputError; + } + public RestErrorResponse(final Response.Status status, final String time, final String error, final String message) { this.time = time; @@ -34,6 +47,7 @@ public class RestErrorResponse { this.message = message; this.status = status.getStatusCode(); this.statusMessage = status.getReasonPhrase(); + this.inputError = null; } public RestErrorResponse(final Response.Status status, final String error, final String message) { @@ -42,6 +56,7 @@ public class RestErrorResponse { this.message = message; this.status = status.getStatusCode(); this.statusMessage = status.getReasonPhrase(); + this.inputError = null; } public RestErrorResponse(final Response.Status status) { @@ -50,6 +65,7 @@ public class RestErrorResponse { this.time = Instant.now().toString(); this.status = status.getStatusCode(); this.statusMessage = status.getReasonPhrase(); + this.inputError = null; } } diff --git a/src/org/kar/archidata/catcher/RestInputError.java b/src/org/kar/archidata/catcher/RestInputError.java new file mode 100644 index 0000000..3e53a9c --- /dev/null +++ b/src/org/kar/archidata/catcher/RestInputError.java @@ -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; + } +} diff --git a/src/org/kar/archidata/exception/RESTErrorResponseException.java b/src/org/kar/archidata/exception/RESTErrorResponseException.java index 3cf09c5..7ca033b 100644 --- a/src/org/kar/archidata/exception/RESTErrorResponseException.java +++ b/src/org/kar/archidata/exception/RESTErrorResponseException.java @@ -1,6 +1,9 @@ package org.kar.archidata.exception; +import java.util.List; + import org.bson.types.ObjectId; +import org.kar.archidata.catcher.RestInputError; public class RESTErrorResponseException extends Exception { private static final long serialVersionUID = 1L; @@ -10,6 +13,7 @@ public class RESTErrorResponseException extends Exception { public String message; public int status; public String statusMessage; + public List inputError; public RESTErrorResponseException() { this.oid = new ObjectId(); @@ -18,16 +22,18 @@ public class RESTErrorResponseException extends Exception { this.message = null; this.status = 0; this.statusMessage = null; + this.inputError = null; } public RESTErrorResponseException(final ObjectId oid, final String time, final String name, final String message, - final int status, final String statusMessage) { + final int status, final String statusMessage, final List inputError) { this.oid = oid; this.time = time; this.name = name; this.message = message; this.status = status; this.statusMessage = statusMessage; + this.inputError = inputError; } @Override diff --git a/src/org/kar/archidata/tools/RESTApi.java b/src/org/kar/archidata/tools/RESTApi.java index 69dbc60..5a145cf 100644 --- a/src/org/kar/archidata/tools/RESTApi.java +++ b/src/org/kar/archidata/tools/RESTApi.java @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import jakarta.ws.rs.core.HttpHeaders; @@ -170,14 +171,21 @@ public class RESTApi { final RESTErrorResponseException out = this.mapper.readValue(httpResponse.body(), RESTErrorResponseException.class); 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) { - throw new IOException( - "Fail to get the data [" + httpResponse.statusCode() + "] " + httpResponse.body()); + 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 JsonParseException ex) { ex.printStackTrace(); LOGGER.error("body: {}", httpResponse.body()); - throw new IOException( - "Fail to get the ERROR data [" + httpResponse.statusCode() + "] " + httpResponse.body()); + throw new IOException("RestAPI Fail to parse the error " + ex.getClass().getName() + " [" + + httpResponse.statusCode() + "] " + httpResponse.body()); } } if (clazz == Void.class || clazz == void.class) { diff --git a/test/src/test/kar/archidata/apiExtern/TestTime.java b/test/src/test/kar/archidata/apiExtern/TestTime.java index 7f18386..1aaf2e0 100644 --- a/test/src/test/kar/archidata/apiExtern/TestTime.java +++ b/test/src/test/kar/archidata/apiExtern/TestTime.java @@ -30,8 +30,6 @@ public class TestTime { static WebLauncherTest webInterface = null; static RESTApi api = null; - private static Long idTest = 0L; - @BeforeAll public static void configureWebServer() throws Exception { ConfigureDb.configure(); diff --git a/test/src/test/kar/archidata/hybernateValidator/TestValidator.java b/test/src/test/kar/archidata/hybernateValidator/TestValidator.java new file mode 100644 index 0000000..2493cf8 --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/TestValidator.java @@ -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); + } +} diff --git a/test/src/test/kar/archidata/hybernateValidator/WebLauncher.java b/test/src/test/kar/archidata/hybernateValidator/WebLauncher.java new file mode 100755 index 0000000..7b0c9d1 --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/WebLauncher.java @@ -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 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 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(); + } + } +} diff --git a/test/src/test/kar/archidata/hybernateValidator/WebLauncherTest.java b/test/src/test/kar/archidata/hybernateValidator/WebLauncherTest.java new file mode 100755 index 0000000..363a3a9 --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/WebLauncherTest.java @@ -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"; + } +} diff --git a/test/src/test/kar/archidata/hybernateValidator/model/ValidatorModel.java b/test/src/test/kar/archidata/hybernateValidator/model/ValidatorModel.java new file mode 100644 index 0000000..cc6804a --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/model/ValidatorModel.java @@ -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 multipleElement; + + @Valid + public ValidatorSubModel subElement; +} diff --git a/test/src/test/kar/archidata/hybernateValidator/model/ValidatorSubModel.java b/test/src/test/kar/archidata/hybernateValidator/model/ValidatorSubModel.java new file mode 100644 index 0000000..93b5ef8 --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/model/ValidatorSubModel.java @@ -0,0 +1,8 @@ +package test.kar.archidata.hybernateValidator.model; + +import jakarta.validation.constraints.Size; + +public class ValidatorSubModel { + @Size(min = 2) + public String data; +} diff --git a/test/src/test/kar/archidata/hybernateValidator/resource/TestResourceValidator.java b/test/src/test/kar/archidata/hybernateValidator/resource/TestResourceValidator.java new file mode 100644 index 0000000..66bfe28 --- /dev/null +++ b/test/src/test/kar/archidata/hybernateValidator/resource/TestResourceValidator.java @@ -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; + } + +}