diff --git a/сonfig-data-executor/.gitignore b/сonfig-data-executor/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/сonfig-data-executor/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/сonfig-data-executor/pom.xml b/сonfig-data-executor/pom.xml
new file mode 100644
index 0000000..67f4a98
--- /dev/null
+++ b/сonfig-data-executor/pom.xml
@@ -0,0 +1,115 @@
+
+
+ 4.0.0
+
+ org.micord
+ config-data-executor
+ 1.0-SNAPSHOT
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+
+ com.amazonaws
+ aws-java-sdk-bom
+ 1.12.770
+ pom
+ import
+
+
+
+
+
+
+ org.jooq
+ jooq
+ 3.19.11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 3.3.2
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ 3.3.2
+
+
+ org.projectlombok
+ lombok
+ provided
+ 1.18.34
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ 2.3.1
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+ runtime
+
+
+ com.arangodb
+ arangodb-java-driver
+ 7.7.1
+
+
+ com.amazonaws
+ aws-java-sdk-s3
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.0
+
+
+ javax.transaction
+ javax.transaction-api
+ 1.3
+
+
+
+
+
+ maven_central
+ Maven Central
+ https://repo.maven.apache.org/maven2/
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ org.micord.Main
+
+
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/java/org/micord/Main.java b/сonfig-data-executor/src/main/java/org/micord/Main.java
new file mode 100644
index 0000000..b37af38
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/Main.java
@@ -0,0 +1,15 @@
+package org.micord;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+/**
+ * @author Maksim Tereshin
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public class Main {
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java b/сonfig-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java
new file mode 100644
index 0000000..266f816
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java
@@ -0,0 +1,34 @@
+package org.micord.config;
+
+import com.arangodb.ArangoDB;
+import com.arangodb.ArangoDBException;
+import com.arangodb.ArangoDatabase;
+import org.micord.models.AqlConnectionParams;
+
+/**
+ * @author Maksim Tereshin
+ */
+public class ArangoDBConnection {
+
+ public static ArangoDatabase getConnection(AqlConnectionParams params) {
+ try {
+ ArangoDB arangoDB = new ArangoDB.Builder()
+ .host(params.getHost(), params.getPort())
+ .user(params.getUsername())
+ .password(params.getPassword())
+ .build();
+
+ ArangoDatabase db = arangoDB.db(params.getDatabase());
+
+ if (!db.exists()) {
+ throw new ArangoDBException("Database does not exist: " + params.getDatabase());
+ }
+
+ return db;
+
+ } catch (ArangoDBException e) {
+ throw new RuntimeException("Failed to connect to ArangoDB", e);
+ }
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/config/AtomikosConfig.java b/сonfig-data-executor/src/main/java/org/micord/config/AtomikosConfig.java
new file mode 100644
index 0000000..7f731eb
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/config/AtomikosConfig.java
@@ -0,0 +1,27 @@
+package org.micord.config;
+
+import com.atomikos.icatch.jta.UserTransactionImp;
+import com.atomikos.icatch.jta.UserTransactionManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Configuration
+@EnableTransactionManagement
+public class AtomikosConfig {
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ public UserTransactionManager atomikosTransactionManager() {
+ return new UserTransactionManager();
+ }
+
+ @Bean
+ public UserTransactionImp atomikosUserTransaction() {
+ return new UserTransactionImp();
+ }
+
+}
+
diff --git a/сonfig-data-executor/src/main/java/org/micord/config/DatabaseConnection.java b/сonfig-data-executor/src/main/java/org/micord/config/DatabaseConnection.java
new file mode 100644
index 0000000..0057bf0
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/config/DatabaseConnection.java
@@ -0,0 +1,24 @@
+package org.micord.config;
+
+import org.micord.models.SqlConnectionParams;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+/**
+ * @author Maksim Tereshin
+ */
+public class DatabaseConnection {
+
+ public static Connection getConnection(SqlConnectionParams params) throws SQLException {
+ try {
+ Class.forName(params.getJdbcDriverClassName());
+ } catch (ClassNotFoundException e) {
+ throw new SQLException("Unable to load the JDBC driver class", e);
+ }
+
+ return DriverManager.getConnection(params.getJdbcUrl(), params.getJdbcUsername(), params.getJdbcPassword());
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/config/HttpClientConfig.java b/сonfig-data-executor/src/main/java/org/micord/config/HttpClientConfig.java
new file mode 100644
index 0000000..9957ad4
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/config/HttpClientConfig.java
@@ -0,0 +1,19 @@
+package org.micord.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.net.http.HttpClient;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Configuration
+public class HttpClientConfig {
+
+ @Bean
+ public HttpClient httpClient() {
+ return HttpClient.newHttpClient();
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/config/S3HttpConnection.java b/сonfig-data-executor/src/main/java/org/micord/config/S3HttpConnection.java
new file mode 100644
index 0000000..3ee7750
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/config/S3HttpConnection.java
@@ -0,0 +1,62 @@
+package org.micord.config;
+
+import org.micord.models.S3ConnectionParams;
+import org.micord.models.S3Request;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Base64;
+
+/**
+ * @author Maksim Tereshin
+ */
+public class S3HttpConnection {
+
+ public static HttpRequest buildHttpRequest(S3Request request, String file) throws Exception {
+ S3ConnectionParams connectionParams = request.getS3ConnectionParams();
+ String host = connectionParams.getHost() + ":" + connectionParams.getPort();
+ String s3Key = connectionParams.getS3Key();
+ String s3Secret = connectionParams.getS3Secret();
+
+ // The resource to be deleted, typically a file in the S3 bucket
+ String resource = "/" + file;
+ String contentType = "application/octet-stream";
+ String date = ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);
+
+ // Generate a signature for the DELETE request
+ String signature = generateSignature("DELETE", contentType, date, resource, s3Secret);
+
+ // Build and return the HTTP DELETE request
+ return HttpRequest.newBuilder()
+ .uri(URI.create("https://" + host + resource))
+ .header("Host", host)
+ .header("Date", date)
+ .header("Content-Type", contentType)
+ .header("Authorization", "AWS " + s3Key + ":" + signature)
+ .DELETE()
+ .build();
+ }
+
+ // Utility method to generate a signature for the S3-compatible request
+ private static String generateSignature(String method, String contentType, String date, String resource, String s3Secret) throws Exception {
+ String stringToSign = method + "\n" +
+ "\n" + // MD5 - not used for DELETE requests
+ contentType + "\n" +
+ date + "\n" +
+ resource;
+
+ // HMAC-SHA1 signature generation
+ Mac mac = Mac.getInstance("HmacSHA1");
+ SecretKeySpec secretKey = new SecretKeySpec(s3Secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
+ mac.init(secretKey);
+
+ byte[] hash = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(hash);
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/controller/ApiController.java b/сonfig-data-executor/src/main/java/org/micord/controller/ApiController.java
new file mode 100644
index 0000000..a84ab90
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/controller/ApiController.java
@@ -0,0 +1,47 @@
+package org.micord.controller;
+
+import org.micord.exceptions.FileNotModifiedException;
+import org.micord.service.ApiService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * REST Controller for API operations.
+ */
+@RestController
+@RequestMapping("/api")
+public class ApiController {
+
+ @Autowired
+ private ApiService apiService;
+
+ @PostMapping("/block")
+ public CompletableFuture> block(@RequestBody List ids) throws FileNotFoundException, FileNotModifiedException {
+ return apiService.process("block", ids)
+ .thenApply(ResponseEntity::ok);
+ }
+
+ @PostMapping("/unblock")
+ public CompletableFuture> unblock(@RequestBody List ids) throws FileNotFoundException, FileNotModifiedException {
+ return apiService.process("unblock", ids)
+ .thenApply(ResponseEntity::ok);
+ }
+
+ @PostMapping("/removeFromSystem")
+ public CompletableFuture> removeFromSystem(@RequestBody List ids) throws FileNotFoundException, FileNotModifiedException {
+ return apiService.process("removeFromSystem", ids)
+ .thenApply(ResponseEntity::ok);
+ }
+
+ @PostMapping("/removeFromCallList")
+ public CompletableFuture> removeFromCallList(@RequestBody List ids) throws FileNotFoundException, FileNotModifiedException {
+ return apiService.process("removeFromCallList", ids)
+ .thenApply(ResponseEntity::ok);
+ }
+
+}
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/java/org/micord/exceptions/FileNotModifiedException.java b/сonfig-data-executor/src/main/java/org/micord/exceptions/FileNotModifiedException.java
new file mode 100644
index 0000000..d9678e6
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/exceptions/FileNotModifiedException.java
@@ -0,0 +1,10 @@
+package org.micord.exceptions;
+
+/**
+ * @author Maksim Tereshin
+ */
+public class FileNotModifiedException extends Exception {
+ public FileNotModifiedException(String message) {
+ super(message);
+ }
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/AqlConnectionParams.java b/сonfig-data-executor/src/main/java/org/micord/models/AqlConnectionParams.java
new file mode 100644
index 0000000..48e7ddf
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/AqlConnectionParams.java
@@ -0,0 +1,50 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @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/сonfig-data-executor/src/main/java/org/micord/models/AqlRequest.java b/сonfig-data-executor/src/main/java/org/micord/models/AqlRequest.java
new file mode 100644
index 0000000..f05586d
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/AqlRequest.java
@@ -0,0 +1,20 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+public class AqlRequest extends Request {
+
+ private AqlConnectionParams aqlConnectionParams;
+
+ @XmlElement(name = "AqlConnectionParams")
+ public AqlConnectionParams getAqlConnectionParams() {
+ return aqlConnectionParams;
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/Request.java b/сonfig-data-executor/src/main/java/org/micord/models/Request.java
new file mode 100644
index 0000000..1328e6c
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/Request.java
@@ -0,0 +1,29 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+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/сonfig-data-executor/src/main/java/org/micord/models/RequestArgument.java b/сonfig-data-executor/src/main/java/org/micord/models/RequestArgument.java
new file mode 100644
index 0000000..44951f4
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/RequestArgument.java
@@ -0,0 +1,34 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+@XmlRootElement(name = "RequestArgument")
+public class RequestArgument {
+
+ private String id;
+ private String requestURL;
+ private SqlConnectionParams sqlConnectionParams;
+
+ @XmlElement(name = "SqlConnectionParams")
+ public SqlConnectionParams getSqlConnectionParams() {
+ return sqlConnectionParams;
+ }
+
+ @XmlElement(name = "Id")
+ public String getId() {
+ return id;
+ }
+
+ @XmlElement(name = "RequestURL")
+ public String getRequestURL() {
+ return requestURL;
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/Requests.java b/сonfig-data-executor/src/main/java/org/micord/models/Requests.java
new file mode 100644
index 0000000..46a07d2
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/Requests.java
@@ -0,0 +1,35 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.List;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+@XmlRootElement(name = "Requests")
+public class Requests {
+
+ private List sqlRequests;
+ private List aqlRequests;
+ private List s3Requests;
+
+ @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/сonfig-data-executor/src/main/java/org/micord/models/S3ConnectionParams.java b/сonfig-data-executor/src/main/java/org/micord/models/S3ConnectionParams.java
new file mode 100644
index 0000000..6472ab9
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/S3ConnectionParams.java
@@ -0,0 +1,38 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+public class S3ConnectionParams {
+
+ private String s3Key;
+ private String s3Secret;
+ private String host;
+ private String port;
+
+ @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;
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/S3Request.java b/сonfig-data-executor/src/main/java/org/micord/models/S3Request.java
new file mode 100644
index 0000000..0422860
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/S3Request.java
@@ -0,0 +1,20 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+public class S3Request extends Request {
+
+ private S3ConnectionParams s3ConnectionParams;
+
+ @XmlElement(name = "S3ConnectionParams")
+ public S3ConnectionParams getS3ConnectionParams() {
+ return s3ConnectionParams;
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/SqlConnectionParams.java b/сonfig-data-executor/src/main/java/org/micord/models/SqlConnectionParams.java
new file mode 100644
index 0000000..97cdeb1
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/SqlConnectionParams.java
@@ -0,0 +1,41 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+@Setter
+public class SqlConnectionParams {
+
+ private String jdbcUrl;
+ private String jdbcUsername;
+ private String jdbcPassword;
+ private String jdbcDriverClassName;
+ private String jdbcDatabase;
+
+ @XmlElement(name = "JdbcUrl")
+ public String getJdbcUrl() {
+ return jdbcUrl;
+ }
+
+ @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 = "JdbcDatabase")
+ public String getJdbcDatabase() {
+ return jdbcDatabase;
+ }
+
+}
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/java/org/micord/models/SqlRequest.java b/сonfig-data-executor/src/main/java/org/micord/models/SqlRequest.java
new file mode 100644
index 0000000..3a51089
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/models/SqlRequest.java
@@ -0,0 +1,20 @@
+package org.micord.models;
+
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Setter
+public class SqlRequest extends Request {
+
+ private SqlConnectionParams sqlConnectionParams;
+
+ @XmlElement(name = "SqlConnectionParams")
+ public SqlConnectionParams getSqlConnectionParams() {
+ return sqlConnectionParams;
+ }
+
+}
diff --git a/сonfig-data-executor/src/main/java/org/micord/service/ApiService.java b/сonfig-data-executor/src/main/java/org/micord/service/ApiService.java
new file mode 100644
index 0000000..79820ea
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/service/ApiService.java
@@ -0,0 +1,229 @@
+package org.micord.service;
+
+import com.arangodb.ArangoCursor;
+import com.arangodb.ArangoDatabase;
+import com.atomikos.icatch.jta.UserTransactionImp;
+import org.micord.config.ArangoDBConnection;
+import org.micord.config.DatabaseConnection;
+import org.micord.config.S3HttpConnection;
+import org.micord.exceptions.FileNotModifiedException;
+import org.micord.models.*;
+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.net.HttpURLConnection;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+@Service
+public class ApiService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ApiService.class);
+
+ @Autowired
+ private ConfigLoader configLoader;
+
+ @Autowired
+ private UserTransactionImp userTransaction;
+
+ @Autowired
+ private HttpClient httpClient;
+
+ public CompletableFuture process(String methodName, List ids) throws FileNotFoundException, FileNotModifiedException {
+ Optional optionalConfig = configLoader.loadConfigIfModified(methodName);
+
+ if (optionalConfig.isEmpty()) {
+ throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded.");
+ }
+
+ Requests config = optionalConfig.get();
+
+ processRequests(config, ids);
+
+ return CompletableFuture.completedFuture("Processing complete");
+ }
+
+ public void processRequests(Requests config, List ids) {
+ try {
+ userTransaction.begin();
+
+ // Process SQL and AQL requests
+ processSqlAndAqlRequests(config, ids);
+
+ userTransaction.commit();
+ logger.info("Successfully processed requests for all IDs.");
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ logger.error("Transaction rolled back due to failure.", e);
+ } catch (Exception rollbackException) {
+ logger.error("Failed to rollback the transaction", rollbackException);
+ }
+ logger.error("Failed to process requests", e);
+ }
+
+ // Process S3 requests
+ processS3Requests(config.getS3Requests(), ids);
+ }
+
+ private void processSqlAndAqlRequests(Requests config, List ids) {
+ for (SqlRequest request : config.getSqlRequests()) {
+ processSqlRequests(request, ids);
+ }
+
+ for (AqlRequest request : config.getAqlRequests()) {
+ processAqlRequests(request, ids);
+ }
+ }
+
+ private void processSqlRequests(SqlRequest request, List ids) {
+ try (Connection connection = DatabaseConnection.getConnection(request.getSqlConnectionParams())) {
+ String query = buildSqlQuery(request, String.join(",", ids));
+ executeSqlQuery(connection, query);
+ } catch (SQLException e) {
+ logger.error("SQL query execution failed", e);
+ }
+ }
+
+ private String buildSqlQuery(SqlRequest request, String ids) {
+ StringBuilder clauseBuilder = new StringBuilder("id IN (" + ids + ")");
+
+ if (request.getRequestArguments() != null && !request.getRequestArguments().isEmpty()) {
+ for (RequestArgument argument : request.getRequestArguments()) {
+ try (Connection connection = DatabaseConnection.getConnection(argument.getSqlConnectionParams())) {
+ String query = argument.getRequestURL();
+ List result = fetchFileListFromDatabaseSQL(connection, query);
+
+ if (result != null && !result.isEmpty()) {
+ String resultSet = String.join(", ", result.stream()
+ .map(s -> "'" + s + "'")
+ .toArray(String[]::new));
+ clauseBuilder.append(" OR ").append(argument.getId()).append(" IN (").append(resultSet).append(")");
+ }
+ } catch (SQLException e) {
+ logger.error("Failed to execute query for RequestArgument", e);
+ }
+ }
+ }
+
+ String clause = clauseBuilder.toString();
+ return request.getRequestURL().replace("${clause}", clause);
+ }
+
+ private void executeSqlQuery(Connection connection, String query) throws SQLException {
+ try (PreparedStatement stmt = connection.prepareStatement(query)) {
+ stmt.execute();
+ }
+ }
+
+ private List fetchFileListFromDatabaseSQL(Connection connection, String query) throws SQLException {
+ List results = new ArrayList<>();
+ try (PreparedStatement stmt = connection.prepareStatement(query);
+ ResultSet rs = stmt.executeQuery()) {
+ while (rs.next()) {
+ results.add(rs.getString(1)); // Fetch the first column
+ }
+ }
+ return results;
+ }
+
+ // Process S3 Requests concurrently
+ public void processS3Requests(List s3Requests, List ids) {
+ s3Requests.forEach(request -> {
+ List> futures = ids.stream()
+ .map(id -> CompletableFuture.runAsync(() -> processS3Request(request, id)))
+ .toList();
+
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+ .thenRun(() -> logger.info("Successfully processed all S3 requests."))
+ .exceptionally(ex -> {
+ logger.error("Failed to process S3 requests", ex);
+ return null;
+ });
+ });
+ }
+
+ private void processS3Request(S3Request request, String id) {
+ try {
+ List files = new ArrayList<>();
+
+ if (request.getRequestArguments() != null && !request.getRequestArguments().isEmpty()) {
+ for (RequestArgument argument : request.getRequestArguments()) {
+ try (Connection connection = DatabaseConnection.getConnection(argument.getSqlConnectionParams())) {
+ String query = argument.getRequestURL();
+ List result = fetchFileListFromDatabaseSQL(connection, query);
+ if (result != null && !result.isEmpty()) {
+ files.addAll(result);
+ }
+ } catch (SQLException e) {
+ logger.error("Failed to execute query for RequestArgument", e);
+ }
+ }
+ }
+
+ files.forEach(file -> {
+ HttpRequest httpRequest;
+ try {
+ httpRequest = S3HttpConnection.buildHttpRequest(request, file);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
+ .thenAccept(response -> {
+ if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT || response.statusCode() == HttpURLConnection.HTTP_OK) {
+ logger.info("Successfully deleted object for ID {}", id);
+ } else {
+ logger.error("Failed to delete object for ID {}. Response code: {}", id, response.statusCode());
+ }
+ })
+ .exceptionally(ex -> {
+ logger.error("Failed to delete object for ID {}", id, ex);
+ return null;
+ });
+ });
+
+ } catch (Exception e) {
+ logger.error("Failed to process S3 request for id: {}", id, e);
+ }
+ }
+
+ // Process AQL requests
+ private void processAqlRequests(AqlRequest request, List ids) {
+ ArangoDatabase arangoDb = ArangoDBConnection.getConnection(request.getAqlConnectionParams());
+ String aqlQuery = buildAqlQuery(request, ids);
+ List result = executeAqlQueryForStrings(arangoDb, aqlQuery);
+ logger.info("Successfully executed AQL request and retrieved results: {}", result);
+ }
+
+ private String buildAqlQuery(AqlRequest request, List ids) {
+ String collection = request.getAqlConnectionParams().getCollection();
+ String inClause = "FOR doc IN " + collection + " FILTER doc.id IN [" +
+ String.join(", ", ids.stream().map(id -> "'" + id + "'").toArray(String[]::new)) +
+ "] RETURN doc";
+ return request.getRequestURL().replace("${collection}", collection).replace("${clause}", inClause);
+ }
+
+ private List executeAqlQueryForStrings(ArangoDatabase arangoDb, String query) {
+ List results = new ArrayList<>();
+ try (ArangoCursor cursor = arangoDb.query(query, null, null, null)) {
+ while (cursor.hasNext()) {
+ results.add(cursor.next());
+ }
+ } catch (Exception e) {
+ logger.error("Failed to execute AQL query", e);
+ }
+ return results;
+ }
+}
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/java/org/micord/utils/ConfigLoader.java b/сonfig-data-executor/src/main/java/org/micord/utils/ConfigLoader.java
new file mode 100644
index 0000000..f014d95
--- /dev/null
+++ b/сonfig-data-executor/src/main/java/org/micord/utils/ConfigLoader.java
@@ -0,0 +1,70 @@
+package org.micord.utils;
+
+import org.micord.exceptions.FileNotModifiedException;
+import org.micord.models.Requests;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Maksim Tereshin
+ */
+@Component
+public class ConfigLoader {
+
+ private static final Logger LOGGER = Logger.getLogger(ConfigLoader.class.getName());
+ private final Map lastModifiedTimes = new HashMap<>();
+
+ @Value("${configDirectory}")
+ private String configDirectory;
+
+ public Optional loadConfigIfModified(String methodName) throws FileNotModifiedException {
+ String fileName = methodName + ".xml";
+ if (configDirectory == null) {
+ LOGGER.log(Level.SEVERE, "No configuration directory found for method: " + methodName);
+ return Optional.empty();
+ }
+
+ try {
+ File configFile = new File(configDirectory + File.separator + fileName);
+ Path configFilePath = configFile.toPath();
+
+ FileTime currentModifiedTime = Files.getLastModifiedTime(configFilePath);
+ FileTime lastModifiedTime = lastModifiedTimes.getOrDefault(methodName, null);
+
+ if (lastModifiedTime == null || currentModifiedTime.compareTo(lastModifiedTime) > 0) {
+ lastModifiedTimes.put(methodName, currentModifiedTime);
+
+ // Load the updated configuration
+ JAXBContext jaxbContext = JAXBContext.newInstance(Requests.class);
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ Requests loadedConfig = (Requests) unmarshaller.unmarshal(configFile);
+
+ return Optional.of(loadedConfig);
+ } else {
+ throw new FileNotModifiedException("Configuration file for method " + methodName + " has not been modified.");
+ }
+
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Failed to load configuration file: " + fileName, e);
+ return Optional.empty(); // Return empty if there is an IO error
+ } catch (JAXBException e) {
+ LOGGER.log(Level.SEVERE, "Failed to unmarshal configuration file: " + fileName, e);
+ return Optional.empty(); // Return empty if unmarshalling fails
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/сonfig-data-executor/src/main/resources/application.yml b/сonfig-data-executor/src/main/resources/application.yml
new file mode 100644
index 0000000..7c65eb4
--- /dev/null
+++ b/сonfig-data-executor/src/main/resources/application.yml
@@ -0,0 +1,18 @@
+configDirectory: /Users/maksim/Projects/Micord/configs
+
+
+#spring:
+# datasource:
+# url: jdbc:mysql://localhost:3306/micord?useSSL=false
+# hikari:
+# maximum-pool-size: 10
+# minimum-idle: 5
+# idle-timeout: 30000
+# connection-timeout: 20000
+# max-lifetime: 1800000
+# jpa:
+# properties:
+# hibernate:
+# dialect: org.hibernate.dialect.MySQLDialect
+server:
+ port: 8090