diff --git a/src/main/java/com/eactive/eai/custom/alarm/AlarmStateManager.java b/src/main/java/com/eactive/eai/custom/alarm/AlarmStateManager.java index 719a204..83e4e14 100644 --- a/src/main/java/com/eactive/eai/custom/alarm/AlarmStateManager.java +++ b/src/main/java/com/eactive/eai/custom/alarm/AlarmStateManager.java @@ -132,7 +132,7 @@ public class AlarmStateManager { payload = UmsRequestPayload.builder() .content(ObpMonitoringUmsTemplate.builder().message(event.getMessage()).build()) .cellphone(cellPhone).build(); - + logger.info("Alarm is disabled, UmsRequestPayload = {}", payload); usmService.send(payload); } diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsSendManager.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsSendManager.java index dfab3b5..396705f 100644 --- a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsSendManager.java +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsSendManager.java @@ -1,6 +1,7 @@ package com.eactive.eai.custom.alarm.ums; import java.util.Queue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -49,7 +50,17 @@ public class UmsSendManager { void sendMessage() throws Exception { UmsRequestPayload payload; while ((payload = queue.poll()) != null) { - umsService.send(payload); + final UmsRequestPayload finalPayload = payload; + CompletableFuture.runAsync(() -> { + UmsCallResult result = null; + try { + result = umsService.send(finalPayload); + } catch (Exception e) { + logger.error("UMS 전송 실패", e); + }finally { + logger.info(result); + } + }); } } diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java index 4368826..74777de 100644 --- a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java @@ -1,135 +1,154 @@ package com.eactive.eai.custom.alarm.ums; import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.nio.charset.StandardCharsets; + +import javax.annotation.PreDestroy; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.entity.EntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.springframework.stereotype.Component; -import org.springframework.util.StreamUtils; import com.eactive.eai.common.property.PropManager; import com.eactive.eai.common.util.Jsons; import com.eactive.eai.common.util.Logger; import com.eactive.eai.custom.alarm.ums.payload.UmsRequestPayload; import com.eactive.eai.custom.alarm.ums.payload.ValidationUtil; -import com.google.gson.JsonSyntaxException; @Component public class UmsService { - protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); - private final static String UMS_GROUP_PROP_NAME = "UMS"; - private final static String UMS_PROP_HOST = "host"; - private final static String UMS_PROP_URL = "url"; - private final static String UMS_PROP_API_KEY = "apiKey"; - private final static String UMS_URL = "http://172.31.35.144:9021"; - private final static String API_KEY = "7cca6d58-e4f7-474d-9edd-525806bc99ff"; - private final static String SEND_URL = "/ums/openapi/send"; - private final static String CONTENT_TYPE = "application/json; charset=utf-8"; - private final static String AUTHORIZATION_FMT = "Bearer %s"; + // 설정 상수 + private final static String UMS_GROUP_PROP_NAME = "UMS"; + private final static String UMS_PROP_HOST = "host"; + private final static String UMS_PROP_URL = "url"; + private final static String UMS_PROP_API_KEY = "apiKey"; + private final static String UMS_PROP_CONNECTION_TIMEOUT = "timeout.connection.ms"; + private final static String UMS_PROP_TIMEOUT = "timeout.ms"; + + // 기본값 + private final static String DEFAULT_UMS_URL = "http://172.31.35.144:9021"; + private final static String DEFAULT_SEND_URI = "/ums/openapi/send"; + private final static String DEFAULT_API_KEY = "7cca6d58-e4f7-474d-9edd-525806bc99ff"; + private final static String AUTHORIZATION_FMT = "Bearer %s"; - public Future send(UmsRequestPayload payload) throws Exception { + // 재사용되는 HttpClient (Thread-Safe) + private final CloseableHttpClient httpClient; - if (payload.getContent() == null) { - throw new IllegalArgumentException("content is null"); - } + public UmsService() { + // 커넥션 풀 설정: 매번 소켓을 열고 닫지 않고 재사용함 + HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setMaxConnTotal(100) // 전체 최대 커넥션 수 + .setMaxConnPerRoute(20) // 호스트당 최대 커넥션 수 + .setConnectionTimeToLive(TimeValue.ofMinutes(3)) // TTL + .setValidateAfterInactivity(TimeValue.ofSeconds(5)) // 🔥 중요 + .build(); - try { - ValidationUtil.validateOrThrow(payload); - } catch (Exception ex) { - logger.debug(String.format("UMS Request payload validation failed - %s", payload)); - throw ex; - } + this.httpClient = HttpClients.custom() + .setConnectionManager(cm) + .evictExpiredConnections() // 만료 제거 + .evictIdleConnections(TimeValue.ofSeconds(20)) // 20초 idle 제거 + .build(); + } - return call(payload); - } + public UmsCallResult send(UmsRequestPayload payload) throws Exception { + if (payload.getContent() == null) { + throw new IllegalArgumentException("content is null"); + } - private CloseableHttpClient createHttpClient() { - return HttpClients.createDefault(); - } + try { + ValidationUtil.validateOrThrow(payload); + } catch (Exception ex) { + logger.debug(String.format("UMS Request payload validation failed - %s", payload)); + throw ex; + } - private Future call(UmsRequestPayload requestPayload) throws Exception { + return call(payload); + } - ExecutorService executor = Executors.newSingleThreadExecutor(); - - String json = Jsons.GSON.toJson(requestPayload); - - String host = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_HOST, UMS_URL); - String url = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_URL, SEND_URL); - String apiKey = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_API_KEY, API_KEY); + private UmsCallResult call(UmsRequestPayload requestPayload) throws Exception { + // 1. 프로퍼티 로드 + PropManager prop = PropManager.getInstance(); + String host = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_HOST, DEFAULT_UMS_URL); + String uri = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_URL, DEFAULT_SEND_URI); + String apiKey = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_API_KEY, DEFAULT_API_KEY); + long connectionTimeoutMs = Long.parseLong(prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_CONNECTION_TIMEOUT, "3000")); + long timeoutMs = Long.parseLong(prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_TIMEOUT, "3000")); + // 2. 요청 생성 + HttpPost httpPost = new HttpPost(host + uri); + httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, apiKey)); + + String json = Jsons.GSON.toJson(requestPayload); + httpPost.setEntity(EntityBuilder.create() + .setText(json) + .setContentType(ContentType.APPLICATION_JSON) + .setContentEncoding("UTF-8") + .build()); - HttpPost httpPost = new HttpPost(host + url); - httpPost.setHeader("Content-Type", CONTENT_TYPE); - httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, apiKey)); - httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build()); + // 3. 타임아웃 설정 (설정파일 값 적용) + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeoutMs)) + .setResponseTimeout(Timeout.ofMilliseconds(timeoutMs)) + .build(); + httpPost.setConfig(config); - if (logger.isDebugEnabled()) { - try { - logger.debug("Header - {}", httpPost.getHeader("Content-Type")); - logger.debug("Header - {}", httpPost.getHeader("Authorization")); - logger.debug("Request Body : {}", - StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset())); - } catch (ProtocolException | IOException e) { - throw new RuntimeException(e); - } - } + if (logger.isDebugEnabled()) { + logger.debug("UMS Request to: {}", host + uri); + logger.debug("UMS Request Body: {}", json); + } - httpPost.setConfig(RequestConfig.custom().setConnectTimeout(Timeout.ofSeconds(3)) - .setResponseTimeout(Timeout.ofSeconds(3)).build()); - Future future; - try (CloseableHttpClient httpClient = createHttpClient()) { - future = executor.submit(() -> { - return httpClient.execute(httpPost, res -> { - try { - UmsCallResult result; - String body = EntityUtils.toString(res.getEntity()); - if (logger.isDebugEnabled()) { - logger.debug("Response - {}", body); - } - result = Jsons.GSON.fromJson(body, UmsCallResult.class); - result.setUmsMessageId(requestPayload.getMessageIdentityNo()); - result.setSuccess(true); - return result; - } catch (JsonSyntaxException e) { - logger.error("UMS Call error(JSON Syntax)", e); - return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo()) - .isSuccess(false).throwable(e).build(); - } catch (Exception e) { - logger.error("UMS Call error", e); - return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo()) - .isSuccess(false).throwable(e).build(); - } finally { - logger.debug("Response status - {}", res.getCode()); + // 4. 실행 (HttpClient 재사용) + try { + return httpClient.execute(httpPost, response -> { + String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + + if (logger.isDebugEnabled()) { + logger.debug("UMS Response Status: {}", response.getCode()); + logger.debug("UMS Response Body: {}", body); + } - if (res.getCode() != 200) { - Arrays.asList(res.getHeaders()).forEach(o -> { - logger.warn("[Header] {}: {}", o.getName(), o.getValue()); - }); - } - } - }); - }); + UmsCallResult result = Jsons.GSON.fromJson(body, UmsCallResult.class); + result.setUmsMessageId(requestPayload.getMessageIdentityNo()); + + // HTTP 200인 경우만 성공으로 간주 (필요에 따라 조정) + result.setSuccess(response.getCode() == 200); + + if (response.getCode() != 200) { + logger.warn("UMS Call failed with status: {}", response.getCode()); + } + + return result; + }); + } catch (Exception e) { + logger.error("UMS call exception: " + e.getMessage(), e); + return UmsCallResult.builder() + .umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false) + .throwable(e) + .build(); + } + } - return future; - } catch (Exception e) { - logger.error("UMS call exception", e); - throw new Exception("UMS call failed", e); - } finally { - executor.shutdown(); - } - - } -} + @PreDestroy + public void destroy() { + try { + if (httpClient != null) { + httpClient.close(); + logger.info("UMS HttpClient closed."); + } + } catch (IOException e) { + logger.error("Error closing UMS HttpClient", e); + } + } +} \ No newline at end of file