Compare commits

...

16 Commits

Author SHA1 Message Date
curry772 2bbd3824ed Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-online.git 2026-08-05 13:05:42 +09:00
curry772 4ff3589557 서브모듈 인덱스 업데이트 2026-08-05 13:05:30 +09:00
Rinjae db2b34a355 "SBOM 생성 및 변환 추가 - CycloneDX -> Excel(xlsx) 지원 - Jenkins 단계에 SBOM 빌드 및 아티팩트 보관 연결" 2026-07-31 17:31:45 +09:00
curry772 d79bcf8430 서브모듈 인덱스 업데이트 (elink-online-common)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:05:48 +09:00
curry772 e049420ca3 내부 오류 시 ERROR_CODE_AUTH_FAIL로 설정되는 현상 수정 2026-07-30 13:09:34 +09:00
curry772 10bbd57c78 서브모듈 인덱스 업데이트 2026-07-30 09:15:34 +09:00
curry772 70c636d719 API 거래 uuid(TRANSACTION_UUID) 앞 prefix에 GroupInstId 추가 2026-07-27 10:41:59 +09:00
curry772 1b37fc5e1b GUID 생성 시 node 번호를 -Dinst.Name값을 이용하도록 변경 2026-07-24 17:02:36 +09:00
curry772 9a730dc0b7 Test Filter 수신 메시지 JsonNode 지원 2026-07-24 17:01:54 +09:00
curry772 0ff2259f78 서브모듈 인덱스 갱신 (elink-online-common, elink-online-core,
elink-online-transformer)
2026-07-24 09:01:12 +09:00
curry772 8231318f14 version info 생성 기능 추가 2026-07-23 11:44:35 +09:00
curry772 c44b331a32 DJBErpNsmapiAccessTokenService 토큰 만료시간 및 HTTP 클라이언트 재사용 수정
- 하드코딩된 30초 만료를 제거하고 oAuthCredentialVo.getIntervalSec() 기준으로
  복원 (미설정 시 24시간 폴백), 스케줄러 주기와 불일치하던 문제 해결
- execute() 호출마다 PoolingHttpClientConnectionManager/CloseableHttpClient를
  새로 만들고 즉시 닫던 것을, mTLS 여부+clientId 조합별로 1회만 생성해 재사용하도록
  변경. 이 서비스는 HttpClientAccessTokenServiceFactoryByDB에 싱글턴으로 캐시되어
  재사용되므로 인스턴스 필드로 안전하게 캐시 가능
2026-07-22 09:11:20 +09:00
curry772 88f774e6b5 더존 NSM API 토큰 발급 응답 데이터 필드 이름 수정 token -> access_token 2026-07-21 15:32:00 +09:00
curry772 a77f4d4ef0 message 타입 ObjectNode 대응 2026-07-21 10:49:39 +09:00
curry772 73a50e5db4 미확인 오류 로그 출력 수정 및 favicon.ico 처리하지않도록 변경 2026-07-20 11:13:27 +09:00
curry772 ebf3cfa98d 어댑터별 apikey 헤더명 동적 적용 2026-07-20 11:12:28 +09:00
15 changed files with 546 additions and 138 deletions
+1
View File
@@ -5,6 +5,7 @@ gradle.properties
src/main/generated src/main/generated
WebContent/generated WebContent/generated
src/main/resources/version.info
# Eclipse # # Eclipse #
.metadata .metadata
Vendored
+15
View File
@@ -73,6 +73,21 @@ pipeline {
} }
} }
} }
// SBOM(CycloneDX) -> xlsx. 산출물 eapim-online-sbom.xlsx 를 아티팩트로 보관.
// 실패해도 배포는 진행하도록 UNSTABLE 로만 표시한다.
stage('SBOM') {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
sh 'gradle sbomXlsx --no-daemon -Pprofile=weblogic'
}
}
post {
always {
archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/sbom/*.xlsx', fingerprint: true
}
}
}
} }
} }
+52
View File
@@ -5,6 +5,7 @@ plugins {
id 'groovy' id 'groovy'
id 'war' id 'war'
id 'com.diffplug.eclipse.apt' version '3.41.1' id 'com.diffplug.eclipse.apt' version '3.41.1'
id 'org.cyclonedx.bom' version '3.2.4'
} }
group 'com.eactvie' group 'com.eactvie'
@@ -172,6 +173,54 @@ task initDirs() {
file(generatedJavaDir).mkdirs() file(generatedJavaDir).mkdirs()
} }
// version.info 를 classpath 리소스로 생성해 WAR의 WEB-INF/classes에 포함시킨다.
// elink-online-common 등 공유 라이브러리 모듈이 아니라, 실제 배포 아티팩트(eapim-online.war)를
// 만드는 이 루트 프로젝트에서 생성해야 "지금 배포된 게이트웨이가 정확히 어느 커밋인지"를
// eapim-online 소스 변경 여부와 무관하게 항상 최신으로 반영한다.
// ToolsController(/manage/tools/version, elink-online-common 모듈)는 이 파일을
// Class.getResourceAsStream("/version.info")로 읽는데, WAR는 WEB-INF/classes와
// WEB-INF/lib 전체가 하나의 클래스로더를 공유하므로 어느 모듈의 클래스에서 조회하든 찾을 수 있다.
//
// elink-online-common/elink-online-core/... 는 별도 저장소를 참조하는 git submodule이라
// 루트(eapim-online)의 describe/dirty 만으로는 "어느 서브모듈이 바뀌었는지"를 알 수 없다
// (서브모듈 포인터가 커밋되고 나면 루트는 다시 clean 해져서 -dirty 흔적도 사라진다).
// 그래서 서브모듈 각각에 대해서도 describe를 실행해 module.<이름>=<결과> 형식으로 함께 기록한다.
def describeGit(File dir) {
try {
def proc = "git describe --tags --always --dirty".execute(null, dir)
// proc.text는 JVM/OS 기본 charset(Windows 환경에선 CP949 등)으로 stdout을 디코딩한다.
// git 태그명은 UTF-8 바이트이므로 기본 charset이 UTF-8이 아니면 여기서 한글이 깨진다.
// stream을 명시적으로 UTF-8로 읽어서 이 문제를 피한다.
def output = proc.inputStream.getText("UTF-8")
proc.waitFor()
return (proc.exitValue() == 0) ? output.trim() : "unknown"
} catch (Exception e) {
logger.warn("${dir} git describe 실행 불가, unknown 으로 대체: ${e.message}")
return "unknown"
}
}
task generateVersionInfo {
doLast {
def gitVersion = describeGit(projectDir)
def sb = new StringBuilder()
sb.append("version=${gitVersion}\n")
sb.append("buildTime=${new Date().format("yyyy-MM-dd HH:mm:ss")}\n")
subprojects.sort { it.name }.each { sub ->
if (file("${sub.projectDir}/.git").exists()) {
sb.append("module.${sub.name}=${describeGit(sub.projectDir)}\n")
}
}
def versionInfoFile = file("$projectDir/src/main/resources/version.info")
versionInfoFile.parentFile.mkdirs()
versionInfoFile.write(sb.toString(), "UTF-8")
}
}
processResources.dependsOn generateVersionInfo
eclipse { eclipse {
wtp { wtp {
component { component {
@@ -201,3 +250,6 @@ eclipse {
} }
} }
// CycloneDX SBOM -> xlsx 변환 (gradle sbomXlsx)
apply from: "$projectDir/gradle/sbom-xlsx.gradle"
+326
View File
@@ -0,0 +1,326 @@
/*
* CycloneDX SBOM(bom.json) -> Excel(xlsx) 변환 태스크.
*
* gradle sbomXlsx # cyclonedxBom 실행 후 변환
* gradle sbomXlsx -PsbomJson=path.json # 기존 bom.json 사용(cyclonedxBom 생략)
* gradle sbomXlsx -PsbomOut=out.xlsx # 출력 경로 지정
*
* buildscript 블록이 이 스크립트에만 적용되므로 POI 의존성이 메인 빌드
* classpath 나 WAR 산출물에는 포함되지 않는다.
*
* 시트: 요약 / WAR 기준
* 산출 기준은 war 태스크의 classpath(= runtimeClasspath) 이므로
* test·annotationProcessor·developmentOnly·compileOnly 의존은 모두 제외된다.
* bom.json 은 라이선스/해시/설명/직접-전이 판별을 위한 메타 소스로만 쓴다.
*/
buildscript {
repositories {
maven {
url "https://nexus.eactive.synology.me:8090/repository/maven-public/"
allowInsecureProtocol = true
}
mavenCentral()
}
dependencies {
classpath 'org.apache.poi:poi-ooxml:3.17'
}
}
import groovy.json.JsonSlurper
import org.apache.poi.ss.usermodel.BorderStyle
import org.apache.poi.ss.usermodel.FillPatternType
import org.apache.poi.ss.usermodel.HorizontalAlignment
import org.apache.poi.ss.usermodel.IndexedColors
import org.apache.poi.ss.usermodel.VerticalAlignment
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.xssf.usermodel.XSSFWorkbook
// 엑셀 셀 문자열 상한(32767)보다 여유를 둔 절단 길이
ext.SBOM_CELL_LIMIT = 32000
task sbomXlsx {
group = 'sbom'
description = 'CycloneDX bom.json 을 WAR 수록 기준 xlsx 로 변환한다'
// -PsbomJson 으로 기존 산출물을 지정하면 재생성하지 않는다
if (!project.hasProperty('sbomJson')) {
dependsOn 'cyclonedxBom'
}
doLast {
File src = resolveBomJson(project)
File out = project.hasProperty('sbomOut')
? project.file(project.property('sbomOut'))
: new File(project.buildDir, "reports/sbom/${sbomFileName(project)}")
out.parentFile.mkdirs()
def bom = new JsonSlurper().parse(src, 'UTF-8')
def deploy = collectDeployJars(project)
def warRows = joinWarRows(deploy.jars, indexComponents(bom))
def wb = new XSSFWorkbook()
def st = createStyles(wb)
writeSummarySheet(wb, st, bom, warRows, src, deploy.label)
writeWarSheet(wb, st, warRows)
out.withOutputStream { os -> wb.write(os) }
wb.close()
int unmatched = warRows.count { it.matched == 'N' }
logger.lifecycle("SBOM xlsx 생성: ${out.absolutePath} " +
"(배포 수록 ${warRows.size()}개, SBOM 미매칭 ${unmatched}개, 원본 ${src.name})")
}
}
/** 산출 파일명: 배포 패키지명 기준 (war 있으면 war 파일명, 없으면 project 이름[-버전]) */
String sbomFileName(Project p) {
def warTask = p.tasks.findByName('war')
if (warTask != null) {
String archive = warTask.archiveFileName.get()
return archive.replaceAll(/\.(war|jar|ear)$/, '') + '-sbom.xlsx'
}
String ver = (p.version == null || p.version.toString() in ['', 'unspecified']) ? '' : "-${p.version}"
return "${p.name}${ver}-sbom.xlsx"
}
/** bom.json 위치 결정: -PsbomJson > cyclonedxBom 산출 경로 후보 */
File resolveBomJson(Project p) {
if (p.hasProperty('sbomJson')) {
File f = p.file(p.property('sbomJson'))
if (!f.exists()) {
throw new GradleException("bom.json 없음: ${f.absolutePath}")
}
return f
}
def candidates = [
new File(p.buildDir, 'reports/cyclonedx/bom.json'),
new File(p.buildDir, 'reports/bom.json'),
]
File found = candidates.find { it.exists() }
if (found == null) {
throw new GradleException(
"bom.json 을 찾지 못했다. 확인한 경로: " + candidates*.absolutePath.join(', ') +
"\n'gradle cyclonedxBom' 실행 후 재시도하거나 -PsbomJson=<경로> 로 지정한다.")
}
return found
}
/**
* 실제 배포물에 packaging 되는 jar 목록.
* war 프로젝트는 war 태스크 classpath(= runtimeClasspath), 그 외는 runtimeClasspath 를
* 기준으로 하므로 test/annotationProcessor/developmentOnly/compileOnly 는 자동으로 빠진다.
*
* @return [label: 기준 설명, jars: 행 목록]
*/
Map collectDeployJars(Project p) {
def cfg = p.configurations.findByName('runtimeClasspath')
if (cfg == null) {
p.logger.warn("[${p.name}] runtimeClasspath 가 없어 배포 기준 시트를 비운다")
return [label: '(없음)', jars: []]
}
def warTask = p.tasks.findByName('war')
def files
String label
if (warTask != null) {
files = warTask.classpath.files
label = 'WAR WEB-INF/lib (war 태스크 classpath)'
} else {
files = cfg.files
label = 'runtimeClasspath (war 태스크 없음)'
}
def coordByFile = [:]
cfg.resolvedConfiguration.resolvedArtifacts.each { a ->
def id = a.moduleVersion.id
coordByFile[a.file] = [group: id.group, name: id.name, version: id.version]
}
def jars = files.findAll { it.name.endsWith('.jar') }.collect { f ->
def c = coordByFile[f]
[
file : f.name,
group : c?.group ?: '',
name : c?.name ?: f.name.replaceAll(/\.jar$/, ''),
version: c?.version ?: '',
coord : c ? "${c.group}:${c.name}:${c.version}".toString() : '',
]
}.sort { it.file }
return [label: label, jars: jars]
}
/** bom.json 컴포넌트를 'group:name:version' 키로 색인 (라이선스/해시/설명/직접-전이) */
Map indexComponents(bom) {
String rootRef = bom.metadata?.component?.'bom-ref'
Set directRefs = (bom.dependencies?.find { it.ref == rootRef }?.dependsOn ?: []) as Set
def index = [:]
bom.components?.each { c ->
def hashes = [:]
c.hashes?.each { h -> hashes[h.alg] = h.content }
def licenses = (c.licenses ?: []).collect { l ->
l.license?.id ?: l.license?.name ?: l.expression ?: ''
}.findAll { it }
index["${c.group ?: ''}:${c.name ?: ''}:${c.version ?: ''}".toString()] = [
direct : directRefs.contains(c.'bom-ref') ? '직접' : '전이',
licenses : licenses.join('; '),
licenseList: licenses.isEmpty() ? ['(미상)'] : licenses,
purl : c.purl ?: '',
sha256 : hashes['SHA-256'] ?: '',
sha1 : hashes['SHA-1'] ?: '',
description: c.description ?: '',
]
}
return index
}
/** WAR jar 목록에 SBOM 메타를 좌표로 결합 */
List joinWarRows(List warJars, Map index) {
def result = []
warJars.eachWithIndex { j, i ->
def m = j.coord ? index[j.coord] : null
result << [
no : i + 1,
file : j.file,
group : j.group,
name : j.name,
version : j.version,
direct : m?.direct ?: '',
licenses : m?.licenses ?: '',
licenseList: m?.licenseList ?: ['(미상)'],
purl : m?.purl ?: '',
sha256 : m?.sha256 ?: '',
sha1 : m?.sha1 ?: '',
matched : (m != null) ? 'Y' : 'N',
description: m?.description ?: '',
]
}
return result
}
Map createStyles(wb) {
def headFont = wb.createFont()
headFont.setBold(true)
headFont.setColor(IndexedColors.WHITE.getIndex())
def head = wb.createCellStyle()
head.setFont(headFont)
head.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex())
head.setFillPattern(FillPatternType.SOLID_FOREGROUND)
head.setAlignment(HorizontalAlignment.CENTER)
head.setVerticalAlignment(VerticalAlignment.CENTER)
head.setBorderBottom(BorderStyle.THIN)
def body = wb.createCellStyle()
body.setVerticalAlignment(VerticalAlignment.TOP)
def wrap = wb.createCellStyle()
wrap.setVerticalAlignment(VerticalAlignment.TOP)
wrap.setWrapText(true)
def labelFont = wb.createFont()
labelFont.setBold(true)
def label = wb.createCellStyle()
label.setFont(labelFont)
return [head: head, body: body, wrap: wrap, label: label]
}
/** 헤더 행 생성 + 폭 지정 + 틀고정 */
def writeHeader(sheet, style, List<String> headers, List<Integer> widths) {
def row = sheet.createRow(0)
row.setHeightInPoints(20f)
headers.eachWithIndex { h, i ->
def cell = row.createCell(i)
cell.setCellValue(h)
cell.setCellStyle(style)
sheet.setColumnWidth(i, widths[i] * 256)
}
sheet.createFreezePane(0, 1)
}
def cellOf(row, int idx, value, style) {
def cell = row.createCell(idx)
String s = (value == null) ? '' : value.toString()
if (s.length() > SBOM_CELL_LIMIT) {
s = s.substring(0, SBOM_CELL_LIMIT) + '…(생략)'
}
cell.setCellValue(s)
cell.setCellStyle(style)
return cell
}
def writeSummarySheet(wb, st, bom, List warRows, File src, String basisLabel) {
def sheet = wb.createSheet('요약')
def comp = bom.metadata?.component ?: [:]
def tool = bom.metadata?.tools?.components?.getAt(0)
Set licenseKinds = warRows.collectMany { it.licenseList } as Set
def items = [
['대상 프로젝트', "${comp.group ?: ''}:${comp.name ?: ''}:${comp.version ?: ''}"],
['산출 기준', "${basisLabel} — test/annotationProcessor/compileOnly 제외"],
['BOM 포맷', "${bom.bomFormat ?: ''} ${bom.specVersion ?: ''}"],
['serialNumber', bom.serialNumber ?: ''],
['생성 시각', bom.metadata?.timestamp ?: ''],
['생성 도구', tool ? "${tool.name} ${tool.version}" : ''],
['원본 파일', src.absolutePath],
['배포 수록 jar', warRows.size()],
[' └ 직접 의존', warRows.count { it.direct == '직접' }],
[' └ 전이 의존', warRows.count { it.direct == '전이' }],
[' └ SBOM 미매칭', warRows.count { it.matched == 'N' }],
['라이선스 종류', licenseKinds.size()],
['라이선스 미상', warRows.count { it.licenses.isEmpty() }],
]
writeHeader(sheet, st.head, ['항목', '값'], [30, 90])
items.eachWithIndex { item, i ->
def row = sheet.createRow(i + 1)
cellOf(row, 0, item[0], st.label)
cellOf(row, 1, item[1], st.body)
}
// 라이선스 분포 (요약 하단)
def byLicense = [:].withDefault { 0 }
warRows.each { r -> r.licenseList.each { lic -> byLicense[lic] = byLicense[lic] + 1 } }
def sorted = byLicense.entrySet().sort { a, b -> (b.value <=> a.value) ?: (a.key <=> b.key) }
int base = items.size() + 2
def hdr = sheet.createRow(base)
cellOf(hdr, 0, '라이선스', st.head)
cellOf(hdr, 1, 'jar 수', st.head)
sorted.eachWithIndex { e, i ->
def row = sheet.createRow(base + 1 + i)
cellOf(row, 0, e.key, st.body)
cellOf(row, 1, e.value, st.body)
}
}
/** 실제 배포물(WAR WEB-INF/lib) 기준 시트 */
def writeWarSheet(wb, st, List warRows) {
def sheet = wb.createSheet('WAR 기준')
def headers = ['No', 'jar 파일명', 'Group', 'Name', 'Version', '구분',
'License', 'purl', 'SHA-256', 'SHA-1', 'SBOM매칭', 'Description']
def widths = [6, 46, 32, 34, 16, 7, 30, 60, 40, 30, 10, 60]
writeHeader(sheet, st.head, headers, widths)
warRows.eachWithIndex { r, i ->
def row = sheet.createRow(i + 1)
cellOf(row, 0, r.no, st.body)
cellOf(row, 1, r.file, st.body)
cellOf(row, 2, r.group, st.body)
cellOf(row, 3, r.name, st.body)
cellOf(row, 4, r.version, st.body)
cellOf(row, 5, r.direct, st.body)
cellOf(row, 6, r.licenses, st.body)
cellOf(row, 7, r.purl, st.body)
cellOf(row, 8, r.sha256, st.body)
cellOf(row, 9, r.sha1, st.body)
cellOf(row, 10, r.matched, st.body)
cellOf(row, 11, r.description, st.wrap)
}
if (!warRows.isEmpty()) {
sheet.setAutoFilter(new CellRangeAddress(0, warRows.size(), 0, headers.size() - 1))
}
}
@@ -50,7 +50,8 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
@Autowired @Autowired
DJBApiAdapterService service; DJBApiAdapterService service;
EAIServerManager eaiServerManager;
/** /**
* DJErp OAuth 조기 적용을 위한 Controller * DJErp OAuth 조기 적용을 위한 Controller
* *
@@ -67,7 +68,7 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
"/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**", "/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**",
"/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**", "/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**",
"/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**", "/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**",
"/**/{path:^(?!oauth).*$}", "/**/{path:^(?!oauth).*$}/**", "/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}", "/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}/**",
}) })
public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse) public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws Exception { throws Exception {
@@ -83,17 +84,31 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI()); logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI());
} }
if (adptUri == null) {
logError(servletRequest);
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
"can not find Adapter Uri");
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
}
// Received time , jwhong // Received time , jwhong
long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함 long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함
String receivedTimeStr = String.valueOf(receivedTimeMillis); String receivedTimeStr = String.valueOf(receivedTimeMillis);
ResponseEntity<String> responseEntity = null;
Properties transactionProp = new Properties();
if(eaiServerManager == null)
eaiServerManager = EAIServerManager.getInstance();
String instid = eaiServerManager.getGroupInstId();
String uuid = instid+UUIDGenerator.getUUID().toString().replaceAll("-", "");
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
if (adptUri == null) {
//logError(servletRequest);
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
"can not find Adapter Uri");
logError(servletRequest, "HTTP_IN_NO_URI", MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
errorMsg, transactionProp, null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
}
String adapterGroupName = adptUri.getAdptGrpName(); String adapterGroupName = adptUri.getAdptGrpName();
String adapterName = adptUri.getAdptName(); String adapterName = adptUri.getAdptName();
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
@@ -101,6 +116,8 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
if (adapterVO == null) { if (adapterVO == null) {
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR,
"Adapter not found error"); "Adapter not found error");
logError(servletRequest, adapterGroupName, MessageUtil.ERROR_CODE_AP_ERROR,
errorMsg, transactionProp, null);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(errorMsg); .body(errorMsg);
} }
@@ -112,16 +129,10 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode); MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode);
String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT); String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);
ResponseEntity<String> responseEntity = null;
Properties transactionProp = new Properties();
String uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
TxSiftContext.begin(uuid); TxSiftContext.begin(uuid);
// jwhong, put api received time, eaiSvcCode // jwhong, put api received time, eaiSvcCode
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
String responseData = ""; String responseData = "";
try { try {
responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO, responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO,
@@ -150,14 +161,14 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg); responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
} else if (e instanceof JwtAuthException) { } else if (e instanceof JwtAuthException) {
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName, responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
transactionProp, MessageUtil.ERROR_CODE_AP_ERROR, e, adptMsgType, encode, errorResponseFormat); transactionProp, MessageUtil.ERROR_CODE_AUTH_FAIL, e, adptMsgType, encode, errorResponseFormat);
JwtAuthException e1 = (JwtAuthException) e; JwtAuthException e1 = (JwtAuthException) e;
this.logError(servletRequest, adapterGroupName, e1.getCode(), responseErrorMsg, transactionProp, e); this.logError(servletRequest, adapterGroupName, e1.getCode(), responseErrorMsg, transactionProp, e);
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(responseErrorMsg); responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(responseErrorMsg);
} else if (e instanceof HttpStatusException) { } else if (e instanceof HttpStatusException) {
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName, responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
transactionProp, MessageUtil.ERROR_CODE_AUTH_FAIL, e, adptMsgType, encode, errorResponseFormat); transactionProp, MessageUtil.ERROR_CODE_AP_ERROR, e, adptMsgType, encode, errorResponseFormat);
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage()); logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
HttpStatusException e1 = (HttpStatusException) e; HttpStatusException e1 = (HttpStatusException) e;
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg); responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
@@ -154,6 +154,8 @@ public class DJBApiAdapterService extends HttpAdapterServiceSupport {
transactionProp.put(HEADER_GROUP, headerGroupName); transactionProp.put(HEADER_GROUP, headerGroupName);
transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys); transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys);
transactionProp.put(ADAPTER_TOKEN_HEADER_NAME, httpProp.getProperty(ADAPTER_TOKEN_HEADER_NAME, "")); // OAuth 인증 토큰 헤더 이름
transactionProp.put(ADAPTER_APIKEY_HEADER_NAME, httpProp.getProperty(ADAPTER_APIKEY_HEADER_NAME, "")); // API-KEY 인증 헤더 이름
// SEED 컬럼암호하 시 Key로 사용함 // SEED 컬럼암호하 시 Key로 사용함
String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString(); String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString();
ElinkTransactionContext.setSeedKey(seedkey); ElinkTransactionContext.setSeedKey(seedkey);
@@ -6,7 +6,6 @@ import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -16,6 +15,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension; import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -30,17 +30,8 @@ public class DecrytTestFilter implements HttpAdapterFilter {
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest request, HttpServletResponse response) throws Exception {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode alg = rootNode.get("alg"); JsonNode alg = rootNode.get("alg");
JsonNode mode = rootNode.get("mode"); JsonNode mode = rootNode.get("mode");
JsonNode padding = rootNode.get("padding"); JsonNode padding = rootNode.get("padding");
@@ -8,7 +8,6 @@ import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -19,6 +18,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension; import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -33,17 +33,8 @@ public class EncrytTestFilter implements HttpAdapterFilter {
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest request, HttpServletResponse response) throws Exception {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
// 1. HSM에서 키 가져오기 // 1. HSM에서 키 가져오기
byte[] hsmKeyBytes = null; byte[] hsmKeyBytes = null;
JsonNode hsmKeyAlias = rootNode.get("hsmKeyAlias"); JsonNode hsmKeyAlias = rootNode.get("hsmKeyAlias");
@@ -8,7 +8,6 @@ import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -18,6 +17,7 @@ import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy; import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy;
import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy; import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -32,17 +32,8 @@ public class GenKeyFilter implements HttpAdapterFilter {
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest request, HttpServletResponse response) throws Exception {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode hsmKey = rootNode.get("hsmKey"); JsonNode hsmKey = rootNode.get("hsmKey");
JsonNode hsmIv = rootNode.get("hsmIv"); JsonNode hsmIv = rootNode.get("hsmIv");
JsonNode contextKey = rootNode.get("contextKey"); JsonNode contextKey = rootNode.get("contextKey");
@@ -10,6 +10,7 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Date; import java.util.Date;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@@ -62,6 +63,13 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
private static boolean testMode = TestModeChecker.isTestMode(); private static boolean testMode = TestModeChecker.isTestMode();
/**
* mTLS 여부/clientId 조합별로 CloseableHttpClient(및 내부 PoolingHttpClientConnectionManager)를
* 1회만 생성해 재사용한다. 이 서비스 인스턴스는 HttpClientAccessTokenServiceFactoryByDB에
* className 기준으로 캐시되어 재사용되므로, 이 필드도 인스턴스 생명주기 동안 안전하게 재사용된다.
*/
private final ConcurrentHashMap<String, CloseableHttpClient> httpClientCache = new ConcurrentHashMap<>();
/** /**
* 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항 * 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항
* - JSON 방식 * - JSON 방식
@@ -121,80 +129,11 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId); logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId);
} }
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS; // mTLS 여부(및 clientId)별로 CloseableHttpClient를 재사용한다. 매 호출마다 새로 만들면
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST; // PoolingHttpClientConnectionManager를 쓰는 의미가 없어지고 SSL 핸드셰이크 비용만 반복된다.
String httpClientCacheKey = useMtls ? "mtls:" + clientId : "default";
HttpOutTlsInfoVO mtlsInfo = null; CloseableHttpClient httpClient = httpClientCache.computeIfAbsent(httpClientCacheKey,
SSLContext sslContext = null; key -> buildHttpClient(useMtls, clientId, name));
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
PoolingHttpClientConnectionManager connectionManager = null;
try {
if (useMtls) {
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
if (StringUtils.isNotEmpty(clientId)) {
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
}
}
if (useMtls && mtlsInfo != null) {
String storeType = mtlsInfo.getStoreType();
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
String keyStorePassword = mtlsInfo.getKeystorePassword();
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
String trustStorePassword = mtlsInfo.getTruststorePassword();
String[] tlsVersions = null;
String[] cipherSuites = null;
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
throw new Exception("mTLS keyStore config error");
}
boolean skipTrust = false;
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
if (logger.isWarn())
logger.warn("Skip trustStore validation adapterGroupName : " + name);
skipTrust = true;
}
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
SSLConnectionSocketFactory sslSocketFactory = null;
if (testMode) {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
} else {
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
SSLConnectionSocketFactory sslSocketFactory = null;
if(testMode) {
// Hostname verifier 비활성화 (테스트용)
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
, NoopHostnameVerifier.INSTANCE
);
}
else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
| IOException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
connectionManager = cmBuilder.build();
connectionManager.setMaxTotal(maxTotalConnections);
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
//json body setting //json body setting
// Map<String,Object> jsonMap = new HashMap<>(); // Map<String,Object> jsonMap = new HashMap<>();
@@ -203,7 +142,7 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
// ObjectMapper objMapper = new ObjectMapper(); // ObjectMapper objMapper = new ObjectMapper();
// String authJsonBody = objMapper.writeValueAsString(jsonMap); // String authJsonBody = objMapper.writeValueAsString(jsonMap);
try (CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();) { {
HttpPost httpPost = new HttpPost(uri); HttpPost httpPost = new HttpPost(uri);
// httpPost.setEntity(new StringEntity(authJsonBody)); // httpPost.setEntity(new StringEntity(authJsonBody));
httpPost.setHeader("Content-Type", contentType+" charset=" + encode); httpPost.setHeader("Content-Type", contentType+" charset=" + encode);
@@ -264,11 +203,14 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
if (responseJSON.has("data")) { if (responseJSON.has("data")) {
JsonNode data = responseJSON.path("data"); JsonNode data = responseJSON.path("data");
if(data.has("token")) { if(data.has("access_token")) {
String token = data.path("token").asText(); String token = data.path("access_token").asText();
long intervalSec = oAuthCredentialVo.getIntervalSec() > 0
? oAuthCredentialVo.getIntervalSec()
: 24 * 60 * 60;
accessToken = new OAuth2AccessTokenVO(); accessToken = new OAuth2AccessTokenVO();
accessToken.setAccessToken(token); accessToken.setAccessToken(token);
accessToken.setExpiration(new Date(currentTime + 30_000L)); accessToken.setExpiration(new Date(currentTime + intervalSec * 1000L));
logger.debug("oauthToken =" + accessToken.toString()); logger.debug("oauthToken =" + accessToken.toString());
return accessToken; return accessToken;
} else { } else {
@@ -277,8 +219,6 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
} else { } else {
throw new Exception("oauth token return null"); throw new Exception("oauth token return null");
} }
// accessToken.setExpiration(new Date(currentTime + oAuthCredentialVo.getIntervalSec() * 1000L));
} else { } else {
throw new Exception("oauth token return null"); throw new Exception("oauth token return null");
} }
@@ -289,6 +229,94 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
} }
} }
/**
* mTLS 여부/clientId 조합에 맞는 SSLContext로 PoolingHttpClientConnectionManager와
* CloseableHttpClient를 생성한다. httpClientCache에 의해 조합당 1회만 호출되며, 반환된
* CloseableHttpClient는 재사용을 위해 닫지 않는다(닫으면 커넥션 풀이 함께 종료된다).
*/
private CloseableHttpClient buildHttpClient(boolean useMtls, String clientId, String adapterGroupName) {
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS;
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST;
HttpOutTlsInfoVO mtlsInfo = null;
SSLContext sslContext = null;
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
try {
if (useMtls) {
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
if (StringUtils.isNotEmpty(clientId)) {
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
}
}
if (useMtls && mtlsInfo != null) {
String storeType = mtlsInfo.getStoreType();
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
String keyStorePassword = mtlsInfo.getKeystorePassword();
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
String trustStorePassword = mtlsInfo.getTruststorePassword();
String[] tlsVersions = null;
String[] cipherSuites = null;
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
throw new Exception("mTLS keyStore config error");
}
boolean skipTrust = false;
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
if (logger.isWarn())
logger.warn("Skip trustStore validation adapterGroupName : " + adapterGroupName);
skipTrust = true;
}
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
SSLConnectionSocketFactory sslSocketFactory = null;
if (testMode) {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
} else {
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
SSLConnectionSocketFactory sslSocketFactory = null;
if(testMode) {
// Hostname verifier 비활성화 (테스트용)
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
, NoopHostnameVerifier.INSTANCE
);
}
else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
| IOException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
PoolingHttpClientConnectionManager connectionManager = cmBuilder.build();
connectionManager.setMaxTotal(maxTotalConnections);
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
if (logger.isInfo()) {
logger.info("DJBErpNsmapiAccessTokenService] HttpClient(재사용) 생성. adapterGroupName={}, useMtls={}, clientId={}",
adapterGroupName, useMtls, clientId);
}
return HttpClients.custom().setConnectionManager(connectionManager).build();
}
/** /**
* URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다. * URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다.
* *
@@ -28,7 +28,7 @@ public final class GUIDGeneratorDJB {
static { static {
// 노드번호(2): 서버명 마지막 2자리, 없으면 "00" // 노드번호(2): 서버명 마지막 2자리, 없으면 "00"
String serverName = System.getProperty("server.key", ""); String serverName = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, "");
if (serverName.length() >= 2) { if (serverName.length() >= 2) {
NODE_NO = serverName.substring(serverName.length() - 2); NODE_NO = serverName.substring(serverName.length() - 2);
} else { } else {