diff --git a/src/main/java/com/bsmlab/dfx/agent/config/AgentConfigDto.java b/src/main/java/com/bsmlab/dfx/agent/config/AgentConfigDto.java new file mode 100644 index 0000000..b07b4d2 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/config/AgentConfigDto.java @@ -0,0 +1,135 @@ +package com.bsmlab.dfx.agent.config; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AgentConfigDto { + private String description; + private String myHostId; + private int myListenPort; + private List knownAgentList; + private StatusChecker statusChecker; + private List dataSourceConfig; + private List sqlMapperLocations; + private DropBoxConfig dropBoxConfig; + private PostmanConfig postmanConfig; + private LoggingConfig logging; + + + // ============================ INNER CLASSES ============================ + @Data + public static class KnownAgent { + private String hostId; + private String hostName; + private int listenPort; + private List dropBoxIdList; + } + + @Data + public static class StatusChecker { + private String cron; + } + + @Data + public static class DataSourceConfig { + private String dataSourceId; + private String driverClassName; + private String url; + private String username; + private String password; + private int initialSize; + private int minIdle; + private int maxIdle; + private int maxTotal; + private int maximumRowForTransaction; + } + + @Data + public static class DropBoxConfig { + private String receivedMessageStorageRoot; + private String processedMessageStorageRoot; + private String failureMessageStorageRoot; + private List dropBoxList; + private int threadPoolSize; + private int retentionDaysOfProcessedMessage; + } + + @Data + public static class DropBox { + private String dropBoxId; + private TaskType taskType; + private String dataSourceId; + private String sqlId; + private String saveDirectoryRoot; + private String description; + } + + @Data + public static class PostmanConfig { + private int threadPoolSize; + private List postmanList; + } + + @Data + public static class Postman { + private String postmanId; + private TaskType taskType; + private PostmanAction action; + private PostmanMessage message; + private String recipientHostId; + private String recipientDropBoxId; + private List routingHostIdList; + private String description; + } + + @Data + public static class PostmanAction { + private ActionType type; + private String command; + private List parametersKeyList; + private String cron; + } + + @Data + public static class PostmanMessage { + private MessageType messageType; + private String dataSourceId; + private String sqlId; + private String watchDirectory; + private String metaDataDataSourceId; + private String metaDataSqlId; + private String metaDropBoxId; + private String postProcessingSqlId; + } + + public static enum ActionType { + TRIGGER, + SCHEDULED + } + + public static enum MessageType { + TRANSFER_DB_TO_DB, + TRANSFER_FILE; + } + + public static enum TaskType { + DB_READ_THEN_SEND, + FILE_READ_THEN_SEND, + RECEIVE_DB_TO_DB_SAVE, + RECEIVE_FILE + } + + @Data + public static class LoggingConfig { + private String logDirectory; + private String logFileName; + private String logName; + private int maxHistory; + private String logPattern; + private String rootLogLevel; + private List> packages; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/listener/dto/AckDto.java b/src/main/java/com/bsmlab/dfx/agent/listener/dto/AckDto.java new file mode 100644 index 0000000..3350d2c --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/listener/dto/AckDto.java @@ -0,0 +1,32 @@ +package com.bsmlab.dfx.agent.listener.dto; + +import lombok.*; + +@ToString +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +/* + * Listen 프로세스 + * 1. listener 수신 + * 2. 수신 메시지 저장 (파일 저장 후 메시지 큐(DropBoxQueue) 에 추가) + * 3. 수신 메시지 저장 완료 Ack 전달 (result : RECEIVE_SUCCESS) + * 3.1 수신 메시지 저상 싪패시 Ack 전달 (result : RECEIVE_FAIL) + * 4. Worker 동작 (메지시 큐에서 가져오고 저장된 메시지를 규칙에 맞게 처리) + * 4.1 Worker 동작 성공시 sender 에게 처리완료 Ack 전달 (result : PROCESS_SUCCESS) + * 4.2 Worker 동작 실패시 sender 에게 처리오류 Ack 전달 (result : PROCESS_FAIL) + */ +public class AckDto { + private ResultType result; + private String resultText; + private String messageUuid; + + public static enum ResultType { + RECEIVE_SUCCESS, + RECEIVE_FAIL, + PROCESS_SUCCESS, + PROCESS_FAIL + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/listener/dto/CommandDto.java b/src/main/java/com/bsmlab/dfx/agent/listener/dto/CommandDto.java new file mode 100644 index 0000000..3a0a0b3 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/listener/dto/CommandDto.java @@ -0,0 +1,18 @@ +package com.bsmlab.dfx.agent.listener.dto; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CommandDto { + private CommandType commandType; + private String messageUuid; + + public static enum CommandType { + ALIVE, + INFORMATION, + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/listener/dto/ReceiveMessageDto.java b/src/main/java/com/bsmlab/dfx/agent/listener/dto/ReceiveMessageDto.java new file mode 100644 index 0000000..71988f1 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/listener/dto/ReceiveMessageDto.java @@ -0,0 +1,33 @@ +package com.bsmlab.dfx.agent.listener.dto; + +import com.bsmlab.dfx.agent.config.AgentConfigDto; +import lombok.*; + +import java.io.Serializable; +import java.util.List; + +@ToString +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ReceiveMessageDto implements Serializable { + private String senderHostId; + private long senderTimestamp; + private String messageUuid; + private AgentConfigDto.MessageType messageType; + private long receivedTimestamp; + private String recipientHostId; + private String recipientDropBoxId; + private String data; + private List attachFileList;// = new ArrayList<>(); + private ProcessStatus processStatus; + + public static enum ProcessStatus { + PROCESS_RECEIVED, + PROCESS_DONE, + PROCESS_NOT_POSSIBLE, + PROCESS_FAIL + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/MessageUtils.java b/src/main/java/com/bsmlab/dfx/agent/support/MessageUtils.java new file mode 100644 index 0000000..8408627 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/MessageUtils.java @@ -0,0 +1,190 @@ +package com.bsmlab.dfx.agent.support; + +import com.bsmlab.dfx.agent.config.AgentConfigDto; +import com.bsmlab.dfx.agent.listener.dto.AckDto; +import com.bsmlab.dfx.agent.listener.dto.CommandDto; +import com.bsmlab.dfx.agent.listener.dto.ReceiveMessageDto; +import com.bsmlab.dfx.agent.support.exception.IllegalMessageException; +import com.bsmlab.dfx.agent.support.exception.InCompleteMessageException; +import com.bsmlab.dfx.agent.support.exception.NullMessageException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Slf4j +public class MessageUtils { + private MessageUtils() {}; + + @SuppressWarnings("unchecked") + public static ReceiveMessageDto toReceiveMessageDto(String messageJsonString) throws IllegalMessageException, NullMessageException, InCompleteMessageException { + ReceiveMessageDto receiveMessageDto = null; + ObjectMapper objectMapper = new ObjectMapper(); + Map map = null; + try { + map = objectMapper.readValue(messageJsonString, new TypeReference>() {}); + if(map == null) { + throw new NullMessageException(""); + } + String senderHostId; + if(map.get("senderHostId") == null) { + throw new InCompleteMessageException("senderHostId 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("senderHostId")))) { + throw new InCompleteMessageException("senderHostId 값을 찾을 수 없습니다."); + } + else { + senderHostId = String.valueOf(map.get("senderHostId")); + } + long senderTimestamp = 0; + if(map.get("senderTimestamp") == null) { + throw new InCompleteMessageException("senderTimestamp 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("senderTimestamp")))) { + throw new InCompleteMessageException("senderTimestamp 값을 찾을 수 없습니다."); + } + else { + String senderTimeStampString = String.valueOf(map.get("senderTimestamp")); + try { + senderTimestamp = Long.parseLong(senderTimeStampString); + } catch (NumberFormatException e) { + throw new InCompleteMessageException("senderTimestamp 값의 형식이 숫자형식이 아닙니다. " + senderTimeStampString); + } + } + String messageUuid; + if(map.get("messageUuid") == null) { + throw new InCompleteMessageException("messageUuid 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("messageUuid")))) { + throw new InCompleteMessageException("messageUuid 값을 찾을 수 없습니다."); + } + else { + messageUuid = String.valueOf(map.get("messageUuid")); + try { + UUID.fromString(messageUuid); + } + catch (IllegalArgumentException e) { + throw new InCompleteMessageException("messageUuid 값의 형식이 숫자형식이 아닙니다. " + messageUuid); + } + } + AgentConfigDto.MessageType messageType; + if(map.get("messageType") == null) { + throw new InCompleteMessageException("messageType 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("messageType")))) { + throw new InCompleteMessageException("messageType 값을 찾을 수 없습니다."); + } + else { + String messageTypeString = String.valueOf(map.get("messageType")); + if(!EnumUtils.isValidEnum(AgentConfigDto.MessageType.class, messageTypeString)) { + throw new InCompleteMessageException("messageType 값이 옳바르지 않습니다. " + messageTypeString); + } + messageType = EnumUtils.getEnum(AgentConfigDto.MessageType.class, messageTypeString); + } + long receivedTimestamp = 0; + if(map.get("receivedTimestamp") == null) { + throw new InCompleteMessageException("receivedTimestamp 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("receivedTimestamp")))) { + throw new InCompleteMessageException("receivedTimestamp 값을 찾을 수 없습니다."); + } + else { + String receivedTimestampString = String.valueOf(map.get("receivedTimestamp")); + try { + receivedTimestamp = Long.parseLong(receivedTimestampString); + } catch (NumberFormatException e) { + throw new InCompleteMessageException("receivedTimestamp 값의 형식이 숫자형식이 아닙니다. " + receivedTimestampString); + } + } + String recipientHostId; + if(map.get("recipientHostId") == null) { + throw new InCompleteMessageException("recipientHostId 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("recipientHostId")))) { + throw new InCompleteMessageException("recipientHostId 값을 찾을 수 없습니다."); + } + else { + recipientHostId = String.valueOf(map.get("recipientHostId")); + } + String recipientDropBoxId; + if(map.get("recipientDropBoxId") == null) { + throw new InCompleteMessageException("recipientDropBoxId 엘리먼트를 찾을 수 없습니다."); + } + else if(StringUtils.isBlank(String.valueOf(map.get("recipientDropBoxId")))) { + throw new InCompleteMessageException("recipientDropBoxId 값을 찾을 수 없습니다."); + } + else { + recipientDropBoxId = String.valueOf(map.get("recipientDropBoxId")); + } + String dataString; + if(map.get("data") == null) { + throw new InCompleteMessageException("data 엘리먼트를 찾을 수 없습니다."); + } + else if(!(map.get("data") instanceof String)) { + throw new InCompleteMessageException("data 엘리먼트의 데이터가 문자 타입이 아닙니다."); + } + else { + dataString = String.valueOf(map.get("data")); + } + receiveMessageDto = ReceiveMessageDto.builder() + .senderHostId(senderHostId).senderTimestamp(senderTimestamp) + .messageUuid(messageUuid).messageType(messageType).receivedTimestamp(receivedTimestamp) + .recipientHostId(recipientHostId).recipientDropBoxId(recipientDropBoxId) + .data(dataString).attachFileList(new ArrayList<>()).processStatus(ReceiveMessageDto.ProcessStatus.PROCESS_RECEIVED) + .build(); + } + catch(JsonProcessingException e) { + throw new IllegalMessageException(e.getMessage()); + } + return receiveMessageDto; + } + + public static AckDto toAckDto(String messageJsonString) throws NullMessageException, IllegalMessageException { + ObjectMapper objectMapper = new ObjectMapper(); + Map map = null; + AckDto ackDto; + try { + map = objectMapper.readValue(messageJsonString, new TypeReference>() {}); + if(map == null) { + throw new NullMessageException(""); + } + else { + AckDto.ResultType result = EnumUtils.getEnum(AckDto.ResultType.class, String.valueOf(map.get("result"))); + String resultText = String.valueOf(map.get("resultText")); + String messageUuid = String.valueOf(map.get("messageUuid")); + ackDto = AckDto.builder().result(result).resultText(resultText).messageUuid(messageUuid).build(); + } + } + catch(JsonProcessingException e) { + throw new IllegalMessageException(e.getMessage()); + } + return ackDto; + } + + public static CommandDto toCommandDto(String messageJsonString) throws IllegalMessageException, NullMessageException { + CommandDto commandDto; + ObjectMapper objectMapper = new ObjectMapper(); + Map map = null; + try { + map = objectMapper.readValue(messageJsonString, new TypeReference>() {}); + if(map == null) { + throw new NullMessageException("command message json string is invalid"); + } + else { + CommandDto.CommandType commandType = EnumUtils.getEnum(CommandDto.CommandType.class, String.valueOf(map.get("commandType"))); + String messageUuid = String.valueOf(map.get("messageUuid")); + commandDto = CommandDto.builder().commandType(commandType).messageUuid(messageUuid).build(); + } + } catch (JsonProcessingException e) { + throw new IllegalMessageException(e.getMessage()); + } + return commandDto; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/ServletUtils.java b/src/main/java/com/bsmlab/dfx/agent/support/ServletUtils.java new file mode 100644 index 0000000..a0f49e3 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/ServletUtils.java @@ -0,0 +1,16 @@ +package com.bsmlab.dfx.agent.support; + +import jakarta.servlet.http.HttpServletRequest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.stream.Collectors; + +public class ServletUtils { + private ServletUtils() {}; + + public static String getBodyString(HttpServletRequest request) throws IOException { + String bodyString = new BufferedReader(request.getReader()).lines().collect(Collectors.joining("\n")); + return bodyString; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/exception/DfxException.java b/src/main/java/com/bsmlab/dfx/agent/support/exception/DfxException.java new file mode 100644 index 0000000..974ef19 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/exception/DfxException.java @@ -0,0 +1,19 @@ +package com.bsmlab.dfx.agent.support.exception; + +public class DfxException extends Exception { + protected String additionalMessage; + + public DfxException(String additionalMessage) { + this.additionalMessage = additionalMessage; + } + + @Override + public String getMessage() { + return "cannot find message contents. " + this.additionalMessage; + } + + @Override + public String getLocalizedMessage() { + return "파싱한 메시지의 내용을 찾을 수 없습니다. " + this.additionalMessage; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/exception/IllegalMessageException.java b/src/main/java/com/bsmlab/dfx/agent/support/exception/IllegalMessageException.java new file mode 100644 index 0000000..ef9404c --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/exception/IllegalMessageException.java @@ -0,0 +1,17 @@ +package com.bsmlab.dfx.agent.support.exception; + +public class IllegalMessageException extends DfxException { + public IllegalMessageException(String additionalMessage) { + super(additionalMessage); + } + + @Override + public String getMessage() { + return "cannot parse json message." + this.additionalMessage; + } + + @Override + public String getLocalizedMessage() { + return "json 메시지를 파싱할 수 없습니다." + this.additionalMessage; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/exception/InCompleteMessageException.java b/src/main/java/com/bsmlab/dfx/agent/support/exception/InCompleteMessageException.java new file mode 100644 index 0000000..cb0e8ab --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/exception/InCompleteMessageException.java @@ -0,0 +1,17 @@ +package com.bsmlab.dfx.agent.support.exception; + +public class InCompleteMessageException extends DfxException { + public InCompleteMessageException(String additionalMessage) { + super(additionalMessage); + } + + @Override + public String getMessage() { + return "incomplete message." + this.additionalMessage; + } + + @Override + public String getLocalizedMessage() { + return "메시지가 불완전합니다." + this.additionalMessage; + } +} diff --git a/src/main/java/com/bsmlab/dfx/agent/support/exception/NullMessageException.java b/src/main/java/com/bsmlab/dfx/agent/support/exception/NullMessageException.java new file mode 100644 index 0000000..a23e3a4 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/agent/support/exception/NullMessageException.java @@ -0,0 +1,17 @@ +package com.bsmlab.dfx.agent.support.exception; + +public class NullMessageException extends DfxException { + public NullMessageException(String addtitionalMessage) { + super(addtitionalMessage); + } + + @Override + public String getMessage() { + return "cannot find message contents." + this.additionalMessage; + } + + @Override + public String getLocalizedMessage() { + return "파싱한 메시지의 내용을 찾을 수 없습니다." + this.additionalMessage; + } +} diff --git a/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/ListenerController.java b/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/ListenerController.java new file mode 100644 index 0000000..c2bd0c7 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/ListenerController.java @@ -0,0 +1,46 @@ +package com.bsmlab.dfx.dfxconsole.app.communicate; + +import com.bsmlab.dfx.agent.listener.dto.AckDto; +import com.bsmlab.dfx.agent.support.ServletUtils; +import com.bsmlab.dfx.dfxconsole.app.communicate.service.ListenerService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; + +import java.io.IOException; + +@Controller +@Slf4j +@RequiredArgsConstructor +public class ListenerController { + private final ListenerService listenerService; + + /** + * 에이전트로부터 메시지를 수신한다. + * + * @param request + * @return + */ + @PostMapping(value = "/listen") + public AckDto listener(HttpServletRequest request) { + AckDto ackDto = AckDto.builder().build(); + return ackDto; + } + + /** + * 에이전트로부터 상태 점검 등 command 수신한다. + */ + @PostMapping(value = "/command") + public AckDto command(HttpServletRequest request) { + AckDto ackDto = AckDto.builder().build(); + try { + String bodyString = ServletUtils.getBodyString(request); + ackDto = listenerService.receiveCommand(bodyString); + } catch (IOException e) { + ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).messageUuid("").build(); + } + return ackDto; + } +} \ No newline at end of file diff --git a/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/service/ListenerService.java b/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/service/ListenerService.java new file mode 100644 index 0000000..dca2ac1 --- /dev/null +++ b/src/main/java/com/bsmlab/dfx/dfxconsole/app/communicate/service/ListenerService.java @@ -0,0 +1,32 @@ +package com.bsmlab.dfx.dfxconsole.app.communicate.service; + +import com.bsmlab.dfx.agent.listener.dto.AckDto; +import com.bsmlab.dfx.agent.listener.dto.CommandDto; +import com.bsmlab.dfx.agent.support.MessageUtils; +import com.bsmlab.dfx.agent.support.exception.IllegalMessageException; +import com.bsmlab.dfx.agent.support.exception.NullMessageException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ListenerService { + public AckDto receiveCommand(String messageJsonString) { + AckDto ackDto; + CommandDto commandDto; + try { + commandDto = MessageUtils.toCommandDto(messageJsonString); + String resultText = null; + if(CommandDto.CommandType.ALIVE == commandDto.getCommandType()) { + resultText = "ALIVE"; + } + ackDto = AckDto.builder().result(AckDto.ResultType.PROCESS_SUCCESS).messageUuid(commandDto.getMessageUuid()).resultText(resultText).build(); + } + catch (IllegalMessageException | NullMessageException e) { + ackDto = AckDto.builder().result(AckDto.ResultType.RECEIVE_FAIL).resultText(e.getLocalizedMessage()).messageUuid("").build(); + } + return ackDto; + } +}