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.ArangoDB;
import com.arangodb.ArangoDBException; import com.arangodb.ArangoDBException;
import com.arangodb.ArangoDatabase; import com.arangodb.ArangoDatabase;
import org.micord.models.AqlConnectionParams; import org.micord.models.requests.AqlConnectionParams;
/** /**
* @author Maksim Tereshin * @author Maksim Tereshin

View file

@ -1,38 +1,29 @@
package org.micord.config; package org.micord.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager;
/**
* @author Maksim Tereshin
*/
@Configuration @Configuration
@EnableTransactionManagement @EnableTransactionManagement
public class AtomikosConfig { public class AtomikosConfig {
@Bean @Bean(initMethod = "init", destroyMethod = "close")
public UserTransaction userTransaction() throws Throwable { public UserTransactionManager userTransactionManager() {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(300);
return userTransactionImp;
}
@Bean
public TransactionManager atomikosTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager(); UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true); userTransactionManager.setForceShutdown(true);
return userTransactionManager; return userTransactionManager;
} }
@Bean @Bean
public JtaTransactionManager transactionManager() throws Throwable { public JtaTransactionManager transactionManager() {
return new JtaTransactionManager(userTransaction(), atomikosTransactionManager()); JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(userTransactionManager());
jtaTransactionManager.setUserTransaction(userTransactionManager());
return jtaTransactionManager;
} }
} }

View file

@ -1,7 +1,7 @@
package org.micord.config; package org.micord.config;
import com.atomikos.jdbc.AtomikosDataSourceBean; import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.micord.models.SqlConnectionParams; import org.micord.models.requests.SqlConnectionParams;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View file

@ -1,7 +1,7 @@
package org.micord.config; package org.micord.config;
import org.micord.models.S3ConnectionParams; import org.micord.models.requests.S3ConnectionParams;
import org.micord.models.S3Request; import org.micord.models.requests.S3Request;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;

View file

@ -1,23 +1,28 @@
package org.micord.controller; package org.micord.controller;
import java.io.File; import org.micord.enums.ConfigType;
import java.io.FileInputStream; import org.micord.exceptions.ValidationException;
import java.io.FileNotFoundException; import org.micord.models.requests.DownloadCSVRequest;
import java.io.IOException;
import java.util.List;
import org.micord.models.DownloadCSVRequest;
import org.micord.service.ApiService; import org.micord.service.ApiService;
import org.micord.service.ValidationService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; 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. * REST Controller for API operations.
*/ */
@ -30,38 +35,91 @@ public class ApiController {
@Autowired @Autowired
private ApiService apiService; private ApiService apiService;
@Autowired
private ValidationService validationService;
@PostMapping("/block") @PostMapping("/block")
public ResponseEntity<?> block(@RequestBody List<String> ids) throws FileNotFoundException { public ResponseEntity<?> block(@RequestBody List<String> ids) {
logger.debug("Starting block process for ids: {}", ids); try {
apiService.process("block", ids); validationService.validateAll(ids);
logger.debug("Finished block process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно."); logger.debug("Starting block process for ids: {}", ids);
apiService.process(ConfigType.BLOCK, ids);
logger.debug("Finished block process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
} }
@PostMapping("/unblock") @PostMapping("/unblock")
public ResponseEntity<?> unblock(@RequestBody List<String> ids) throws FileNotFoundException { public ResponseEntity<?> unblock(@RequestBody List<String> ids) {
logger.debug("Starting unblock process for ids: {}", ids); try {
apiService.process("unblock", ids); validationService.validateAll(ids);
logger.debug("Finished unblock process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно."); logger.debug("Starting unblock process for ids: {}", ids);
apiService.process(ConfigType.UNBLOCK, ids);
logger.debug("Finished unblock process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
} }
@PostMapping("/removeFromSystem") @PostMapping("/removeFromSystem")
public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids) public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids) {
throws FileNotFoundException { try {
logger.debug("Starting removeFromSystem process for ids: {}", ids); validationService.validateAll(ids);
apiService.process("removeFromSystem", ids);
logger.debug("Finished removeFromSystem process for ids: {}", ids); logger.debug("Starting removeFromSystem process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно."); 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") @PostMapping("/removeFromCallList")
public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids) public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids) {
throws FileNotFoundException { try {
logger.debug("Starting removeFromCallList process for ids: {}", ids); validationService.validateAll(ids);
apiService.process("removeFromCallList", ids);
logger.debug("Finished removeFromCallList process for ids: {}", ids); logger.debug("Starting removeFromCallList process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно."); 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") @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)); InputStreamResource resource = new InputStreamResource(new FileInputStream(csvFile));
logger.debug("Finished downloadCSV process for request: {}. Sending to user...", request.getType()); logger.debug("Finished downloadCSV process for request: {}. Sending to user...", request.getType());
@ -89,7 +147,7 @@ public class ApiController {
@GetMapping("/listDownloadTypes") @GetMapping("/listDownloadTypes")
public ResponseEntity<?> listDownloadTypes() public ResponseEntity<?> listDownloadTypes()
throws FileNotFoundException { throws FileNotFoundException {
List<String> downloadCSVTypes = apiService.getDownloadTypes("downloadCSV"); List<String> downloadCSVTypes = apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV);
return ResponseEntity.ok(downloadCSVTypes); 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; package org.micord.models;
import lombok.Getter;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
public class CachedConfig { @Getter
private final Requests config; public class CachedConfig<T> {
private final T config;
private final FileTime modifiedTime; private final FileTime modifiedTime;
public CachedConfig(Requests config, FileTime modifiedTime) { public CachedConfig(T config, FileTime modifiedTime) {
this.config = config; this.config = config;
this.modifiedTime = modifiedTime; 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; package org.micord.service;
import org.micord.models.*; import org.micord.enums.ConfigType;
import org.micord.utils.ConfigLoader; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -10,7 +12,8 @@ import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -18,22 +21,22 @@ public class ApiService {
private static final Logger logger = LoggerFactory.getLogger(ApiService.class); private static final Logger logger = LoggerFactory.getLogger(ApiService.class);
@Autowired
private ConfigLoader configLoader;
@Autowired @Autowired
private RequestService sqlAndAqlService; private RequestService sqlAndAqlService;
@Autowired @Autowired
private DownloadService downloadService; private DownloadService downloadService;
public void process(String methodName, List<String> ids) throws FileNotFoundException { @Autowired
Requests config = getConfig(methodName); private ConfigService configService;
public void process(ConfigType methodName, List<String> ids) throws FileNotFoundException {
Requests config = configService.getConfig(methodName, Requests.class);
sqlAndAqlService.processSqlAndAqlRequests(config, ids); sqlAndAqlService.processSqlAndAqlRequests(config, ids);
} }
public File download(String methodName, DownloadCSVRequest request) throws IOException { public File download(ConfigType methodName, DownloadCSVRequest request) throws IOException {
Requests config = getConfig(methodName); Requests config = configService.getConfig(methodName, Requests.class);
String type = request.getType(); String type = request.getType();
List<String> ids = Optional.ofNullable(request.getIds()) List<String> ids = Optional.ofNullable(request.getIds())
@ -48,22 +51,12 @@ public class ApiService {
return downloadService.download(selectedRequest, ids, request.getStartDate(), request.getEndDate()); return downloadService.download(selectedRequest, ids, request.getStartDate(), request.getEndDate());
} }
public List<String> getDownloadTypes(String methodName) throws FileNotFoundException { public List<String> getDownloadTypes(ConfigType methodName) throws FileNotFoundException {
Requests config = getConfig(methodName); Requests config = configService.getConfig(methodName, Requests.class);
return config.getDownloadRequests().stream() return config.getDownloadRequests().stream()
.map(DownloadRequest::getDownloadRequestType) .map(DownloadRequest::getDownloadRequestType)
.collect(Collectors.toList()); .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; package org.micord.service;
import org.micord.config.DatabaseConnection; import org.micord.config.DatabaseConnection;
import org.micord.models.DownloadRequest; import org.micord.models.requests.DownloadRequest;
import org.micord.models.RequestArgument; import org.micord.models.requests.RequestArgument;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View file

@ -1,5 +1,22 @@
package org.micord.service; 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.HttpURLConnection;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
@ -12,23 +29,6 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; 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 * @author Maksim Tereshin
*/ */
@ -44,16 +44,20 @@ public class RequestService {
logger.debug("Starting processing of S3 requests"); logger.debug("Starting processing of S3 requests");
if (s3Requests != null && !s3Requests.isEmpty()) { if (s3Requests != null && !s3Requests.isEmpty()) {
List<CompletableFuture<Void>> requestFutures = s3Requests.stream() 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(); .toList();
CompletableFuture.allOf(requestFutures.toArray(new CompletableFuture[0])) CompletableFuture.allOf(requestFutures.toArray(new CompletableFuture[0]))
.thenRun(() -> logger.info("Successfully processed all S3 requests."))
.whenComplete((result, ex) -> { .whenComplete((result, ex) -> {
if (ex != null) { if (ex != null) {
logger.error("Error processing S3 requests", ex); logger.error("Error processing S3 requests", ex);
} else { throw new RuntimeException("Error processing S3 requests", ex);
logger.info("Successfully processed all S3 requests.");
} }
}); });
} }
@ -81,10 +85,16 @@ public class RequestService {
} }
catch (SQLException e) { catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", 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 -> { files.forEach(file -> {
HttpRequest httpRequest; HttpRequest httpRequest;
logger.debug("Starting building HTTP request for S3 request"); logger.debug("Starting building HTTP request for S3 request");
@ -92,7 +102,7 @@ public class RequestService {
if (file == null || file.isBlank()) { if (file == null || file.isBlank()) {
logger.warn("Skipping invalid file path: {}", file); logger.warn("Skipping invalid file path: {}", file);
return; throw new RuntimeException("Invalid file path");
} }
try { try {
httpRequest = S3HttpConnection.buildHttpRequest(request, file); httpRequest = S3HttpConnection.buildHttpRequest(request, file);
@ -167,6 +177,7 @@ public class RequestService {
} }
catch (SQLException e) { catch (SQLException e) {
logger.error("SQL execution failed for query: {}", query, 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; 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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -10,14 +18,6 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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 * @author Maksim Tereshin
@ -31,7 +31,9 @@ public class ConfigLoader {
@Value("${configDirectory}") @Value("${configDirectory}")
private String 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"; String fileName = methodName + ".xml";
if (configDirectory == null) { if (configDirectory == null) {
@ -47,14 +49,14 @@ public class ConfigLoader {
if (cachedConfig == null || !currentModifiedTime.equals(cachedConfig.getModifiedTime())) { if (cachedConfig == null || !currentModifiedTime.equals(cachedConfig.getModifiedTime())) {
// Load the updated configuration // Load the updated configuration
JAXBContext jaxbContext = JAXBContext.newInstance(Requests.class); JAXBContext jaxbContext = JAXBContext.newInstance(configClass);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Requests loadedConfig = (Requests) unmarshaller.unmarshal(configFile); T loadedConfig = unmarshalConfig(configFile, configClass);
cachedConfigs.put(methodName, new CachedConfig(loadedConfig, currentModifiedTime)); cachedConfigs.put(methodName, new CachedConfig(loadedConfig, currentModifiedTime));
return Optional.of(loadedConfig); return Optional.of(loadedConfig);
} }
else { 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 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));
}
} }