Add exceptions, add validator service

This commit is contained in:
Maksim Tereshin 2024-11-29 14:04:51 +01:00
parent 8a7422286d
commit 1bb9a51e73
No known key found for this signature in database
29 changed files with 844 additions and 121 deletions

View file

@ -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

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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<String> ids) throws FileNotFoundException {
public ResponseEntity<?> block(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
logger.debug("Starting block process for ids: {}", ids);
apiService.process("block", 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<String> ids) throws FileNotFoundException {
public ResponseEntity<?> unblock(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
logger.debug("Starting unblock process for ids: {}", ids);
apiService.process("unblock", 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<String> ids)
throws FileNotFoundException {
public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
logger.debug("Starting removeFromSystem process for ids: {}", ids);
apiService.process("removeFromSystem", 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<String> ids)
throws FileNotFoundException {
public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
logger.debug("Starting removeFromCallList process for ids: {}", ids);
apiService.process("removeFromCallList", 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<String> downloadCSVTypes = apiService.getDownloadTypes("downloadCSV");
List<String> downloadCSVTypes = apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV);
return ResponseEntity.ok(downloadCSVTypes);
}

View file

@ -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;
}
}

View file

@ -0,0 +1,17 @@
package org.micord.exceptions;
import lombok.Getter;
import java.util.Map;
@Getter
public class ValidationException extends RuntimeException {
private final Map<String, String> validationDetails;
public ValidationException(String message, Map<String, String> validationDetails) {
super(message);
this.validationDetails = validationDetails;
}
}

View file

@ -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<T> {
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;
}
}

View file

@ -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;
}
}

View file

@ -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<AqlRequestCollection> aqlRequestCollections;
@XmlElement(name = "AqlConnectionParams")
public AqlConnectionParams getAqlConnectionParams() {
return aqlConnectionParams;
}
@XmlElementWrapper(name = "AqlRequestCollections")
@XmlElement(name = "AqlRequestCollection")
public List<AqlRequestCollection> getAqlRequestCollections() {
return aqlRequestCollections;
}
}

View file

@ -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;
}
}

View file

@ -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<RequestArgument> requestArguments;
private String requestURL;
@XmlElement(name = "RequestArgument")
public List<RequestArgument> getRequestArguments() {
return requestArguments;
}
@XmlElement(name = "RequestURL")
public String getRequestURL() {
return requestURL;
}
}

View file

@ -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<String> ids;
private LocalDate startDate;
private LocalDate endDate;
}

View file

@ -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;
}
}

View file

@ -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<RequestArgument> requestArguments;
private String requestURL;
@XmlElement(name = "RequestArgument")
public List<RequestArgument> getRequestArguments() {
return requestArguments;
}
@XmlElement(name = "RequestURL")
public String getRequestURL() {
return requestURL;
}
}

View file

@ -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;
}
}

View file

@ -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<SqlRequest> sqlRequests;
private List<AqlRequest> aqlRequests;
private List<S3Request> s3Requests;
private List<DownloadRequest> downloadRequests;
@XmlElement(name = "DownloadRequest")
public List<DownloadRequest> getDownloadRequests() {
return downloadRequests;
}
@XmlElement(name = "SqlRequest")
public List<SqlRequest> getSqlRequests() {
return sqlRequests;
}
@XmlElement(name = "AqlRequest")
public List<AqlRequest> getAqlRequests() {
return aqlRequests;
}
@XmlElement(name = "S3Request")
public List<S3Request> getS3Requests() {
return s3Requests;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<String> validationColumns;
@XmlElement(name = "RequestURL")
public String getRequestURL() {
return requestURL;
}
@XmlElement(name = "SqlConnectionParams")
public SqlConnectionParams getSqlConnectionParams() {
return sqlConnectionParams;
}
@XmlAttribute(name = "validationColumns")
public List<String> getValidationColumns() {
return validationColumns;
}
@XmlAttribute(name = "idColumn")
public String getIdColumn() {
return idColumn;
}
}

View file

@ -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<ValidationRule> validationRules;
@XmlElement(name = "ValidationRule")
public List<ValidationRule> getValidationRules() {
return validationRules;
}
}

View file

@ -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<String> ids) throws FileNotFoundException {
Requests config = getConfig(methodName);
@Autowired
private ConfigService configService;
public void process(ConfigType methodName, List<String> 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<String> ids = Optional.ofNullable(request.getIds())
@ -48,22 +51,12 @@ public class ApiService {
return downloadService.download(selectedRequest, ids, request.getStartDate(), request.getEndDate());
}
public List<String> getDownloadTypes(String methodName) throws FileNotFoundException {
Requests config = getConfig(methodName);
public List<String> 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<Requests> optionalConfig = configLoader.loadConfigIfModified(methodName);
if (optionalConfig.isEmpty()) {
throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded.");
}
return optionalConfig.get();
}
}

View file

@ -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> T getConfig(ConfigType methodName, Class<T> configClass) throws FileNotFoundException {
logger.debug("Loading configuration for method: {}", methodName);
Optional<T> optionalConfig = configLoader.loadConfigIfModified(methodName, configClass);
if (optionalConfig.isEmpty()) {
throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded.");
}
return optionalConfig.get();
}
}

View file

@ -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;

View file

@ -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<CompletableFuture<Void>> 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);
}
}

View file

@ -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<String, Map<String, Object>> validate(List<String> 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<String, Map<String, Object>> validationResults = new HashMap<>();
while (resultSet.next()) {
String id = resultSet.getString(rule.getIdColumn());
Map<String, Object> 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<String, String> validateAll(List<String> ids) throws ValidationException, FileNotFoundException, SQLException {
ValidationRules config = configService.getConfig(ConfigType.VALIDATE_BLOCK, ValidationRules.class);
if (config.getValidationRules() == null || config.getValidationRules().isEmpty()) {
return null;
}
Map<String, Map<String, Object>> validationResults = new HashMap<>();
for (ValidationRule rule : config.getValidationRules()) {
validationResults.putAll(validate(ids, rule));
}
Map<String, String> invalidRecords = new HashMap<>();
validationResults.forEach((id, columnValues) -> {
List<String> 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;
}
}

View file

@ -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<Requests> loadConfigIfModified(String methodName) {
public <T> Optional<T> loadConfigIfModified(ConfigType configType, Class<T> 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<T>) Optional.of(cachedConfigs.get(methodName).getConfig());
}
}
@ -67,4 +69,10 @@ public class ConfigLoader {
return Optional.empty(); // Return empty if unmarshalling fails
}
}
private <T> T unmarshalConfig(File configFile, Class<T> configClass) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(configClass);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return configClass.cast(unmarshaller.unmarshal(configFile));
}
}