diff --git a/CHANGELOG.md b/CHANGELOG.md index 05db80d..9cd1f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,19 @@ -* 4.1.1.SNAPSHOT.20220000: +* 4.3.0.SNAPSHOT.20220829: + * **VALIDATION** : + * **BUG FIX** :raised_bug: : Filter primitive values for not validate. + * **GENERAL** : + * **IMPROVEMENT** :raised: : Created Api UC & Api Controllee, an UC and a Controller wich return ApiResponse by default. + * **IMPROVEMENT** :raised: : Find by return null if id not found. + * **IMPROVEMENT** :raised: : Created builder factory in ApiResponse for message. + * **IMPROVEMENT** :raised: : Created ResponseExtractor. + * **IMPROVEMENT** :raised: : Created Validation for Enum values. + * **IMPROVEMENT** :raised: : Upgrade gradle dependencies. + +* 4.2.2.RELEASE.20220613: + * **VALIDATION** : + * **IMPROVEMENT** :raised_hands: : Add recursive validation. + +* 4.1.1.SNAPSHOT.20220531: * **GENERAL** : * **IMPROVEMENT** :raised: : Change `count()` to type `long`. diff --git a/build.gradle b/build.gradle index 3c1f06f..1a81493 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ plugins { - id 'java' + id 'java' } group = 'dev.root101.clean' -version = '4.2.2.RELEASE.20220613' +version = '4.3.0.SNAPSHOT.20220829' repositories { jcenter() @@ -18,15 +18,17 @@ dependencies{ //javax.validation.NoProviderFoundException: Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath. implementation 'org.hibernate.validator:hibernate-validator:6.2.3.Final' - //integration with spring - implementation 'org.springframework:spring-web:5.3.20' - implementation 'org.springframework:spring-webmvc:5.3.20' - implementation 'org.springframework:spring-context:5.3.20' - //repo - implementation 'org.springframework.data:spring-data-jpa:2.7.0' + //spring + implementation 'org.springframework.boot:spring-boot-starter-web:2.7.3' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.3' //Jackson implementation 'com.fasterxml.jackson.core:jackson-core:2.13.3' - implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.3' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.3' + + //lombok + implementation 'org.projectlombok:lombok:1.18.24' + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' } \ No newline at end of file diff --git a/src/main/java/dev/root101/clean/core/app/usecase/ApiCRUDUseCase.java b/src/main/java/dev/root101/clean/core/app/usecase/ApiCRUDUseCase.java new file mode 100644 index 0000000..0a4111d --- /dev/null +++ b/src/main/java/dev/root101/clean/core/app/usecase/ApiCRUDUseCase.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Or read it directly from LICENCE.txt file at the root of this project. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.root101.clean.core.app.usecase; + +import dev.root101.clean.core.app.domain.DomainObject; +import dev.root101.clean.core.framework.ApiResponse; +import java.util.List; + +/** + * + * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) + * @author JesusHdezWaterloo@Github + * @param + * @param + */ +public interface ApiCRUDUseCase, ID> extends AbstractUseCase { + + public ApiResponse create(Domain newObject) throws RuntimeException; + + public ApiResponse edit(Domain objectToEdit) throws RuntimeException; + + public ApiResponse destroy(Domain objectToDestroy) throws RuntimeException; + + public ApiResponse destroyById(ID keyId) throws RuntimeException; + + public ApiResponse findBy(ID keyId) throws RuntimeException; + + public ApiResponse> findAll() throws RuntimeException; + + /** + * By default return the size of the findAll() list. + * + * @return findAll().size() + * @throws RuntimeException + */ + public default ApiResponse count() throws RuntimeException { + return ApiResponse.buildSucces( + (long) findAll().getData().size() + ); + } + +} diff --git a/src/main/java/dev/root101/clean/core/app/usecase/ApiDefaultCRUDUseCase.java b/src/main/java/dev/root101/clean/core/app/usecase/ApiDefaultCRUDUseCase.java new file mode 100644 index 0000000..e8b3e0d --- /dev/null +++ b/src/main/java/dev/root101/clean/core/app/usecase/ApiDefaultCRUDUseCase.java @@ -0,0 +1,63 @@ +package dev.root101.clean.core.app.usecase; + +import dev.root101.clean.core.app.domain.DomainObject; +import dev.root101.clean.core.framework.ApiResponse; +import dev.root101.clean.core.repo.CRUDRepository; +import java.util.List; + +public class ApiDefaultCRUDUseCase, ID, CRUDRepo extends CRUDRepository> implements ApiCRUDUseCase { + + private final DefaultCRUDUseCase crudUC; + + public ApiDefaultCRUDUseCase(CRUDRepo crudRepo) { + this.crudUC = new DefaultCRUDUseCase(crudRepo); + } + + @Override + public ApiResponse create(Domain newObject) throws RuntimeException { + return ApiResponse.buildSucces( + crudUC.create(newObject) + ); + } + + @Override + public ApiResponse edit(Domain objectToEdit) throws RuntimeException { + return ApiResponse.buildSucces( + crudUC.edit(objectToEdit) + ); + } + + @Override + public ApiResponse destroy(Domain objectToDestroy) throws RuntimeException { + crudUC.destroy(objectToDestroy); + return ApiResponse.buildSuccesVoid(); + } + + @Override + public ApiResponse destroyById(ID keyId) throws RuntimeException { + crudUC.destroyById(keyId); + return ApiResponse.buildSuccesVoid(); + } + + @Override + public ApiResponse findBy(ID keyId) throws RuntimeException { + return ApiResponse.buildSucces( + crudUC.findBy(keyId) + ); + } + + @Override + public ApiResponse> findAll() throws RuntimeException { + return ApiResponse.buildSucces( + crudUC.findAll() + ); + } + + @Override + public ApiResponse count() throws RuntimeException { + return ApiResponse.buildSucces( + crudUC.count() + ); + } + +} diff --git a/src/main/java/dev/root101/clean/core/exceptions/ApiException.java b/src/main/java/dev/root101/clean/core/exceptions/ApiException.java index a880de0..ab32ba7 100644 --- a/src/main/java/dev/root101/clean/core/exceptions/ApiException.java +++ b/src/main/java/dev/root101/clean/core/exceptions/ApiException.java @@ -25,20 +25,37 @@ */ public class ApiException extends RuntimeException { - private final HttpStatus status; + private final int rawStatusCode; + private final String message; public ApiException(HttpStatus status) { super(status.getReasonPhrase()); - this.status = status; + this.rawStatusCode = status.value(); + this.message = status.getReasonPhrase(); } public ApiException(HttpStatus status, String message) { super(message); - this.status = status; + this.rawStatusCode = status.value(); + this.message = message; + } + + public ApiException(int rawStatusCode, String message) { + super(message); + this.rawStatusCode = rawStatusCode; + this.message = message; } public HttpStatus status() { - return status; + return HttpStatus.resolve(rawStatusCode); + } + + public int getRawStatusCode() { + return rawStatusCode; + } + + public String getReasonPhrase() { + return message; } } diff --git a/src/main/java/dev/root101/clean/core/framework/ApiResponse.java b/src/main/java/dev/root101/clean/core/framework/ApiResponse.java index 568faaf..e22944b 100644 --- a/src/main/java/dev/root101/clean/core/framework/ApiResponse.java +++ b/src/main/java/dev/root101/clean/core/framework/ApiResponse.java @@ -23,6 +23,11 @@ public static ApiResponse buildSuccesVoid() { public static ApiResponse buildSucces(T data) { return new ApiResponse<>("200", "Success", data); } + + public static ApiResponse buildSucces(String msg, T data) { + return new ApiResponse<>("200", msg, data); + } + private String status; private String msg; private T data; diff --git a/src/main/java/dev/root101/clean/core/framework/ResponseExtractor.java b/src/main/java/dev/root101/clean/core/framework/ResponseExtractor.java new file mode 100644 index 0000000..7e913ab --- /dev/null +++ b/src/main/java/dev/root101/clean/core/framework/ResponseExtractor.java @@ -0,0 +1,165 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package dev.root101.clean.core.framework; + +import dev.root101.clean.core.exceptions.ApiException; +import dev.root101.clean.core.exceptions.InternalServerErrorException; +import java.util.function.Function; +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.UnknownHttpStatusCodeException; + +/** + * + * @author Yo + * @param + */ +@Slf4j +public class ResponseExtractor { + + private static boolean logs = true; + + // ----------------------- Factory builder ----------------------- \\ + public static ResponseExtractor build(Supplier> suplier) { + return new ResponseExtractor<>(suplier); + } + + public static T extract(Supplier> suplier) { + return new ResponseExtractor<>(suplier).extract(); + } + + // ----------------------- Variables ----------------------- \\ + private final Supplier> suplier; + + private Function, T> onOkResponseCode = (response) -> { + if (logs) { + log.info(response.toString()); + } + return response.getBody(); + }; + + private Function, T> onNotOkResponseCode = (response) -> { + if (logs) { + log.info(response.toString()); + } + throw new ApiException(response.getStatusCode(), response.getStatusCode().getReasonPhrase()); + }; + + private Function onHttpStatusCodeException = (exc) -> { + if (logs) { + log.error(exc.toString()); + } + throw new ApiException(exc.getStatusCode(), exc.getStatusCode().getReasonPhrase()); + }; + + private Function onApiException = (apiExc) -> { + if (logs) { + log.error(apiExc.toString()); + } + throw apiExc; + }; + + private Function onUnknownHttpStatusCodeException = (unk) -> { + if (logs) { + log.error(unk.toString()); + } + throw new ApiException(unk.getRawStatusCode(), unk.getResponseBodyAsString()); + }; + + private Function onGeneralException = (exc) -> { + if (logs) { + log.error(exc.toString()); + } + throw new InternalServerErrorException("Unknown error. Contact support."); + }; + + // ----------------------- Constructor ----------------------- \\ + public ResponseExtractor(Supplier> suplier) { + this.suplier = suplier; + } + + // ----------------------- Extract Logic ----------------------- \\ + /** + *
+     * Flujo:
+     * 0 - try{}catch(){} para mitigar todos los errores.
+     * 1 - Ejecuta como tal la peticion, almacena el response.
+     * 2 - Valida si la peticion fue 200 ok
+     *  2.1 - Si la peticion fue 200 revuelve el body, el contenido real de la peticion
+     *  2.2 - Si no fue 200 lanza un ApiException con el mismo codigo/mensaje que el error original
+     *
+     * catch:
+     * 1 - ApiException: Si la peticion no es codigo 200 se lanza ApiException, que  se captura y se relanza, esto de primero para que el ultimo catch(Exception) no lo coja
+     * 2 - HttpStatusCodeException: Literal cualquier error que Status Code que puede dar spring, tanto Client como Server.
+     * 3 - UnknownHttpStatusCodeException: Otro error raro que lanza spring si no reconoce el error.4
+     * 4 - Exception: super generico para que no se vaya nada.
+     * 
+ * + * @return + */ + public T extract() { + //step 0 + try { + //step 1 + ResponseEntity response = suplier.get(); + + //step 2 + if (response.getStatusCodeValue() == HttpStatus.OK.value()) { + //step 2.1 + return onOkResponseCode.apply(response); + } else { + //step 2.2 + return onNotOkResponseCode.apply(response); + } + } catch (ApiException apiException) {//catch 1 + return onApiException.apply(apiException); + } catch (org.springframework.web.client.HttpStatusCodeException httpStatusCodeException) {//catch 2 + return onHttpStatusCodeException.apply(httpStatusCodeException); + } catch (org.springframework.web.client.UnknownHttpStatusCodeException unknownHttpStatusCodeException) {//catch 3 + return onUnknownHttpStatusCodeException.apply(unknownHttpStatusCodeException); + } catch (Exception generalException) {//catch 4 + return onGeneralException.apply(generalException); + } + } + + // ----------------------- Builder Setters ----------------------- \\ + public ResponseExtractor onHttpStatusCodeException(Function onHttpStatusCodeException) { + this.onHttpStatusCodeException = onHttpStatusCodeException; + return this; + } + + public ResponseExtractor onApiException(Function onApiException) { + this.onApiException = onApiException; + return this; + } + + public ResponseExtractor onUnknownHttpStatusCodeException(Function onUnknownHttpStatusCodeException) { + this.onUnknownHttpStatusCodeException = onUnknownHttpStatusCodeException; + return this; + } + + public ResponseExtractor onGeneralException(Function onGeneralException) { + this.onGeneralException = onGeneralException; + return this; + } + + public ResponseExtractor onOkResponseCode(Function, T> onOkResponseCode) { + this.onOkResponseCode = onOkResponseCode; + return this; + } + + public ResponseExtractor onNotOkResponseCode(Function, T> onNotOkResponseCode) { + this.onNotOkResponseCode = onNotOkResponseCode; + return this; + } + + // ----------------------- Config Setters ----------------------- \\ + public static void info(boolean info) { + ResponseExtractor.logs = info; + } +} diff --git a/src/main/java/dev/root101/clean/core/framework/spring_data/DefaultJPARepository.java b/src/main/java/dev/root101/clean/core/framework/spring_data/DefaultJPARepository.java index 2206654..3dc397b 100644 --- a/src/main/java/dev/root101/clean/core/framework/spring_data/DefaultJPARepository.java +++ b/src/main/java/dev/root101/clean/core/framework/spring_data/DefaultJPARepository.java @@ -6,6 +6,7 @@ import dev.root101.clean.core.repo.external_repo.CRUDExternalRepository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; /** @@ -49,7 +50,8 @@ public void destroyById(ID keyId) throws RuntimeException { @Override public Entity findBy(ID keyId) throws RuntimeException { - return repo.findById(keyId).get(); + Optional finded = repo.findById(keyId); + return finded.isPresent() ? finded.get() : null; } @Override diff --git a/src/main/java/dev/root101/clean/core/framework/spring_web/rest/ApiCRUDRestServiceTemplate.java b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/ApiCRUDRestServiceTemplate.java new file mode 100644 index 0000000..59fa6ad --- /dev/null +++ b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/ApiCRUDRestServiceTemplate.java @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Or read it directly from LICENCE.txt file at the root of this project. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.root101.clean.core.framework.spring_web.rest; + +import dev.root101.clean.core.app.domain.DomainObject; +import dev.root101.clean.core.app.usecase.ApiCRUDUseCase; +import dev.root101.clean.core.framework.ApiResponse; +import dev.root101.clean.core.rest.ApiCRUDRestService; +import java.util.List; +import org.springframework.web.bind.annotation.*; + +/** + * + * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) + * @author JesusHdezWaterloo@Github + * @param + * @param + * @param + */ +public class ApiCRUDRestServiceTemplate, ID, UseCase extends ApiCRUDUseCase> implements ApiCRUDRestService { + + private final boolean doFirePropertyChanges = false;//for the moment allways disabled + protected transient final java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + protected final UseCase crudUC; + + public ApiCRUDRestServiceTemplate(UseCase crudUc) { + this.crudUC = crudUc; + } + + protected ApiCRUDUseCase useCase() { + return crudUC; + } + + @Override + @PostMapping(path = RESTUrlConstants.CREATE_PATH) + public ApiResponse create(@RequestBody Domain domain) throws RuntimeException { + return crudUC.create(domain); + } + + @Override + @PostMapping(RESTUrlConstants.EDIT_PATH) + public ApiResponse edit(@RequestBody Domain domain) throws RuntimeException { + return crudUC.edit(domain); + } + + @Override + @DeleteMapping(RESTUrlConstants.DESTROY_PATH) + public ApiResponse destroy(@RequestBody Domain t) throws RuntimeException { + return crudUC.destroy(t); + } + + @Override + @DeleteMapping(RESTUrlConstants.DESTROY_ID_PATH) + public ApiResponse destroyById(@PathVariable(RESTUrlConstants.ID) ID id) throws RuntimeException { + return crudUC.destroyById(id); + } + + @Override + @GetMapping(RESTUrlConstants.FIND_ALL_PATH) + public ApiResponse> findAll() throws RuntimeException { + return crudUC.findAll(); + } + + @Override + @GetMapping(RESTUrlConstants.FIND_BY_PATH) + public ApiResponse findBy(@PathVariable(RESTUrlConstants.ID) ID id) throws RuntimeException { + return crudUC.findBy(id); + } + + @Override + @GetMapping(RESTUrlConstants.COUNT_PATH) + public ApiResponse count() throws RuntimeException { + return crudUC.count(); + } + + @Override + public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + if (doFirePropertyChanges) { + propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); + } + } + +} diff --git a/src/main/java/dev/root101/clean/core/framework/spring_web/rest/CRUDRestServiceTemplate.java b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/CRUDRestServiceTemplate.java index d6ed2ff..43b210e 100644 --- a/src/main/java/dev/root101/clean/core/framework/spring_web/rest/CRUDRestServiceTemplate.java +++ b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/CRUDRestServiceTemplate.java @@ -37,8 +37,8 @@ public class CRUDRestServiceTemplate, ID, UseCas protected final UseCase crudUC; - public CRUDRestServiceTemplate(UseCase crudRepo) { - this.crudUC = crudRepo; + public CRUDRestServiceTemplate(UseCase crudUc) { + this.crudUC = crudUc; } protected CRUDUseCase useCase() { @@ -65,7 +65,7 @@ public void destroy(@RequestBody Domain t) throws RuntimeException { @Override @PostMapping(RESTUrlConstants.DESTROY_ID_PATH) - public void destroyById(@RequestBody ID id) throws RuntimeException { + public void destroyById(@PathVariable(RESTUrlConstants.ID) ID id) throws RuntimeException { crudUC.destroyById(id); } diff --git a/src/main/java/dev/root101/clean/core/framework/spring_web/rest/RESTUrlConstants.java b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/RESTUrlConstants.java index 446b466..b4d4ea4 100644 --- a/src/main/java/dev/root101/clean/core/framework/spring_web/rest/RESTUrlConstants.java +++ b/src/main/java/dev/root101/clean/core/framework/spring_web/rest/RESTUrlConstants.java @@ -25,6 +25,8 @@ */ public class RESTUrlConstants { + public static final String ID = "id"; + public static final String CREATE_PATH = "/create"; public static final RequestMethod CREATE_METHOD = RequestMethod.POST; @@ -34,13 +36,12 @@ public class RESTUrlConstants { public static final String DESTROY_PATH = "/destroy"; public static final RequestMethod DESTROY_METHOD = RequestMethod.POST; - public static final String DESTROY_ID_PATH = "/destroy_id"; + public static final String DESTROY_ID_PATH = "/destroy_id/{" + ID + "}"; public static final RequestMethod DESTROY_ID_METHOD = RequestMethod.POST; public static final String FIND_ALL_PATH = "/find_all"; public static final RequestMethod DFIND_ALL_METHOD = RequestMethod.GET; - public static final String ID = "id"; public static final String FIND_BY_PATH = "/find_by/{" + ID + "}"; public static final RequestMethod FIND_BY_METHOD = RequestMethod.GET; diff --git a/src/main/java/dev/root101/clean/core/repo/DefaultCRUDRepo.java b/src/main/java/dev/root101/clean/core/repo/DefaultCRUDRepo.java index d6b94e9..c9b90b9 100644 --- a/src/main/java/dev/root101/clean/core/repo/DefaultCRUDRepo.java +++ b/src/main/java/dev/root101/clean/core/repo/DefaultCRUDRepo.java @@ -120,23 +120,37 @@ public Domain findBy(ID keyId) throws RuntimeException { //do the findBy, returned the entity Entity entity = externalRepo.findBy(keyId); + //check if entity exists + if (entity == null) { + firePropertyChange(AFTER_FIND_BY, null, null); + + return null; + } + //convert the domain back - Domain objectDestroyed = converter.toDomain(entity); + Domain objectFinded = converter.toDomain(entity); - firePropertyChange(AFTER_FIND_BY, null, objectDestroyed); + firePropertyChange(AFTER_FIND_BY, null, objectFinded); - return objectDestroyed; + return objectFinded; } @Override public List findAll() throws RuntimeException { firePropertyChange(BEFORE_FIND_ALL, null, null); - List d = converter.toDomainAll(externalRepo.findAll()); + List allEntities = externalRepo.findAll(); + + if (allEntities == null) { + firePropertyChange(AFTER_FIND_ALL, null, null); + return null; + } + + List list = converter.toDomainAll(externalRepo.findAll()); - firePropertyChange(AFTER_FIND_ALL, null, d); + firePropertyChange(AFTER_FIND_ALL, null, list); - return d; + return list; } @Override diff --git a/src/main/java/dev/root101/clean/core/rest/ApiCRUDRestService.java b/src/main/java/dev/root101/clean/core/rest/ApiCRUDRestService.java new file mode 100644 index 0000000..0fa3fb6 --- /dev/null +++ b/src/main/java/dev/root101/clean/core/rest/ApiCRUDRestService.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Or read it directly from LICENCE.txt file at the root of this project. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.root101.clean.core.rest; + +import dev.root101.clean.core.app.domain.DomainObject; +import dev.root101.clean.core.framework.ApiResponse; +import java.util.List; + +/** + * + * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) + * @author JesusHdezWaterloo@Github + * @param + * @param + */ +public interface ApiCRUDRestService, ID> extends AbstractRestService { + + public ApiResponse create(Domain newObject) throws RuntimeException; + + public ApiResponse edit(Domain objectToEdit) throws RuntimeException; + + public ApiResponse destroy(Domain objectToDestroy) throws RuntimeException; + + public ApiResponse destroyById(ID keyId) throws RuntimeException; + + public ApiResponse findBy(ID keyId) throws RuntimeException; + + public ApiResponse> findAll() throws RuntimeException; + + /** + * By default return the size of the findAll() list. + * + * @return findAll().size() + * @throws RuntimeException + */ + public default ApiResponse count() throws RuntimeException { + return ApiResponse.buildSucces( + (long) findAll().getData().size() + ); + } + +} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/ValidationService.java b/src/main/java/dev/root101/clean/core/utils/validation/ValidationService.java index 8eda814..577b15f 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/ValidationService.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/ValidationService.java @@ -98,7 +98,7 @@ private static List> validateRecursive(List { - return t != null && !(ClassUtils.isPrimitiveOrWrapper(t.getClass()) || t instanceof String); + return t != null && !(ClassUtils.isPrimitiveOrWrapper(t.getClass()) || t.getClass().getName().startsWith("java")); }) .toArray(); //de todos los campos que no son promitivos los valido diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/Digit.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/Digit.java index 826fbc6..4d7730d 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/annotations/Digit.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/Digit.java @@ -16,7 +16,6 @@ */ package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.annotations.registers.DigitRegister; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; @@ -31,7 +30,7 @@ */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) -@Constraint(validatedBy = DigitRegister.class) +@Constraint(validatedBy = DigitRegister_Character.class) public @interface Digit { String message() default ""; diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/DigitRegister.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/DigitRegister_Character.java similarity index 75% rename from src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/DigitRegister.java rename to src/main/java/dev/root101/clean/core/utils/validation/annotations/DigitRegister_Character.java index 37bff5d..af428ab 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/DigitRegister.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/DigitRegister_Character.java @@ -14,10 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.root101.clean.core.utils.validation.annotations.registers; +package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.annotations.Digit; -import dev.root101.clean.core.utils.validation.checkables.CheckerFactory; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -26,7 +24,7 @@ * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) * @author JesusHdezWaterloo@Github */ -public class DigitRegister implements ConstraintValidator { +public class DigitRegister_Character implements ConstraintValidator { @Override public void initialize(Digit a) { @@ -35,7 +33,7 @@ public void initialize(Digit a) { @Override public boolean isValid(Character digit, ConstraintValidatorContext cvc) { - return CheckerFactory.buildDigitCheckable("source", digit).check(); + return Character.isDigit(digit); } } diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValue.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValue.java new file mode 100644 index 0000000..1070261 --- /dev/null +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValue.java @@ -0,0 +1,74 @@ +package dev.root101.clean.core.utils.validation.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +/** + * Enum target must implement an static 'isPresent' method. + *
+ *
+ *  public enum Type {
+ *      a("a"),
+ *      b("b"),
+ *      c("c"),
+ *      d("d"),
+ *      e("e"),
+ *      f("f"),
+ *      g("g"),
+ *      h("h"),
+ *      i("i");
+ *
+ *      private final String name;
+ *
+ *      private Type(String name) {
+ *          this.name = name;
+ *      }
+ *
+ *      @Override
+ *      public String toString() {
+ *          return name;
+ *      }
+ *
+ *      public boolean equals(String statusName) {
+ *          return this.toString().equalsIgnoreCase(statusName.trim().toLowerCase());
+ *      }
+ *
+ *
+ *      public static boolean isPresent(String event) {
+ *          return List.of(Type.values()
+ *          ).stream().anyMatch((Type t) -> {
+ *              return t.equals(event);
+ *          });
+ *      }
+ *  }
+ *
+ * 
+ * + * The annotated element must be one of the values in 'target' enum. Supported + * types are: + *
    + *
  • {@code String}
  • + *
+ * + * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) + * @author JesusHdezWaterloo@Github + */ +@Target(value = ElementType.FIELD) +@Retention(value = RetentionPolicy.RUNTIME) +@Constraint(validatedBy = {EnumValueRegister_String.class}) +public @interface EnumValue { + + String message() default "Value is not present in enum list."; + + Class[] groups() default {}; + + Class[] payload() default {}; + + public String detailMessage() default ""; + + public Class target(); +} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValueRegister_String.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValueRegister_String.java new file mode 100644 index 0000000..3667e75 --- /dev/null +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/EnumValueRegister_String.java @@ -0,0 +1,84 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package dev.root101.clean.core.utils.validation.annotations; + +import dev.root101.clean.core.utils.validation.annotations.EnumValue; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.BiPredicate; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; + +/** + * + * @author JesusHdezWaterloo@Github + */ +@Slf4j +public class EnumValueRegister_String implements ConstraintValidator { + + private static BiPredicate defaultComparison = (currentEnumValue, testValue) -> { + return currentEnumValue.name().equals(testValue); + }; + + public static void setDefaultEnumComparator(BiPredicate defaultComparison) { + Assert.notNull(defaultComparison, "Default comparison can't be null"); + EnumValueRegister_String.defaultComparison = defaultComparison; + } + + private Class clazz; + + @Override + public void initialize(EnumValue a) { + ConstraintValidator.super.initialize(a); + clazz = a.target(); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + //saco los valores del enum para usarlos despues + Enum[] valuesArr; + try { + Method valuesMethod = clazz.getDeclaredMethod("values"); + valuesArr = (Enum[]) valuesMethod.invoke(null); + } catch (Exception e) {//nunca debe entrar aqui + valuesArr = new Enum[0]; + } + + try { + boolean present; + try { + //trato de validar el objeto por el metodo estatico 'isPresent' + Method containMethod = clazz.getDeclaredMethod("isPresent", String.class); + present = (boolean) containMethod.invoke(null, value); + } catch (NoSuchMethodException ex) { + log.error("No static 'isPresent(java.lang.String)' method in: " + clazz.getCanonicalName() + ". Using default comparison of enum in '" + EnumValueRegister_String.class.getCanonicalName() + "'."); + + //Si dio el error de 'NoSuchMethodException', que no tiene ese metodo implementado, me voy a la comparacion por defecto + present = List.of(valuesArr).stream().anyMatch((currentEnum) -> { + return defaultComparison.test(currentEnum, value); + }); + } + + if (!present) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(String + .format( + "'%s' is not one of the one of allowable values: %s".formatted(value, + List.of(valuesArr).stream().map((Object object) -> { + return object.toString(); + }).toList().toString()) + )) + .addConstraintViolation(); + } + + return present; + } catch (Exception ex) { + log.error(ex.toString()); + return false; + } + } +} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExact.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExact.java index d380c31..1694f55 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExact.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExact.java @@ -16,7 +16,6 @@ */ package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.annotations.registers.SizeExactRegister; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; @@ -25,18 +24,26 @@ import java.lang.annotation.Target; /** + * * The annotated element must not have a sizeequal to 'length'. Supported + * types are: + *
    + *
  • {@code CharSequence} (length of char sequence is evaluated)
  • + *
  • {@code Collection} (collection size is evaluated)
  • + *
  • {@code Map} (map size is evaluated)
  • + *
  • Array (array length is evaluated)
  • + *
* * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) * @author JesusHdezWaterloo@Github */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) -@Constraint(validatedBy = SizeExactRegister.class) +@Constraint(validatedBy = {SizeExactRegister_CharSequence.class, SizeExactRegister_CollectionString.class, SizeExactRegister_ArrayString.class, SizeExactRegister_MapString.class}) public @interface SizeExact { int length(); - String message() default ""; + String message() default "Size dont match specific length."; Class[] groups() default {}; diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/SizeExactRegister.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_ArrayString.java similarity index 70% rename from src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/SizeExactRegister.java rename to src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_ArrayString.java index d41ddfb..b9f15f6 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/annotations/registers/SizeExactRegister.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_ArrayString.java @@ -14,10 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.root101.clean.core.utils.validation.annotations.registers; +package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.annotations.SizeExact; -import dev.root101.clean.core.utils.validation.checkables.CheckerFactory; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -26,7 +24,7 @@ * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) * @author JesusHdezWaterloo@Github */ -public class SizeExactRegister implements ConstraintValidator { +public class SizeExactRegister_ArrayString implements ConstraintValidator { private int length; @@ -37,8 +35,8 @@ public void initialize(SizeExact a) { } @Override - public boolean isValid(String text, ConstraintValidatorContext cvc) { - return CheckerFactory.buildLengthExactCheckable("source", text, length).check(); + public boolean isValid(Object[] array, ConstraintValidatorContext cvc) { + return array.length == length; } } diff --git a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/NeverCheckable.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CharSequence.java similarity index 61% rename from src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/NeverCheckable.java rename to src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CharSequence.java index a388d37..640fcbb 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/NeverCheckable.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CharSequence.java @@ -14,38 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.root101.clean.core.utils.validation.checkables.impl; +package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.checkables.Checkable; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; /** * * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) * @author JesusHdezWaterloo@Github */ -public class NeverCheckable implements Checkable { +public class SizeExactRegister_CharSequence implements ConstraintValidator { - private final String source; - private final Object value; - - public NeverCheckable(String source, Object value) { - this.source = source; - this.value = value; - } - - @Override - public boolean check() { - return false; - } + private int length; @Override - public String getSource() { - return source; + public void initialize(SizeExact a) { + ConstraintValidator.super.initialize(a); + this.length = a.length(); } @Override - public Object getValue() { - return value; + public boolean isValid(CharSequence text, ConstraintValidatorContext cvc) { + return text.length() == length; } } diff --git a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/DigitCheckable.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CollectionString.java similarity index 59% rename from src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/DigitCheckable.java rename to src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CollectionString.java index d33d9d5..68c0190 100644 --- a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/DigitCheckable.java +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_CollectionString.java @@ -14,38 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.root101.clean.core.utils.validation.checkables.impl; +package dev.root101.clean.core.utils.validation.annotations; -import dev.root101.clean.core.utils.validation.checkables.Checkable; +import java.util.Collection; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; /** * * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) * @author JesusHdezWaterloo@Github */ -public class DigitCheckable implements Checkable { +public class SizeExactRegister_CollectionString implements ConstraintValidator { - private final char value; - private final String source; - - public DigitCheckable(String source, char value) { - this.source = source; - this.value = value; - } - - @Override - public boolean check() { - return Character.isDigit(value); - } + private int length; @Override - public String getSource() { - return source; + public void initialize(SizeExact a) { + ConstraintValidator.super.initialize(a); + this.length = a.length(); } @Override - public Character getValue() { - return value; + public boolean isValid(Collection list, ConstraintValidatorContext cvc) { + return list.size() == length; } } diff --git a/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_MapString.java b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_MapString.java new file mode 100644 index 0000000..4663d1b --- /dev/null +++ b/src/main/java/dev/root101/clean/core/utils/validation/annotations/SizeExactRegister_MapString.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Or read it directly from LICENCE.txt file at the root of this project. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.root101.clean.core.utils.validation.annotations; + +import java.util.Map; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * + * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) + * @author JesusHdezWaterloo@Github + */ +public class SizeExactRegister_MapString implements ConstraintValidator { + + private int length; + + @Override + public void initialize(SizeExact a) { + ConstraintValidator.super.initialize(a); + this.length = a.length(); + } + + @Override + public boolean isValid(Map map, ConstraintValidatorContext cvc) { + return map.size() == length; + } + +} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/checkables/Checkable.java b/src/main/java/dev/root101/clean/core/utils/validation/checkables/Checkable.java deleted file mode 100644 index 1b02a2d..0000000 --- a/src/main/java/dev/root101/clean/core/utils/validation/checkables/Checkable.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Or read it directly from LICENCE.txt file at the root of this project. - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.root101.clean.core.utils.validation.checkables; - -/** - * - * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) - * @author JesusHdezWaterloo@Github - * @author jjhurtado@Github - * @param - */ -public interface Checkable { - - public boolean check(); - - public String getSource(); - - public T getValue(); - -} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/checkables/CheckerFactory.java b/src/main/java/dev/root101/clean/core/utils/validation/checkables/CheckerFactory.java deleted file mode 100644 index b0fad12..0000000 --- a/src/main/java/dev/root101/clean/core/utils/validation/checkables/CheckerFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Or read it directly from LICENCE.txt file at the root of this project. - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.root101.clean.core.utils.validation.checkables; - -import dev.root101.clean.core.utils.validation.checkables.impl.DigitCheckable; -import dev.root101.clean.core.utils.validation.checkables.impl.NeverCheckable; -import dev.root101.clean.core.utils.validation.checkables.impl.SizeExactCheckable; - -/** - * - * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) - * @author JesusHdezWaterloo@Github - */ -public class CheckerFactory { - - public static Checkable buildDigitCheckable(String source, Character value) { - return new DigitCheckable(source, value); - } - - public static Checkable buildLengthExactCheckable(String source, String value, int length) { - return new SizeExactCheckable(source, value, length); - } - - public static Checkable buildNeverCheckable(String source, Object value) { - return new NeverCheckable(source, value); - } -} diff --git a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/SizeExactCheckable.java b/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/SizeExactCheckable.java deleted file mode 100644 index 22bf3d5..0000000 --- a/src/main/java/dev/root101/clean/core/utils/validation/checkables/impl/SizeExactCheckable.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2022 Root101 (jhernandezb96@gmail.com, +53-5-426-8660). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Or read it directly from LICENCE.txt file at the root of this project. - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.root101.clean.core.utils.validation.checkables.impl; - -import dev.root101.clean.core.utils.validation.checkables.Checkable; - -/** - * - * @author Root101 (jhernandezb96@gmail.com, +53-5-426-8660) - * @author JesusHdezWaterloo@Github - */ -public class SizeExactCheckable implements Checkable { - - private final String source; - private final String value; - private final int length; - - public SizeExactCheckable(String source, String value, int length) { - this.source = source; - this.value = value; - this.length = length; - } - - @Override - public boolean check() { - return value.length() == length; - } - - @Override - public String getSource() { - return source; - } - - @Override - public String getValue() { - return value; - } - -}