diff --git a/config-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java b/config-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java index 266f816..a298010 100644 --- a/config-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java +++ b/config-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java @@ -3,7 +3,7 @@ package org.micord.config; import com.arangodb.ArangoDB; import com.arangodb.ArangoDBException; import com.arangodb.ArangoDatabase; -import org.micord.models.AqlConnectionParams; +import org.micord.models.requests.AqlConnectionParams; /** * @author Maksim Tereshin diff --git a/config-data-executor/src/main/java/org/micord/config/AtomikosConfig.java b/config-data-executor/src/main/java/org/micord/config/AtomikosConfig.java index 1cc4563..c4fba3b 100644 --- a/config-data-executor/src/main/java/org/micord/config/AtomikosConfig.java +++ b/config-data-executor/src/main/java/org/micord/config/AtomikosConfig.java @@ -1,38 +1,29 @@ package org.micord.config; -import com.atomikos.icatch.jta.UserTransactionImp; import com.atomikos.icatch.jta.UserTransactionManager; -import javax.transaction.TransactionManager; -import javax.transaction.UserTransaction; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.jta.JtaTransactionManager; -/** - * @author Maksim Tereshin - */ + @Configuration @EnableTransactionManagement public class AtomikosConfig { - @Bean - public UserTransaction userTransaction() throws Throwable { - UserTransactionImp userTransactionImp = new UserTransactionImp(); - userTransactionImp.setTransactionTimeout(300); - return userTransactionImp; - } - - @Bean - public TransactionManager atomikosTransactionManager() { + @Bean(initMethod = "init", destroyMethod = "close") + public UserTransactionManager userTransactionManager() { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(true); return userTransactionManager; } @Bean - public JtaTransactionManager transactionManager() throws Throwable { - return new JtaTransactionManager(userTransaction(), atomikosTransactionManager()); + public JtaTransactionManager transactionManager() { + JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(); + jtaTransactionManager.setTransactionManager(userTransactionManager()); + jtaTransactionManager.setUserTransaction(userTransactionManager()); + return jtaTransactionManager; } } diff --git a/config-data-executor/src/main/java/org/micord/config/DatabaseConnection.java b/config-data-executor/src/main/java/org/micord/config/DatabaseConnection.java index aabcf2b..41ec81e 100644 --- a/config-data-executor/src/main/java/org/micord/config/DatabaseConnection.java +++ b/config-data-executor/src/main/java/org/micord/config/DatabaseConnection.java @@ -1,7 +1,7 @@ package org.micord.config; import com.atomikos.jdbc.AtomikosDataSourceBean; -import org.micord.models.SqlConnectionParams; +import org.micord.models.requests.SqlConnectionParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/config-data-executor/src/main/java/org/micord/config/S3HttpConnection.java b/config-data-executor/src/main/java/org/micord/config/S3HttpConnection.java index 668332d..616f698 100644 --- a/config-data-executor/src/main/java/org/micord/config/S3HttpConnection.java +++ b/config-data-executor/src/main/java/org/micord/config/S3HttpConnection.java @@ -1,7 +1,7 @@ package org.micord.config; -import org.micord.models.S3ConnectionParams; -import org.micord.models.S3Request; +import org.micord.models.requests.S3ConnectionParams; +import org.micord.models.requests.S3Request; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; diff --git a/config-data-executor/src/main/java/org/micord/controller/ApiController.java b/config-data-executor/src/main/java/org/micord/controller/ApiController.java index 7c1ee0c..294f4f8 100644 --- a/config-data-executor/src/main/java/org/micord/controller/ApiController.java +++ b/config-data-executor/src/main/java/org/micord/controller/ApiController.java @@ -1,23 +1,28 @@ package org.micord.controller; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; - -import org.micord.models.DownloadCSVRequest; +import org.micord.enums.ConfigType; +import org.micord.exceptions.ValidationException; +import org.micord.models.requests.DownloadCSVRequest; import org.micord.service.ApiService; +import org.micord.service.ValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Map; + /** * REST Controller for API operations. */ @@ -30,38 +35,91 @@ public class ApiController { @Autowired private ApiService apiService; + @Autowired + private ValidationService validationService; + @PostMapping("/block") - public ResponseEntity block(@RequestBody List ids) throws FileNotFoundException { - logger.debug("Starting block process for ids: {}", ids); - apiService.process("block", ids); - logger.debug("Finished block process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно."); + public ResponseEntity block(@RequestBody List ids) { + try { + validationService.validateAll(ids); + + logger.debug("Starting block process for ids: {}", ids); + apiService.process(ConfigType.BLOCK, ids); + logger.debug("Finished block process for ids: {}", ids); + return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно."); + } catch (ValidationException e) { + logger.error("Validation failed for IDs: {}", ids, e); + return ResponseEntity.badRequest().body(Map.of( + "message", "Validation error occurred", + "details", e.getValidationDetails() + )); + } catch (Exception e) { + logger.error("Unexpected error during processing: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); + } } @PostMapping("/unblock") - public ResponseEntity unblock(@RequestBody List ids) throws FileNotFoundException { - logger.debug("Starting unblock process for ids: {}", ids); - apiService.process("unblock", ids); - logger.debug("Finished unblock process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно."); + public ResponseEntity unblock(@RequestBody List ids) { + try { + validationService.validateAll(ids); + + logger.debug("Starting unblock process for ids: {}", ids); + apiService.process(ConfigType.UNBLOCK, ids); + logger.debug("Finished unblock process for ids: {}", ids); + return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно."); + } catch (ValidationException e) { + logger.error("Validation failed for IDs: {}", ids, e); + return ResponseEntity.badRequest().body(Map.of( + "message", "Validation error occurred", + "details", e.getValidationDetails() + )); + } catch (Exception e) { + logger.error("Unexpected error during processing: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); + } } @PostMapping("/removeFromSystem") - public ResponseEntity removeFromSystem(@RequestBody List ids) - throws FileNotFoundException { - logger.debug("Starting removeFromSystem process for ids: {}", ids); - apiService.process("removeFromSystem", ids); - logger.debug("Finished removeFromSystem process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно."); + public ResponseEntity removeFromSystem(@RequestBody List ids) { + try { + validationService.validateAll(ids); + + logger.debug("Starting removeFromSystem process for ids: {}", ids); + apiService.process(ConfigType.REMOVE_FROM_SYSTEM, ids); + logger.debug("Finished removeFromSystem process for ids: {}", ids); + return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно."); + } catch (ValidationException e) { + logger.error("Validation failed for IDs: {}", ids, e); + return ResponseEntity.badRequest().body(Map.of( + "message", "Validation error occurred", + "details", e.getValidationDetails() + )); + } catch (Exception e) { + logger.error("Unexpected error during processing: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); + } } @PostMapping("/removeFromCallList") - public ResponseEntity removeFromCallList(@RequestBody List ids) - throws FileNotFoundException { - logger.debug("Starting removeFromCallList process for ids: {}", ids); - apiService.process("removeFromCallList", ids); - logger.debug("Finished removeFromCallList process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно."); + public ResponseEntity removeFromCallList(@RequestBody List ids) { + try { + validationService.validateAll(ids); + + logger.debug("Starting removeFromCallList process for ids: {}", ids); + apiService.process(ConfigType.REMOVE_FROM_CALL_LIST, ids); + logger.debug("Finished removeFromCallList process for ids: {}", ids); + return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно."); + } catch (ValidationException e) { + logger.error("Validation failed for IDs: {}", ids, e); + return ResponseEntity.badRequest().body(Map.of( + "message", "Validation error occurred", + "details", e.getValidationDetails() + )); + } catch (Exception e) { + logger.error("Unexpected error during processing: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); + } } @PostMapping("/downloadCSV") @@ -74,7 +132,7 @@ public class ApiController { } } - File csvFile = apiService.download("downloadCSV", request); + File csvFile = apiService.download(ConfigType.DOWNLOAD_CSV, request); InputStreamResource resource = new InputStreamResource(new FileInputStream(csvFile)); logger.debug("Finished downloadCSV process for request: {}. Sending to user...", request.getType()); @@ -89,7 +147,7 @@ public class ApiController { @GetMapping("/listDownloadTypes") public ResponseEntity listDownloadTypes() throws FileNotFoundException { - List downloadCSVTypes = apiService.getDownloadTypes("downloadCSV"); + List downloadCSVTypes = apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV); return ResponseEntity.ok(downloadCSVTypes); } diff --git a/config-data-executor/src/main/java/org/micord/enums/ConfigType.java b/config-data-executor/src/main/java/org/micord/enums/ConfigType.java new file mode 100644 index 0000000..34440d7 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/enums/ConfigType.java @@ -0,0 +1,21 @@ +package org.micord.enums; + +import lombok.Getter; + +@Getter +public enum ConfigType { + + BLOCK("block"), + UNBLOCK("block"), + REMOVE_FROM_SYSTEM("removeFromSystem"), + REMOVE_FROM_CALL_LIST("removeFromCallList"), + DOWNLOAD_CSV("downloadCSV"), + VALIDATE_BLOCK("validateBlock"); + + private final String type; + + ConfigType(String type) { + this.type = type; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/exceptions/ValidationException.java b/config-data-executor/src/main/java/org/micord/exceptions/ValidationException.java new file mode 100644 index 0000000..3c5669c --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/exceptions/ValidationException.java @@ -0,0 +1,17 @@ +package org.micord.exceptions; + +import lombok.Getter; + +import java.util.Map; + +@Getter +public class ValidationException extends RuntimeException { + private final Map validationDetails; + + public ValidationException(String message, Map validationDetails) { + super(message); + this.validationDetails = validationDetails; + } + +} + diff --git a/config-data-executor/src/main/java/org/micord/models/CachedConfig.java b/config-data-executor/src/main/java/org/micord/models/CachedConfig.java index 1c19cbb..9b0284e 100644 --- a/config-data-executor/src/main/java/org/micord/models/CachedConfig.java +++ b/config-data-executor/src/main/java/org/micord/models/CachedConfig.java @@ -1,21 +1,16 @@ package org.micord.models; +import lombok.Getter; + import java.nio.file.attribute.FileTime; -public class CachedConfig { - private final Requests config; +@Getter +public class CachedConfig { + private final T config; private final FileTime modifiedTime; - public CachedConfig(Requests config, FileTime modifiedTime) { + public CachedConfig(T config, FileTime modifiedTime) { this.config = config; this.modifiedTime = modifiedTime; } - - public Requests getConfig() { - return config; - } - - public FileTime getModifiedTime() { - return modifiedTime; - } } diff --git a/config-data-executor/src/main/java/org/micord/models/requests/AqlConnectionParams.java b/config-data-executor/src/main/java/org/micord/models/requests/AqlConnectionParams.java new file mode 100644 index 0000000..09fbf34 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/AqlConnectionParams.java @@ -0,0 +1,49 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + +/** + * @author Maksim Tereshin + */ +@Setter +public class AqlConnectionParams { + + private String host; + private int port; + private String username; + private String password; + private String database; + private String collection; + + @XmlElement(name = "Host") + public String getHost() { + return host; + } + + @XmlElement(name = "Port") + public int getPort() { + return port; + } + + @XmlElement(name = "Username") + public String getUsername() { + return username; + } + + @XmlElement(name = "Password") + public String getPassword() { + return password; + } + + @XmlElement(name = "Database") + public String getDatabase() { + return database; + } + + @XmlElement(name = "Collection") + public String getCollection() { + return collection; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/AqlRequest.java b/config-data-executor/src/main/java/org/micord/models/requests/AqlRequest.java new file mode 100644 index 0000000..66df4fe --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/AqlRequest.java @@ -0,0 +1,26 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElementWrapper; +import lombok.Setter; + +import java.util.List; + +@Setter +public class AqlRequest extends BaseRequest { + + private AqlConnectionParams aqlConnectionParams; + private List aqlRequestCollections; + + @XmlElement(name = "AqlConnectionParams") + public AqlConnectionParams getAqlConnectionParams() { + return aqlConnectionParams; + } + + @XmlElementWrapper(name = "AqlRequestCollections") + @XmlElement(name = "AqlRequestCollection") + public List getAqlRequestCollections() { + return aqlRequestCollections; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/AqlRequestCollection.java b/config-data-executor/src/main/java/org/micord/models/requests/AqlRequestCollection.java new file mode 100644 index 0000000..bd62c5d --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/AqlRequestCollection.java @@ -0,0 +1,23 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlValue; +import lombok.Setter; + +@Setter +public class AqlRequestCollection { + + private String type; + private String collectionUrl; + + @XmlAttribute(name = "type") + public String getType() { + return type; + } + + @XmlValue + public String getCollectionUrl() { + return collectionUrl; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/BaseRequest.java b/config-data-executor/src/main/java/org/micord/models/requests/BaseRequest.java new file mode 100644 index 0000000..dc4cd85 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/BaseRequest.java @@ -0,0 +1,29 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlSeeAlso; +import lombok.Setter; + +import java.util.List; + +/** + * @author Maksim Tereshin + */ +@Setter +@XmlSeeAlso({SqlRequest.class, S3Request.class, AqlRequest.class}) +public abstract class BaseRequest { + + private List requestArguments; + private String requestURL; + + @XmlElement(name = "RequestArgument") + public List getRequestArguments() { + return requestArguments; + } + + @XmlElement(name = "RequestURL") + public String getRequestURL() { + return requestURL; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/DownloadCSVRequest.java b/config-data-executor/src/main/java/org/micord/models/requests/DownloadCSVRequest.java new file mode 100644 index 0000000..920bf18 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/DownloadCSVRequest.java @@ -0,0 +1,16 @@ +package org.micord.models.requests; + + +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +public class DownloadCSVRequest { + private String type; + private List ids; + private LocalDate startDate; + private LocalDate endDate; +} + diff --git a/config-data-executor/src/main/java/org/micord/models/requests/DownloadRequest.java b/config-data-executor/src/main/java/org/micord/models/requests/DownloadRequest.java new file mode 100644 index 0000000..ae001c3 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/DownloadRequest.java @@ -0,0 +1,23 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + + +@Setter +public class DownloadRequest extends BaseRequest { + + private SqlConnectionParams sqlConnectionParams; + private String downloadRequestType; + + @XmlElement(name = "SqlConnectionParams") + public SqlConnectionParams getSqlConnectionParams() { + return sqlConnectionParams; + } + + @XmlElement(name = "DownloadRequestType") + public String getDownloadRequestType() { + return downloadRequestType; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/Request.java b/config-data-executor/src/main/java/org/micord/models/requests/Request.java new file mode 100644 index 0000000..f4dd593 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/Request.java @@ -0,0 +1,29 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlSeeAlso; +import lombok.Setter; + +import java.util.List; + +/** + * @author Maksim Tereshin + */ +@Setter +@XmlSeeAlso({SqlRequest.class, S3Request.class}) +public abstract class Request { + + private List requestArguments; + private String requestURL; + + @XmlElement(name = "RequestArgument") + public List getRequestArguments() { + return requestArguments; + } + + @XmlElement(name = "RequestURL") + public String getRequestURL() { + return requestURL; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/RequestArgument.java b/config-data-executor/src/main/java/org/micord/models/requests/RequestArgument.java new file mode 100644 index 0000000..3e2b127 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/RequestArgument.java @@ -0,0 +1,41 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import lombok.Setter; +import org.micord.enums.RequestArgumentType; + +/** + * @author Maksim Tereshin + */ +@Setter +@XmlRootElement(name = "RequestArgument") +public class RequestArgument { + + private RequestArgumentType type; + private String requestArgumentName;; + private String requestArgumentURL; + private SqlConnectionParams requestArgumentConnectionParams; + + @XmlAttribute(name = "type") + public RequestArgumentType getType() { + return type; + } + + @XmlElement(name = "RequestArgumentName") + public String getRequestArgumentName() { + return requestArgumentName; + } + + @XmlElement(name = "RequestArgumentURL") + public String getRequestArgumentURL() { + return requestArgumentURL; + } + + @XmlElement(name = "RequestArgumentConnectionParams") + public SqlConnectionParams getRequestArgumentConnectionParams() { + return requestArgumentConnectionParams; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/Requests.java b/config-data-executor/src/main/java/org/micord/models/requests/Requests.java new file mode 100644 index 0000000..4630972 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/Requests.java @@ -0,0 +1,41 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import lombok.Setter; + +import java.util.List; + +/** + * @author Maksim Tereshin + */ +@Setter +@XmlRootElement(name = "Requests") +public class Requests { + + private List sqlRequests; + private List aqlRequests; + private List s3Requests; + private List downloadRequests; + + @XmlElement(name = "DownloadRequest") + public List getDownloadRequests() { + return downloadRequests; + } + + @XmlElement(name = "SqlRequest") + public List getSqlRequests() { + return sqlRequests; + } + + @XmlElement(name = "AqlRequest") + public List getAqlRequests() { + return aqlRequests; + } + + @XmlElement(name = "S3Request") + public List getS3Requests() { + return s3Requests; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/S3ConnectionParams.java b/config-data-executor/src/main/java/org/micord/models/requests/S3ConnectionParams.java new file mode 100644 index 0000000..1699e48 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/S3ConnectionParams.java @@ -0,0 +1,55 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + +/** + * @author Maksim Tereshin + */ +@Setter +public class S3ConnectionParams { + + private String s3Key; + private String s3Secret; + private String host; + private String port; + private String contentType; + private String method; + private String body; + + @XmlElement(name = "S3Key") + public String getS3Key() { + return s3Key; + } + + @XmlElement(name = "S3Secret") + public String getS3Secret() { + return s3Secret; + } + + @XmlElement(name = "Host") + public String getHost() { + return host; + } + + @XmlElement(name = "Port") + public String getPort() { + return port; + } + + @XmlElement(name = "ContentType") + public String getContentType() { + return contentType; + } + + @XmlElement(name = "Method") + public String getMethod() { + return method; + } + + @XmlElement(name = "Body") + public String getBody() { + return body; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/S3Request.java b/config-data-executor/src/main/java/org/micord/models/requests/S3Request.java new file mode 100644 index 0000000..a9057d7 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/S3Request.java @@ -0,0 +1,19 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + +/** + * @author Maksim Tereshin + */ +@Setter +public class S3Request extends BaseRequest { + + private S3ConnectionParams s3ConnectionParams; + + @XmlElement(name = "S3ConnectionParams") + public S3ConnectionParams getS3ConnectionParams() { + return s3ConnectionParams; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/requests/SqlConnectionParams.java b/config-data-executor/src/main/java/org/micord/models/requests/SqlConnectionParams.java new file mode 100644 index 0000000..5bd05e4 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/SqlConnectionParams.java @@ -0,0 +1,65 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + +@Setter +public class SqlConnectionParams { + + private String jdbcHost; + private String jdbcPort; + private String jdbcUsername; + private String jdbcPassword; + private String jdbcDriverClassName; + private String jdbcXaDataSourceClassName; + private String jdbcXaDataSourcePoolSize; + private String jdbcDatabase; + private String jdbcXaDataSourceBorrowConnectionTimeout; + + + @XmlElement(name = "JdbcXaDataSourceBorrowConnectionTimeout") + public String getJdbcXaDataSourceBorrowConnectionTimeout() { + return jdbcXaDataSourceBorrowConnectionTimeout; + } + + @XmlElement(name = "JdbcXaDataSourcePoolSize") + public String getJdbcXaDataSourcePoolSize() { + return jdbcXaDataSourcePoolSize; + } + + @XmlElement(name = "JdbcHost") + public String getJdbcHost() { + return jdbcHost; + } + + @XmlElement(name = "JdbcPort") + public String getJdbcPort() { + return jdbcPort; + } + + @XmlElement(name = "JdbcUsername") + public String getJdbcUsername() { + return jdbcUsername; + } + + @XmlElement(name = "JdbcPassword") + public String getJdbcPassword() { + return jdbcPassword; + } + + @XmlElement(name = "JdbcDriverClassName") + public String getJdbcDriverClassName() { + return jdbcDriverClassName; + } + + @XmlElement(name = "JdbcXaDataSourceClassName") + public String getJdbcXaDataSourceClassName() { + return jdbcXaDataSourceClassName; + } + + @XmlElement(name = "JdbcDatabase") + public String getJdbcDatabase() { + return jdbcDatabase; + } + +} \ No newline at end of file diff --git a/config-data-executor/src/main/java/org/micord/models/requests/SqlRequest.java b/config-data-executor/src/main/java/org/micord/models/requests/SqlRequest.java new file mode 100644 index 0000000..0d67779 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/requests/SqlRequest.java @@ -0,0 +1,19 @@ +package org.micord.models.requests; + +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; + +/** + * @author Maksim Tereshin + */ +@Setter +public class SqlRequest extends BaseRequest { + + private SqlConnectionParams sqlConnectionParams; + + @XmlElement(name = "SqlConnectionParams") + public SqlConnectionParams getSqlConnectionParams() { + return sqlConnectionParams; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/validations/ValidationRule.java b/config-data-executor/src/main/java/org/micord/models/validations/ValidationRule.java new file mode 100644 index 0000000..6912655 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/validations/ValidationRule.java @@ -0,0 +1,39 @@ +package org.micord.models.validations; + +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import lombok.Setter; +import org.micord.models.requests.SqlConnectionParams; + +import java.util.List; + + +@Setter +public class ValidationRule { + + private SqlConnectionParams sqlConnectionParams; + private String requestURL; + private String idColumn; + private List validationColumns; + + @XmlElement(name = "RequestURL") + public String getRequestURL() { + return requestURL; + } + + @XmlElement(name = "SqlConnectionParams") + public SqlConnectionParams getSqlConnectionParams() { + return sqlConnectionParams; + } + + @XmlAttribute(name = "validationColumns") + public List getValidationColumns() { + return validationColumns; + } + + @XmlAttribute(name = "idColumn") + public String getIdColumn() { + return idColumn; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/models/validations/ValidationRules.java b/config-data-executor/src/main/java/org/micord/models/validations/ValidationRules.java new file mode 100644 index 0000000..ce4df7b --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/models/validations/ValidationRules.java @@ -0,0 +1,21 @@ +package org.micord.models.validations; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import lombok.Setter; + +import java.util.List; + + +@Setter +@XmlRootElement(name = "ValidationRules") +public class ValidationRules { + + private List validationRules; + + @XmlElement(name = "ValidationRule") + public List getValidationRules() { + return validationRules; + } + +} diff --git a/config-data-executor/src/main/java/org/micord/service/ApiService.java b/config-data-executor/src/main/java/org/micord/service/ApiService.java index 5a2b68d..97e5c6c 100644 --- a/config-data-executor/src/main/java/org/micord/service/ApiService.java +++ b/config-data-executor/src/main/java/org/micord/service/ApiService.java @@ -1,7 +1,9 @@ package org.micord.service; -import org.micord.models.*; -import org.micord.utils.ConfigLoader; +import org.micord.enums.ConfigType; +import org.micord.models.requests.DownloadCSVRequest; +import org.micord.models.requests.DownloadRequest; +import org.micord.models.requests.Requests; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -10,7 +12,8 @@ import org.springframework.stereotype.Service; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.*; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -18,22 +21,22 @@ public class ApiService { private static final Logger logger = LoggerFactory.getLogger(ApiService.class); - @Autowired - private ConfigLoader configLoader; - @Autowired private RequestService sqlAndAqlService; @Autowired private DownloadService downloadService; - public void process(String methodName, List ids) throws FileNotFoundException { - Requests config = getConfig(methodName); + @Autowired + private ConfigService configService; + + public void process(ConfigType methodName, List ids) throws FileNotFoundException { + Requests config = configService.getConfig(methodName, Requests.class); sqlAndAqlService.processSqlAndAqlRequests(config, ids); } - public File download(String methodName, DownloadCSVRequest request) throws IOException { - Requests config = getConfig(methodName); + public File download(ConfigType methodName, DownloadCSVRequest request) throws IOException { + Requests config = configService.getConfig(methodName, Requests.class); String type = request.getType(); List ids = Optional.ofNullable(request.getIds()) @@ -48,22 +51,12 @@ public class ApiService { return downloadService.download(selectedRequest, ids, request.getStartDate(), request.getEndDate()); } - public List getDownloadTypes(String methodName) throws FileNotFoundException { - Requests config = getConfig(methodName); + public List getDownloadTypes(ConfigType methodName) throws FileNotFoundException { + Requests config = configService.getConfig(methodName, Requests.class); return config.getDownloadRequests().stream() .map(DownloadRequest::getDownloadRequestType) .collect(Collectors.toList()); } - private Requests getConfig(String methodName) throws FileNotFoundException { - logger.debug("Loading configuration for method: {}", methodName); - Optional optionalConfig = configLoader.loadConfigIfModified(methodName); - - if (optionalConfig.isEmpty()) { - throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded."); - } - - return optionalConfig.get(); - } } diff --git a/config-data-executor/src/main/java/org/micord/service/ConfigService.java b/config-data-executor/src/main/java/org/micord/service/ConfigService.java new file mode 100644 index 0000000..d7d99f3 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/service/ConfigService.java @@ -0,0 +1,33 @@ +package org.micord.service; + + +import org.micord.enums.ConfigType; +import org.micord.utils.ConfigLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.FileNotFoundException; +import java.util.Optional; + +@Service +public class ConfigService { + + private static final Logger logger = LoggerFactory.getLogger(ConfigService.class); + + @Autowired + private ConfigLoader configLoader; + + public T getConfig(ConfigType methodName, Class configClass) throws FileNotFoundException { + logger.debug("Loading configuration for method: {}", methodName); + Optional optionalConfig = configLoader.loadConfigIfModified(methodName, configClass); + + if (optionalConfig.isEmpty()) { + throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded."); + } + + return optionalConfig.get(); + } + +} diff --git a/config-data-executor/src/main/java/org/micord/service/DownloadService.java b/config-data-executor/src/main/java/org/micord/service/DownloadService.java index d70ede4..de27af8 100644 --- a/config-data-executor/src/main/java/org/micord/service/DownloadService.java +++ b/config-data-executor/src/main/java/org/micord/service/DownloadService.java @@ -1,8 +1,8 @@ package org.micord.service; import org.micord.config.DatabaseConnection; -import org.micord.models.DownloadRequest; -import org.micord.models.RequestArgument; +import org.micord.models.requests.DownloadRequest; +import org.micord.models.requests.RequestArgument; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; diff --git a/config-data-executor/src/main/java/org/micord/service/RequestService.java b/config-data-executor/src/main/java/org/micord/service/RequestService.java index 72d23b7..be88d57 100644 --- a/config-data-executor/src/main/java/org/micord/service/RequestService.java +++ b/config-data-executor/src/main/java/org/micord/service/RequestService.java @@ -1,5 +1,22 @@ package org.micord.service; +import com.arangodb.ArangoCursor; +import com.arangodb.ArangoDBException; +import com.arangodb.ArangoDatabase; +import com.arangodb.entity.StreamTransactionEntity; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.model.StreamTransactionOptions; +import org.micord.config.ArangoDBConnection; +import org.micord.config.DatabaseConnection; +import org.micord.config.S3HttpConnection; +import org.micord.enums.RequestArgumentType; +import org.micord.models.requests.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import java.net.HttpURLConnection; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -12,23 +29,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import com.arangodb.ArangoCursor; -import com.arangodb.ArangoDBException; -import com.arangodb.ArangoDatabase; -import com.arangodb.entity.StreamTransactionEntity; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.model.StreamTransactionOptions; -import org.micord.config.ArangoDBConnection; -import org.micord.config.DatabaseConnection; -import org.micord.config.S3HttpConnection; -import org.micord.enums.RequestArgumentType; -import org.micord.models.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - /** * @author Maksim Tereshin */ @@ -44,16 +44,20 @@ public class RequestService { logger.debug("Starting processing of S3 requests"); if (s3Requests != null && !s3Requests.isEmpty()) { List> requestFutures = s3Requests.stream() - .map(request -> CompletableFuture.runAsync(() -> processS3Request(request, ids))) + .map(request -> CompletableFuture.runAsync(() -> { + try { + processS3Request(request, ids); + } catch (Exception e) { + throw new RuntimeException("Error processing S3 request: " + request, e); + } + })) .toList(); CompletableFuture.allOf(requestFutures.toArray(new CompletableFuture[0])) - .thenRun(() -> logger.info("Successfully processed all S3 requests.")) .whenComplete((result, ex) -> { if (ex != null) { logger.error("Error processing S3 requests", ex); - } else { - logger.info("Successfully processed all S3 requests."); + throw new RuntimeException("Error processing S3 requests", ex); } }); } @@ -81,10 +85,16 @@ public class RequestService { } catch (SQLException e) { logger.error("Failed to execute query for RequestArgument", e); + throw new RuntimeException("Error executing database query: " + argument.getRequestArgumentURL(), e); } } } + if (files.isEmpty()) { + logger.warn("No files found for S3 request"); + throw new RuntimeException("No files found for S3 request"); + } + files.forEach(file -> { HttpRequest httpRequest; logger.debug("Starting building HTTP request for S3 request"); @@ -92,7 +102,7 @@ public class RequestService { if (file == null || file.isBlank()) { logger.warn("Skipping invalid file path: {}", file); - return; + throw new RuntimeException("Invalid file path"); } try { httpRequest = S3HttpConnection.buildHttpRequest(request, file); @@ -167,6 +177,7 @@ public class RequestService { } catch (SQLException e) { logger.error("SQL execution failed for query: {}", query, e); + throw new RuntimeException("Error executing SQL query", e); } } diff --git a/config-data-executor/src/main/java/org/micord/service/ValidationService.java b/config-data-executor/src/main/java/org/micord/service/ValidationService.java new file mode 100644 index 0000000..1cc36c2 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/service/ValidationService.java @@ -0,0 +1,101 @@ +package org.micord.service; + +import org.micord.config.DatabaseConnection; +import org.micord.enums.ConfigType; +import org.micord.exceptions.ValidationException; +import org.micord.models.validations.ValidationRule; +import org.micord.models.validations.ValidationRules; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.FileNotFoundException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class ValidationService { + + @Autowired + private ConfigService configService; + + public Map> validate(List ids, ValidationRule rule) throws SQLException { + + String query = rule.getRequestURL(); + if (!query.contains("${endpointArguments}")) { + throw new IllegalArgumentException("The query must contain the placeholder '${endpointArguments}' for ID replacement."); + } + + String finalQuery = query.replace("${endpointArguments}", "(" + ids.stream().map(id -> "?").collect(Collectors.joining(", ")) + ")"); + + try (Connection connection = DatabaseConnection.getConnection( + rule.getSqlConnectionParams()); + PreparedStatement preparedStatement = connection.prepareStatement(finalQuery)) { + + for (int i = 0; i < ids.size(); i++) { + preparedStatement.setObject(i + 1, UUID.fromString(ids.get(i))); + } + + try (ResultSet resultSet = preparedStatement.executeQuery()) { + Map> validationResults = new HashMap<>(); + + while (resultSet.next()) { + String id = resultSet.getString(rule.getIdColumn()); + Map columnValues = new HashMap<>(); + + for (String column : rule.getValidationColumns()) { + Object value = resultSet.getObject(column); + columnValues.put(column, value); + } + validationResults.put(id, columnValues); + } + + return validationResults; + } catch (SQLException e) { + throw new SQLException("Failed to execute query for ValidationRule", e); + } + } + } + + public Map validateAll(List ids) throws ValidationException, FileNotFoundException, SQLException { + ValidationRules config = configService.getConfig(ConfigType.VALIDATE_BLOCK, ValidationRules.class); + + if (config.getValidationRules() == null || config.getValidationRules().isEmpty()) { + return null; + } + + Map> validationResults = new HashMap<>(); + + for (ValidationRule rule : config.getValidationRules()) { + validationResults.putAll(validate(ids, rule)); + } + + Map invalidRecords = new HashMap<>(); + + validationResults.forEach((id, columnValues) -> { + List invalidColumns = columnValues.entrySet().stream() + .filter(entry -> Boolean.FALSE.equals(entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (!invalidColumns.isEmpty()) { + String message = "Запись с UUID = " + id + " не прошла проверку по следующим колонкам: " + String.join(", ", invalidColumns); + invalidRecords.put(id, message); + } + }); + + if (!invalidRecords.isEmpty()) { + throw new ValidationException("Validation failed for some records", invalidRecords); + } + + return invalidRecords; + + } + +} diff --git a/config-data-executor/src/main/java/org/micord/utils/ConfigLoader.java b/config-data-executor/src/main/java/org/micord/utils/ConfigLoader.java index 9347ed3..45586ac 100644 --- a/config-data-executor/src/main/java/org/micord/utils/ConfigLoader.java +++ b/config-data-executor/src/main/java/org/micord/utils/ConfigLoader.java @@ -1,5 +1,13 @@ package org.micord.utils; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Unmarshaller; +import org.micord.enums.ConfigType; +import org.micord.models.CachedConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -10,14 +18,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.Unmarshaller; - -import org.micord.models.CachedConfig; -import org.micord.models.Requests; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; /** * @author Maksim Tereshin @@ -31,7 +31,9 @@ public class ConfigLoader { @Value("${configDirectory}") private String configDirectory; - public Optional loadConfigIfModified(String methodName) { + + public Optional loadConfigIfModified(ConfigType configType, Class configClass) { + String methodName = configType.getType(); String fileName = methodName + ".xml"; if (configDirectory == null) { @@ -47,14 +49,14 @@ public class ConfigLoader { if (cachedConfig == null || !currentModifiedTime.equals(cachedConfig.getModifiedTime())) { // Load the updated configuration - JAXBContext jaxbContext = JAXBContext.newInstance(Requests.class); + JAXBContext jaxbContext = JAXBContext.newInstance(configClass); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - Requests loadedConfig = (Requests) unmarshaller.unmarshal(configFile); + T loadedConfig = unmarshalConfig(configFile, configClass); cachedConfigs.put(methodName, new CachedConfig(loadedConfig, currentModifiedTime)); return Optional.of(loadedConfig); } else { - return Optional.of(cachedConfigs.get(methodName).getConfig()); + return (Optional) Optional.of(cachedConfigs.get(methodName).getConfig()); } } @@ -67,4 +69,10 @@ public class ConfigLoader { return Optional.empty(); // Return empty if unmarshalling fails } } + + private T unmarshalConfig(File configFile, Class configClass) throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(configClass); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + return configClass.cast(unmarshaller.unmarshal(configFile)); + } }