From 0aae3f43bba67b85fce16f86f175d11bfd98a960 Mon Sep 17 00:00:00 2001 From: malkov34 Date: Thu, 5 Sep 2024 13:33:02 +0300 Subject: [PATCH] =?UTF-8?q?SUPPORT-8529:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20config-data-executor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- сonfig-data-executor/.gitignore | 38 +++ сonfig-data-executor/pom.xml | 115 +++++++++ .../src/main/java/org/micord/Main.java | 15 ++ .../org/micord/config/ArangoDBConnection.java | 34 +++ .../java/org/micord/config/AtomikosConfig.java | 27 +++ .../org/micord/config/DatabaseConnection.java | 24 ++ .../org/micord/config/HttpClientConfig.java | 19 ++ .../org/micord/config/S3HttpConnection.java | 62 +++++ .../org/micord/controller/ApiController.java | 47 ++++ .../exceptions/FileNotModifiedException.java | 10 + .../org/micord/models/AqlConnectionParams.java | 50 ++++ .../java/org/micord/models/AqlRequest.java | 20 ++ .../main/java/org/micord/models/Request.java | 29 +++ .../org/micord/models/RequestArgument.java | 34 +++ .../main/java/org/micord/models/Requests.java | 35 +++ .../org/micord/models/S3ConnectionParams.java | 38 +++ .../main/java/org/micord/models/S3Request.java | 20 ++ .../org/micord/models/SqlConnectionParams.java | 41 ++++ .../java/org/micord/models/SqlRequest.java | 20 ++ .../java/org/micord/service/ApiService.java | 229 ++++++++++++++++++ .../java/org/micord/utils/ConfigLoader.java | 70 ++++++ .../src/main/resources/application.yml | 18 ++ 22 files changed, 995 insertions(+) create mode 100644 сonfig-data-executor/.gitignore create mode 100644 сonfig-data-executor/pom.xml create mode 100644 сonfig-data-executor/src/main/java/org/micord/Main.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/config/ArangoDBConnection.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/config/AtomikosConfig.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/config/DatabaseConnection.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/config/HttpClientConfig.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/config/S3HttpConnection.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/controller/ApiController.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/exceptions/FileNotModifiedException.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/AqlConnectionParams.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/AqlRequest.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/Request.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/RequestArgument.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/Requests.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/S3ConnectionParams.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/S3Request.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/SqlConnectionParams.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/models/SqlRequest.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/service/ApiService.java create mode 100644 сonfig-data-executor/src/main/java/org/micord/utils/ConfigLoader.java create mode 100644 сonfig-data-executor/src/main/resources/application.yml 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