Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4e806fa08 | |||
| 0c048a88fe | |||
| b1e7883d41 | |||
| 09ac7f4ca5 | |||
| 90ccf59b25 | |||
| 051305ac55 | |||
| 375f0e0f15 | |||
| 027ec4fcea | |||
| 229156d51d | |||
| f9e6522b8f | |||
| cd5cb4c0b5 | |||
| a1d822c2d7 | |||
| 05e887f7f1 |
@@ -85,6 +85,16 @@ dependencies {
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
// TransformEngine/LayoutManager/MessageFactory 는 ApplicationContextProvider 기반의
|
||||
// JVM 전역 싱글턴이다. 한 JVM 에서 여러 테스트 클래스를 돌리면 마지막에 뜬 스프링
|
||||
// 컨텍스트가 앞선 컨텍스트를 덮어써서, 뒤 클래스가 앞 클래스의 레이아웃을 보게 된다
|
||||
// ("cannot create message" 오류). 클래스마다 JVM 을 새로 띄워 격리한다.
|
||||
forkEvery = 1
|
||||
|
||||
// 클래스별 JVM 분리로 느려지는 것을 CPU 코어 수만큼 병렬 실행해 상쇄한다.
|
||||
// 포크마다 별도 JVM(=별도 H2 인메모리 DB)이라 서로 간섭하지 않는다.
|
||||
maxParallelForks = Math.max(1, Runtime.runtime.availableProcessors().intdiv(2))
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.eactive.eai.transformer.engine;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.nfunk.jep.JEP;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
@@ -26,6 +31,14 @@ public class Parser implements Serializable {
|
||||
|
||||
private static Logger logger = Logger.getLogger("transformer");
|
||||
|
||||
/**
|
||||
* 표현식의 변수가 아니라 파서가 미리 심볼테이블에 등록해 두는 예약 심볼.
|
||||
* getSymbols() 결과에서 제외하지 않으면 Transformer 가 이를 소스전문의 항목경로로 보고
|
||||
* 조회를 시도해 "레이아웃항목이 존재하지 않음" 경고가 매 변환마다 발생한다.
|
||||
*/
|
||||
private static final Set<String> RESERVED_SYMBOLS =
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("true", "false")));
|
||||
|
||||
private transient JEP jep = new JEP(); // Create a new parser
|
||||
|
||||
public Parser() {
|
||||
@@ -36,6 +49,8 @@ public class Parser implements Serializable {
|
||||
getJep().setAllowAssignment(true);
|
||||
getJep().setAllowUndeclared(true);
|
||||
|
||||
addBooleanConstants();
|
||||
|
||||
try {
|
||||
addGenericFunctions();
|
||||
} catch (Exception e) {
|
||||
@@ -89,13 +104,20 @@ public class Parser implements Serializable {
|
||||
|
||||
public void initSymbolTable() {
|
||||
getJep().initSymTab();
|
||||
addBooleanConstants();
|
||||
}
|
||||
|
||||
/** JEP 기본 상수에 없는 true/false 를 Boolean 타입으로 등록한다. */
|
||||
private void addBooleanConstants() {
|
||||
getJep().addVariable("true", Boolean.TRUE);
|
||||
getJep().addVariable("false", Boolean.FALSE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public String[] getSymbols() {
|
||||
String[] symbols = new String[getJep().getSymbolTable().size()];
|
||||
symbols = (String[])getJep().getSymbolTable().keySet().toArray(new String[symbols.length]);
|
||||
return symbols;
|
||||
Set<String> symbols = new LinkedHashSet<>(getJep().getSymbolTable().keySet());
|
||||
symbols.removeAll(RESERVED_SYMBOLS);
|
||||
return symbols.toArray(new String[symbols.size()]);
|
||||
}
|
||||
|
||||
private void addGenericFunctions() throws Exception {
|
||||
@@ -146,6 +168,7 @@ public class Parser implements Serializable {
|
||||
logger.warn("addFunction ("+name+","+command+") skip null");
|
||||
return;
|
||||
}
|
||||
getJep().addFunction(name, command); // 원본 대소문자 (camelCase 표현식 지원)
|
||||
getJep().addFunction(name.toUpperCase(), command);
|
||||
getJep().addFunction(name.toLowerCase(), command);
|
||||
}
|
||||
|
||||
@@ -685,7 +685,7 @@ public class Transformer {
|
||||
}
|
||||
}
|
||||
} catch(InvalidAccessException e) {
|
||||
e.printStackTrace();
|
||||
//e.printStackTrace();
|
||||
errors.add("Transformer] ITEM Error during conversion (source message access) - continue- " + e.getMessage());
|
||||
value ="";
|
||||
findSource = false;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 날짜/시간 형식 변환
|
||||
*
|
||||
* <p>사용법: {@code convertDateTime(source, fromFormat, toFormat)}
|
||||
* <ul>
|
||||
* <li>{@code source} - 변환할 날짜 문자열 (예: "20240508")</li>
|
||||
* <li>{@code fromFormat} - 입력 형식 패턴 (예: "yyyyMMdd")</li>
|
||||
* <li>{@code toFormat} - 출력 형식 패턴 (예: "yyyy-MM-dd")</li>
|
||||
* </ul>
|
||||
* 반환값: toFormat으로 재포맷된 날짜 문자열
|
||||
*/
|
||||
public class ConvertDateTime extends PostfixMathCommand {
|
||||
public ConvertDateTime() {
|
||||
numberOfParameters = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (source, fromFormat, toFormat) 순으로 꺼내 날짜 형식을 변환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터 타입 오류, 날짜 파싱 실패, 잘못된 형식 패턴
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p3 = inStack.pop();
|
||||
Object p2 = inStack.pop();
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): source must be a string");
|
||||
}
|
||||
if (!(p2 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): fromFormat must be a string");
|
||||
}
|
||||
if (!(p3 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): toFormat must be a string");
|
||||
}
|
||||
|
||||
String source = (String) p1;
|
||||
String fromFormat = (String) p2;
|
||||
String toFormat = (String) p3;
|
||||
|
||||
try {
|
||||
SimpleDateFormat sdfFrom = new SimpleDateFormat(fromFormat);
|
||||
sdfFrom.setLenient(false);
|
||||
Date date = sdfFrom.parse(source);
|
||||
inStack.push(new SimpleDateFormat(toFormat).format(date));
|
||||
} catch (java.text.ParseException e) {
|
||||
throw new ParseException(
|
||||
"convertDateTime(): failed to parse '" + source + "' with format '" + fromFormat + "': " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParseException(
|
||||
"convertDateTime(): invalid format pattern: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 현재 일시 반환
|
||||
*
|
||||
* <p>사용법: {@code getCurrent(format)}
|
||||
* <ul>
|
||||
* <li>{@code format} - SimpleDateFormat 패턴 (예: "yyyyMMddHHmmss")</li>
|
||||
* </ul>
|
||||
* 반환값: 지정 형식으로 포맷된 현재 일시 문자열
|
||||
*/
|
||||
public class GetCurrent extends PostfixMathCommand {
|
||||
public GetCurrent() {
|
||||
numberOfParameters = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 format 패턴을 꺼내 현재 일시를 포맷하여 반환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터 타입 오류, 잘못된 형식 패턴
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("getCurrent(): format must be a string");
|
||||
}
|
||||
|
||||
try {
|
||||
inStack.push(new SimpleDateFormat((String) p1).format(new Date()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParseException("getCurrent(): invalid date format '" + p1 + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - PropManager 프러퍼티 그룹 기반 값 매핑
|
||||
*
|
||||
* <p>고정된 변환 룰이 아닌, PropManager의 프러퍼티 그룹(예: 공통코드 매핑 테이블)을
|
||||
* 동적으로 조회하여 입력값을 매핑된 값으로 변환한다.
|
||||
* 매핑 정보는 {@code 1000 -> AAAAA}, {@code 1001 -> BBBBB} 처럼 그룹의 key/value로 관리된다.
|
||||
*
|
||||
* <p>사용법:
|
||||
* <ul>
|
||||
* <li>{@code getmapping(group, value)} - group 프러퍼티 그룹에서 value에 매핑된 값을 반환.
|
||||
* 매핑이 없으면 <b>원본 value를 그대로 반환</b>(passthrough)</li>
|
||||
* <li>{@code getmapping(group, value, default)} - 매핑이 없으면 <b>default</b>를 반환</li>
|
||||
* </ul>
|
||||
* 파라미터:
|
||||
* <ul>
|
||||
* <li>{@code group} - PropManager 프러퍼티 그룹명 (문자열)</li>
|
||||
* <li>{@code value} - 매핑 대상 값. 숫자여도 되며 조회 시 문자열 key로 변환된다
|
||||
* (정수형 숫자는 {@code 1000.0}이 아닌 {@code "1000"}으로 조회)</li>
|
||||
* <li>{@code default} - (선택) 매핑이 없을 때 반환할 기본값</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>예: 그룹 "COMMON_CODE"에 {@code 1000=AAAAA}가 있을 때
|
||||
* {@code getmapping("COMMON_CODE", "1000")} → {@code "AAAAA"}
|
||||
*/
|
||||
public class GetMapping extends PostfixMathCommand {
|
||||
public GetMapping() {
|
||||
numberOfParameters = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (group, value) 또는 (group, value, default)를 꺼내 매핑값을 조회한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터 개수가 2 또는 3이 아니거나 group이 문자열이 아닌 경우
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
int n = curNumberOfParameters;
|
||||
if (n != 2 && n != 3) {
|
||||
throw new ParseException(
|
||||
"getmapping() requires 2 or 3 parameters: getmapping(group, value [, default])");
|
||||
}
|
||||
|
||||
boolean hasDefault = (n == 3);
|
||||
Object defaultValue = hasDefault ? inStack.pop() : null;
|
||||
Object value = inStack.pop();
|
||||
Object groupObj = inStack.pop();
|
||||
|
||||
if (!(groupObj instanceof String)) {
|
||||
throw new ParseException("getmapping(): first parameter (group name) must be a string");
|
||||
}
|
||||
|
||||
String group = (String) groupObj;
|
||||
String key = toKey(value);
|
||||
|
||||
String mapped = (key == null) ? null : lookup(group, key);
|
||||
|
||||
if (mapped != null) {
|
||||
inStack.push(mapped);
|
||||
} else if (hasDefault) {
|
||||
inStack.push(defaultValue);
|
||||
} else {
|
||||
inStack.push(value); // 매핑 없음 → 원본값 그대로 반환
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PropManager 프러퍼티 그룹에서 key에 해당하는 값을 조회한다.
|
||||
* 매핑이 없거나 그룹이 존재하지 않으면 null을 반환한다.
|
||||
*
|
||||
* <p>단위 테스트에서는 이 메서드를 오버라이드하여 Spring 컨텍스트 없이 매핑을 주입할 수 있다.
|
||||
*/
|
||||
protected String lookup(String group, String key) {
|
||||
return PropManager.getInstance().getProperty(group, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 매핑 조회에 사용할 문자열 key로 변환한다.
|
||||
* 정수형 숫자(JEP는 숫자를 Double로 처리)는 {@code "1000.0"}이 아닌 {@code "1000"}으로 변환한다.
|
||||
*/
|
||||
private String toKey(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
double d = ((Number) value).doubleValue();
|
||||
if (!Double.isInfinite(d) && !Double.isNaN(d) && d == Math.rint(d)) {
|
||||
return Long.toString((long) d);
|
||||
}
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 삼항 조건 평가
|
||||
*
|
||||
* <p>사용법: {@code getTrinomial(target, compare, operator, trueValue, falseValue)}
|
||||
* <ul>
|
||||
* <li>{@code target} - 비교 대상 값</li>
|
||||
* <li>{@code compare} - 비교 기준 값</li>
|
||||
* <li>{@code operator} - 비교 연산자 문자열 (=, <, >, <=, >=)</li>
|
||||
* <li>{@code trueValue} - 조건이 참일 때 반환할 값</li>
|
||||
* <li>{@code falseValue} - 조건이 거짓일 때 반환할 값</li>
|
||||
* </ul>
|
||||
* 타입별 비교: Number는 수치 비교, Boolean은 '=' 만 지원, 그 외는 문자열 사전순 비교
|
||||
*/
|
||||
public class GetTrinomial extends PostfixMathCommand {
|
||||
public GetTrinomial() {
|
||||
numberOfParameters = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (target, compare, operator, trueValue, falseValue) 순으로 꺼내 조건을 평가한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException operator 타입 오류, 지원하지 않는 연산자, boolean에 '=' 외 연산자 사용
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object falseValue = inStack.pop();
|
||||
Object trueValue = inStack.pop();
|
||||
Object operator = inStack.pop();
|
||||
Object compare = inStack.pop();
|
||||
Object target = inStack.pop();
|
||||
|
||||
if (!(operator instanceof String)) {
|
||||
throw new ParseException("getTrinomial(): operator must be a string (=, <, >, <=, >=)");
|
||||
}
|
||||
|
||||
boolean result = compare(target, compare, ((String) operator).trim());
|
||||
inStack.push(result ? trueValue : falseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* target과 compare를 op 연산자로 비교한다.
|
||||
* Number → 수치 비교, Boolean → 동등 비교, 그 외 → 문자열 사전순 비교
|
||||
*/
|
||||
private boolean compare(Object target, Object compare, String op) throws ParseException {
|
||||
if (target instanceof Number && compare instanceof Number) {
|
||||
double t = ((Number) target).doubleValue();
|
||||
double c = ((Number) compare).doubleValue();
|
||||
switch (op) {
|
||||
case "=": return t == c;
|
||||
case "<": return t < c;
|
||||
case ">": return t > c;
|
||||
case "<=": return t <= c;
|
||||
case ">=": return t >= c;
|
||||
default: throw new ParseException("getTrinomial(): unsupported operator: " + op);
|
||||
}
|
||||
} else if (target instanceof Boolean && compare instanceof Boolean) {
|
||||
if (!"=".equals(op)) {
|
||||
throw new ParseException("getTrinomial(): boolean comparison only supports '=' operator");
|
||||
}
|
||||
return target.equals(compare);
|
||||
} else {
|
||||
String t = target != null ? target.toString() : "";
|
||||
String c = compare != null ? compare.toString() : "";
|
||||
int cmp = t.compareTo(c);
|
||||
switch (op) {
|
||||
case "=": return cmp == 0;
|
||||
case "<": return cmp < 0;
|
||||
case ">": return cmp > 0;
|
||||
case "<=": return cmp <= 0;
|
||||
case ">=": return cmp >= 0;
|
||||
default: throw new ParseException("getTrinomial(): unsupported operator: " + op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 문자열 전체 치환
|
||||
*
|
||||
* <p>사용법: {@code replaceAll(source, from, to)}
|
||||
* <ul>
|
||||
* <li>{@code source} - 원본 문자열</li>
|
||||
* <li>{@code from} - 치환 대상 문자열 (정규식 아님, 리터럴 매칭)</li>
|
||||
* <li>{@code to} - 치환 결과 문자열</li>
|
||||
* </ul>
|
||||
* 반환값: source 내 from과 일치하는 모든 부분을 to로 치환한 문자열
|
||||
*
|
||||
* <p>주의: JEP 내장 함수 {@code str()}와 충돌하므로, 변수명으로 {@code str}을 사용하지 말 것.
|
||||
*/
|
||||
public class ReplaceAll extends PostfixMathCommand {
|
||||
public ReplaceAll() {
|
||||
numberOfParameters = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (source, from, to) 순으로 꺼내 전체 치환 후 결과를 반환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터가 String 타입이 아닌 경우
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p3 = inStack.pop();
|
||||
Object p2 = inStack.pop();
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): first parameter (source string) must be a string");
|
||||
}
|
||||
if (!(p2 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): second parameter (from string) must be a string");
|
||||
}
|
||||
if (!(p3 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): third parameter (to string) must be a string");
|
||||
}
|
||||
|
||||
inStack.push(((String) p1).replace((String) p2, (String) p3));
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,7 @@ public class Converter {
|
||||
}
|
||||
String[] data = normalized.split("[.]");
|
||||
if (pointLength > 0) {
|
||||
// length 는 소수점(.) 문자를 포함한 전체 길이 규약. 정수부 허용 자릿수 = length - pointLength - 1
|
||||
if (data.length == 1) {
|
||||
if (data[0].length() > length - pointLength - 1) return false;
|
||||
} else if (data.length == 2) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.eactive.eai.common.util.MaskingUtils;
|
||||
import com.eactive.eai.transformer.message.InvalidAccessException;
|
||||
import com.eactive.eai.transformer.message.Message;
|
||||
import com.eactive.eai.transformer.message.MessageException;
|
||||
@@ -934,6 +935,9 @@ abstract public class Item implements Serializable, Cloneable {
|
||||
else
|
||||
str = val.toString();
|
||||
|
||||
if(maskLength == 0 && maskOffset > 0)
|
||||
return MaskingUtils.mask(str, maskOffset);
|
||||
|
||||
int len = str.length();
|
||||
if( len <= maskOffset ) return str;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.eactive.eai.transformer.message;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
@@ -10,6 +9,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.transformer.SystemKeys;
|
||||
import com.eactive.eai.transformer.layout.Converter;
|
||||
import com.eactive.eai.transformer.layout.Group;
|
||||
import com.eactive.eai.transformer.layout.InvalidNodeException;
|
||||
import com.eactive.eai.transformer.layout.Item;
|
||||
import com.eactive.eai.transformer.layout.Types;
|
||||
@@ -18,6 +18,7 @@ import com.eactive.eai.transformer.util.Logger;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -37,22 +38,24 @@ public class JSONMessage extends Message {
|
||||
protected JsonNode data;
|
||||
protected ObjectMapper mapper = null;
|
||||
protected JsonFactory factory = null;
|
||||
protected boolean bStaled = false;
|
||||
protected boolean bStaled = true;
|
||||
|
||||
public JSONMessage() {
|
||||
super();
|
||||
mapper = new ObjectMapper();
|
||||
// if necessary, add proper options -> use system property config ?
|
||||
mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||
mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
|
||||
mapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
|
||||
|
||||
factory = mapper.getFactory();
|
||||
factory.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createObject() {
|
||||
data = mapper.createObjectNode();
|
||||
// data = mapper.createObjectNode();
|
||||
data = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,50 +102,20 @@ public class JSONMessage extends Message {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.data == null) {
|
||||
logger.error("Parsed data is null");
|
||||
return;
|
||||
}
|
||||
|
||||
//ObjectNode node = (ObjectNode) this.data.get(getName());
|
||||
/*
|
||||
if(node == null) {
|
||||
ObjectNode node = (ObjectNode) this.data.get(getName());
|
||||
if(node == null) {
|
||||
// if layoutname not appended
|
||||
setValue(this.data, getName());
|
||||
}
|
||||
else {
|
||||
setValue(node, getName());
|
||||
}
|
||||
*/
|
||||
|
||||
ObjectNode node = null;
|
||||
|
||||
// 변경된 부분: NullNode 검사 추가
|
||||
if (this.data.has(getName()) && !this.data.get(getName()).isNull()) {
|
||||
JsonNode nameNode = this.data.get(getName());
|
||||
if (nameNode.isObject()) {
|
||||
node = (ObjectNode) nameNode;
|
||||
|
||||
// 배열 데이터 처리 개선
|
||||
processArrayNodes(this.data);
|
||||
|
||||
// 값 설정
|
||||
setValue(node, getName());
|
||||
} else {
|
||||
logger.warn("Node with name '" + getName() + "' is not an object, skipping setValue operation");
|
||||
}
|
||||
} else {
|
||||
// layoutname이 추가되지 않은 경우
|
||||
setValue(this.data, getName());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new MessageSetDataException(e.getMessage(), this, data, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue(JsonNode node, String itempPath) throws InvalidAccessException {
|
||||
|
||||
try {
|
||||
// this.value = Converter.convert(value, getDataTypeId());
|
||||
/**
|
||||
@@ -150,7 +123,6 @@ public class JSONMessage extends Message {
|
||||
*/
|
||||
|
||||
HashMap validationTargets = new HashMap();
|
||||
|
||||
doSetValue(node, validationTargets, itempPath);
|
||||
|
||||
// validation
|
||||
@@ -176,18 +148,29 @@ public class JSONMessage extends Message {
|
||||
|
||||
protected void doSetValue(JsonNode node, HashMap validationTargets, String parentPath) {
|
||||
Item item = null;
|
||||
|
||||
if (node.isNull()) {
|
||||
// null이면 타겟 형식에서 Grid인지 확인 해 봄
|
||||
String itemPath = parentPath + "[-1]";
|
||||
item = this.getItem(itemPath);
|
||||
if (item != null && item instanceof Group && StringUtils.equalsAny(item.getOccType(), "*", "+")) {
|
||||
item = getOrCreateItem(denomalizeXPath(parentPath));
|
||||
if (item != null) {
|
||||
item.setIndex(-1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if( node.isObject()) {
|
||||
Iterator<String> fieldNames = node.fieldNames();
|
||||
if(StringUtils.isNotEmpty(parentPath)) {
|
||||
item = getOrCreateItem(denomalizeXPath(parentPath));
|
||||
|
||||
if(SystemKeys.isValidationEnabled() && StringUtils.isNotEmpty(item.getOccRef())) {
|
||||
validationTargets.put(item.getParent().getItemPath() + Types.FIELD_SEPARATOR + item.getOccRef(), item);
|
||||
}
|
||||
}
|
||||
while(fieldNames.hasNext()) {
|
||||
String fieldName = fieldNames.next();
|
||||
|
||||
JsonNode fieldValue = node.get(fieldName);
|
||||
try {
|
||||
String path = null;
|
||||
@@ -206,58 +189,42 @@ public class JSONMessage extends Message {
|
||||
}
|
||||
else if( node.isArray() ) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
|
||||
// 배열이 문자열 값만 포함하는지 확인
|
||||
boolean isSimpleArray = true;
|
||||
for (int i = 0; i < arrayNode.size(); i++) {
|
||||
if (!arrayNode.get(i).isValueNode()) {
|
||||
isSimpleArray = false;
|
||||
break;
|
||||
if(arrayNode.size() == 0 && StringUtils.isNotEmpty(parentPath)) {
|
||||
try {
|
||||
// 아직 clone 되지 않았으면 생성 안함.
|
||||
item = this.getItem(denomalizeXPath(parentPath));
|
||||
if (item != null) {
|
||||
// clone 후에는 -1로
|
||||
item = getOrCreateItem(denomalizeXPath(parentPath));
|
||||
if (item != null) {
|
||||
item.setIndex(-1);
|
||||
}
|
||||
}
|
||||
} catch (InvalidNodeException ie) {
|
||||
// continue silently
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < arrayNode.size(); i++) {
|
||||
JsonNode arrayItem = arrayNode.get(i);
|
||||
if(StringUtils.isNotEmpty(parentPath)) {
|
||||
try {
|
||||
item = getOrCreateItem(denomalizeXPath(parentPath + "[" + i + "]"));
|
||||
} catch(InvalidNodeException ie) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSimpleArray) {
|
||||
// 문자열 배열은 직접 값 설정
|
||||
try {
|
||||
setString(parentPath, arrayNode.toString());
|
||||
} catch (Exception e) {
|
||||
logger.error("Error setting simple array: " + parentPath, e);
|
||||
// 개별 요소 설정
|
||||
for (int i = 0; i < arrayNode.size(); i++) {
|
||||
try {
|
||||
String itemPath = parentPath + "[" + i + "]";
|
||||
setString(itemPath, arrayNode.get(i).asText());
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error setting array item: " + parentPath + "[" + i + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 복잡한 배열 구조 처리
|
||||
for (int i = 0; i < arrayNode.size(); i++) {
|
||||
JsonNode arrayItem = arrayNode.get(i);
|
||||
|
||||
if (StringUtils.isNotEmpty(parentPath)) {
|
||||
try {
|
||||
item = getOrCreateItem(denomalizeXPath(parentPath + "[" + i + "]"));
|
||||
} catch (InvalidNodeException ie) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
doSetValue(arrayItem, validationTargets, parentPath + "[" + i + "]");
|
||||
}
|
||||
}
|
||||
doSetValue(arrayItem, validationTargets, parentPath + "[" + i + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
String itemPath = parentPath;
|
||||
item = getOrCreateItem(denomalizeXPath(itemPath));
|
||||
String value = node.asText();
|
||||
|
||||
if(node == null || node.isNull()) {
|
||||
setObject(itemPath, null); // 실제 null 값 설정
|
||||
} else if(value == null || value.length() == 0) {
|
||||
if (node.isNull()) return;
|
||||
if(value == null || value.length() == 0) {
|
||||
setObject( itemPath, value );
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
int itemType = item.getDataTypeId();
|
||||
try {
|
||||
switch(itemType) {
|
||||
@@ -370,15 +337,15 @@ public class JSONMessage extends Message {
|
||||
if( parent instanceof ObjectNode ) {
|
||||
obj = (ObjectNode)parent;
|
||||
|
||||
String val = getStringValue(item.getValue(),item);
|
||||
int itemType = item.getDataTypeId();
|
||||
// 출력 시점 자릿수 검증 (변환으로 생성된 값은 setData 입력검증을 거치지 않음)
|
||||
validateDecimal(item.getValue(), item);
|
||||
|
||||
// String인 경우에만 Masking 이 가능 - 숫자는 불가능
|
||||
int itemType = item.getDataTypeId();
|
||||
if (isMask && "Y".equals(item.getMask()) && (Types.TYPE_STRING == itemType )){
|
||||
val = maskString2(val, item.getMaskLength(), item.getMaskOffset());
|
||||
String val = maskString2(getStringValue(item.getValue(),item), item.getMaskLength(), item.getMaskOffset());
|
||||
item.setValue(val);
|
||||
}
|
||||
// Object value = toJsonObjectType(item.getDataTypeId(), val);
|
||||
String value = val;
|
||||
if( item.getOccMax() > 1 || item.getOccMax() == -1 ) { // FIELD ARRAY FORMAT "name":["value1", "value2"]
|
||||
arr = (ArrayNode)obj.get(item.getName());
|
||||
if (arr == null){
|
||||
@@ -386,7 +353,8 @@ public class JSONMessage extends Message {
|
||||
setJsonArrayValue(arr, item);
|
||||
obj.set(prefix+item.getName(), arr);
|
||||
}else{
|
||||
arr.add(value);
|
||||
// arr.add(value);
|
||||
setJsonArrayValue(arr, item); //IBK에서 사용.
|
||||
}
|
||||
|
||||
} else { //FIELD
|
||||
@@ -418,7 +386,7 @@ public class JSONMessage extends Message {
|
||||
logger.error("not support parent type JSONArray");
|
||||
throw new Exception("not support parent type JSONArray");
|
||||
}
|
||||
if (item.getChildCount() > 0){
|
||||
if (item.getChildCount() > 0 ){
|
||||
stack.push(current);
|
||||
}
|
||||
break;
|
||||
@@ -448,33 +416,6 @@ public class JSONMessage extends Message {
|
||||
node.putNull(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(value instanceof JsonNode) {
|
||||
node.set(fieldName, (JsonNode) value);
|
||||
return;
|
||||
}
|
||||
|
||||
// New code to check if string is JSON array
|
||||
if (itemType == Types.TYPE_STRING && value instanceof String) {
|
||||
String strValue = (String) value;
|
||||
if ((strValue.startsWith("[") && strValue.endsWith("]")) ||
|
||||
(strValue.startsWith("{") && strValue.endsWith("}"))) {
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
JsonNode jsonNode = mapper.readTree(strValue);
|
||||
node.set(fieldName, jsonNode);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// Not valid JSON, continue with normal processing
|
||||
logger.debug("Failed to parse as JSON, using as string: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(value instanceof byte[])) {
|
||||
logger.info("value : " + value.toString());
|
||||
}
|
||||
|
||||
switch(itemType) {
|
||||
case Types.TYPE_INT :
|
||||
case Types.TYPE_SHORT :
|
||||
@@ -484,8 +425,9 @@ public class JSONMessage extends Message {
|
||||
node.put(fieldName, (long) value);
|
||||
break;
|
||||
case Types.TYPE_BIGDECIMAL :
|
||||
BigDecimal bd = (BigDecimal)value;
|
||||
node.put(fieldName, bd);
|
||||
// BigDecimal bd = (BigDecimal)value;
|
||||
// node.put(fieldName, bd.floatValue());
|
||||
node.put(fieldName, (BigDecimal)value);
|
||||
break;
|
||||
case Types.TYPE_DOUBLE :
|
||||
node.put(fieldName, (double) value);
|
||||
@@ -497,12 +439,7 @@ public class JSONMessage extends Message {
|
||||
node.put(fieldName, (boolean) value);
|
||||
break;
|
||||
case Types.TYPE_STRING :
|
||||
|
||||
node.put(fieldName, (String) value);
|
||||
if (value instanceof JsonNode) {
|
||||
logger.info("value : " + (String) value );
|
||||
}
|
||||
|
||||
break;
|
||||
default :
|
||||
node.put(fieldName, (String) value);
|
||||
@@ -524,13 +461,13 @@ public class JSONMessage extends Message {
|
||||
arrayNode.add( (long) value);
|
||||
break;
|
||||
case Types.TYPE_BIGDECIMAL :
|
||||
arrayNode.add( (float) value);
|
||||
arrayNode.add( (BigDecimal)value);
|
||||
break;
|
||||
case Types.TYPE_DOUBLE :
|
||||
arrayNode.add( (double) value);
|
||||
break;
|
||||
case Types.TYPE_FLOAT :
|
||||
arrayNode.add( (float) value);
|
||||
arrayNode.add( (float) value);
|
||||
break;
|
||||
case Types.TYPE_BOOLEAN :
|
||||
arrayNode.add( (boolean) value);
|
||||
@@ -539,52 +476,32 @@ public class JSONMessage extends Message {
|
||||
arrayNode.add( (String) value);
|
||||
break;
|
||||
default :
|
||||
arrayNode.add( (String) value);
|
||||
arrayNode.add( (String) value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* item 값을 문자열로 변환한다. 마스킹 대상(TYPE_STRING) 필드에서만 사용된다.
|
||||
*/
|
||||
protected String getStringValue(Object value, Item item) throws Exception{
|
||||
if (value == null) return null;
|
||||
String val="";
|
||||
if (value instanceof String){
|
||||
String data = (String)value;
|
||||
|
||||
if (item.getDataTypeId() == Types.TYPE_BIGDECIMAL){
|
||||
val = getFormat(data,item.getLength(),item.getDecimalPointLength());
|
||||
}else{
|
||||
val = data;
|
||||
}
|
||||
val = (String)value;
|
||||
} else if (value instanceof byte[]){
|
||||
String data = new String((byte[])value);
|
||||
if (item.getDataTypeId() == Types.TYPE_BIGDECIMAL){
|
||||
val = getFormat(data,item.getLength(),item.getDecimalPointLength());
|
||||
}else{
|
||||
val = data;
|
||||
}
|
||||
val = new String((byte[])value);
|
||||
} else if ( value instanceof BigDecimal) {
|
||||
String data = ((BigDecimal) value).toPlainString();
|
||||
if (item.getDataTypeId() == Types.TYPE_BIGDECIMAL){
|
||||
val = getFormat(data,item.getLength(),item.getDecimalPointLength());
|
||||
}else{
|
||||
val = data;
|
||||
}
|
||||
val = ((BigDecimal) value).toPlainString();
|
||||
} else if ( value instanceof Integer) {
|
||||
String data = String.valueOf(value);
|
||||
if (item.getDataTypeId() == Types.TYPE_BIGDECIMAL){
|
||||
val = getFormat(data,item.getLength(),item.getDecimalPointLength());
|
||||
}else{
|
||||
val = data;
|
||||
}
|
||||
val = String.valueOf(value);
|
||||
} else if ( value instanceof Boolean) {
|
||||
String data = String.valueOf(value);
|
||||
val = data;
|
||||
val = String.valueOf(value);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@Override
|
||||
public Object getData() {
|
||||
|
||||
if (bStaled) {
|
||||
if (bStaled || this.data == null) {
|
||||
try {
|
||||
this.data = getJSONData();
|
||||
} catch(Exception e) {
|
||||
@@ -604,7 +521,6 @@ public class JSONMessage extends Message {
|
||||
return null;
|
||||
}
|
||||
if(this.data == null) return null;
|
||||
|
||||
if(isPretty) return data.toPrettyString();
|
||||
else return data.toString();
|
||||
}
|
||||
@@ -656,51 +572,45 @@ public class JSONMessage extends Message {
|
||||
}
|
||||
}
|
||||
|
||||
private String getFormat(String value,int length,int pointLength) throws Exception {
|
||||
boolean validation = SystemKeys.isValidationEnabled();
|
||||
return getFormat(value, length, pointLength, validation) ;
|
||||
}
|
||||
|
||||
private String getFormat(String value, int length, int pointLength, boolean isValidation) throws Exception{
|
||||
if(isValidation) {
|
||||
if (!Converter.isValid(value,length,pointLength)){
|
||||
throw new Exception(String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d"
|
||||
, value,length,pointLength)
|
||||
);
|
||||
}
|
||||
|
||||
boolean isMinus=false;
|
||||
String pattern = "";
|
||||
//음수/양수 check
|
||||
if (value.indexOf("-")>=0){
|
||||
isMinus = true;
|
||||
}
|
||||
String data = value.replaceAll(" ", "").replaceAll("-", "");
|
||||
if (pointLength> 0 ){//소수부가 있을 경우
|
||||
for(int i=isMinus?1:0;i<length-pointLength-1;i++){
|
||||
pattern = pattern + "#";
|
||||
}
|
||||
pattern =pattern+".";
|
||||
for(int i=0;i<pointLength;i++){
|
||||
pattern = pattern + "0";
|
||||
}
|
||||
}else{//소수부가 없을경우
|
||||
for(int i=isMinus?1:0;i<length-pointLength;i++){
|
||||
pattern = pattern + "#";
|
||||
}
|
||||
}
|
||||
if (isMinus) {
|
||||
pattern="-"+pattern;
|
||||
}
|
||||
DecimalFormat format = new DecimalFormat(pattern);
|
||||
// double num = Double.parseDouble(data);
|
||||
BigDecimal num = new BigDecimal(data);
|
||||
return format.format(num);
|
||||
/**
|
||||
* BIGDECIMAL 필드의 출력값 검증.
|
||||
*
|
||||
* 변환으로 생성된 target 메시지의 값은 setData() 입력검증을 거치지 않으므로
|
||||
* (Message.setBigDecimal() 에 검증 없음) 출력 시점에 레이아웃 자릿수를 확인한다.
|
||||
*
|
||||
* 이전에는 DecimalFormat 으로 자릿수를 맞춘 문자열을 반환하는 getFormat() 이었으나,
|
||||
* 출력이 setJsonNodeValue() 에서 item.getValue() 를 직접 쓰는 방식으로 바뀌면서
|
||||
* 반환값이 사용되지 않아 포맷 로직을 제거하고 검증만 남김.
|
||||
*/
|
||||
private void validateDecimal(Object value, Item item) throws Exception {
|
||||
if (value == null || item.getDataTypeId() != Types.TYPE_BIGDECIMAL) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
BigDecimal num = new BigDecimal(value);
|
||||
return num.toPlainString();
|
||||
|
||||
String data;
|
||||
if (value instanceof String) {
|
||||
data = (String) value;
|
||||
} else if (value instanceof byte[]) {
|
||||
data = new String((byte[]) value);
|
||||
} else if (value instanceof BigDecimal) {
|
||||
data = ((BigDecimal) value).toPlainString();
|
||||
} else if (value instanceof Integer) {
|
||||
data = String.valueOf(value);
|
||||
} else {
|
||||
// 그 외 타입은 검증 대상 아님
|
||||
return;
|
||||
}
|
||||
|
||||
int length = item.getLength();
|
||||
int pointLength = item.getDecimalPointLength();
|
||||
if (SystemKeys.isValidationEnabled() && !Converter.isValid(data, length, pointLength)) {
|
||||
throw new InvalidAccessException(
|
||||
String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d", data, length,
|
||||
pointLength),
|
||||
item.getItemPath());
|
||||
}
|
||||
// 숫자형 여부 확인 - 파싱 불가 시 NumberFormatException
|
||||
new BigDecimal(data.replaceAll(" ", ""));
|
||||
}
|
||||
|
||||
// private Object toJsonObjectType(int itemType, String value) {
|
||||
@@ -736,70 +646,11 @@ public class JSONMessage extends Message {
|
||||
return getName() + " (" + MESSAGE_NAME + ")";
|
||||
}
|
||||
|
||||
// 배열 처리 메소드
|
||||
private void processArrayNodes(JsonNode rootNode) {
|
||||
Iterator<String> fieldNames = rootNode.fieldNames();
|
||||
while (fieldNames.hasNext()) {
|
||||
String fieldName = fieldNames.next();
|
||||
JsonNode node = rootNode.get(fieldName);
|
||||
|
||||
// data_part와 같은 객체 노드 처리
|
||||
if (node.isObject()) {
|
||||
processArrayNodes(node);
|
||||
}
|
||||
// 배열 노드 처리
|
||||
else if (node.isArray()) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
|
||||
// 배열 노드 처리
|
||||
if (arrayNode.size() > 0) {
|
||||
// 배열 데이터 설정
|
||||
try {
|
||||
|
||||
if(this.data instanceof ObjectNode) {
|
||||
ObjectNode objNode = (ObjectNode) this.data;
|
||||
objNode.set(fieldName, arrayNode);
|
||||
} else if (rootNode instanceof ObjectNode) {
|
||||
((ObjectNode) rootNode).set(fieldName, arrayNode);
|
||||
}
|
||||
|
||||
for(int i=0; i < arrayNode.size(); i++) {
|
||||
JsonNode itemNode = arrayNode.get(i);
|
||||
|
||||
String itemPath = fieldName + "[" + i + "]";
|
||||
|
||||
if(itemNode.isObject()) {
|
||||
processArrayNodes(itemNode);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error setting array data for field: " + fieldName, e);
|
||||
// 배열 요소를 개별적으로 처리
|
||||
for (int i = 0; i < arrayNode.size(); i++) {
|
||||
try {
|
||||
String itemPath = fieldName + "[" + i + "]";
|
||||
JsonNode itemNode = arrayNode.get(i);
|
||||
if (itemNode.isTextual()) {
|
||||
setString(itemPath, itemNode.asText());
|
||||
} else {
|
||||
setString(itemPath, itemNode.toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error setting array item [" + i + "] for field: " + fieldName, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
// if staled -> reorganize data
|
||||
String jsonString = null;
|
||||
try {
|
||||
jsonString = (String)getData();
|
||||
jsonString = toLogString();
|
||||
} catch (Exception e) {
|
||||
logger.error("[JSONMessage] " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TransformValidator {
|
||||
try {
|
||||
if (!transform.getTransformItemList().iterator().hasNext()) {
|
||||
// throw new TransformerException("source layout does not exist");
|
||||
throw new TransformerException("전문레이아웃 매핑에 원천 레이아웃이 없습니다.");
|
||||
throw new TransformerException("전문레이아웃 매핑에 변환 항목(TSEAITR03)이 없습니다.");
|
||||
}
|
||||
|
||||
if (transform.getTargetLayout() == null) {
|
||||
@@ -202,7 +202,7 @@ public class TransformValidator {
|
||||
try {
|
||||
if (!transform.getTransformItemList().iterator().hasNext()) {
|
||||
// throw new TransformerException("source layout does not exist");
|
||||
throw new TransformerException("전문레이아웃 매핑에 원천 레이아웃이 없습니다.");
|
||||
throw new TransformerException("전문레이아웃 매핑에 변환 항목(TSEAITR03)이 없습니다.");
|
||||
}
|
||||
|
||||
if (transform.getTargetLayout() == null) {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.ConvertDateTime;
|
||||
|
||||
@DisplayName("ConvertDateTime 날짜 변환 함수 테스트")
|
||||
class ConvertDateTimeFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("convertDateTime", new ConvertDateTime());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMdd → yyyy-MM-dd 변환")
|
||||
void testCompactToHyphen() {
|
||||
jep.addVariable("src", "20240508");
|
||||
assertEquals("2024-05-08", eval("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmss → yyyy-MM-dd'T'HH:mm:ss 변환 (ISO 8601)")
|
||||
void testToIso8601() {
|
||||
jep.addVariable("src", "20240508143022");
|
||||
assertEquals("2024-05-08T14:30:22",
|
||||
eval("convertDateTime(src, \"yyyyMMddHHmmss\", \"yyyy-MM-dd'T'HH:mm:ss\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd'T'HH:mm:ss → yyyyMMddHHmmss 변환")
|
||||
void testFromIso8601ToCompact() {
|
||||
jep.addVariable("src", "2024-05-08T14:30:22");
|
||||
assertEquals("20240508143022",
|
||||
eval("convertDateTime(src, \"yyyy-MM-dd'T'HH:mm:ss\", \"yyyyMMddHHmmss\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmssSSS → yyyy-MM-dd HH:mm:ss.SSS 변환 (밀리초 포함)")
|
||||
void testWithMilliseconds() {
|
||||
jep.addVariable("src", "20240508143022123");
|
||||
assertEquals("2024-05-08 14:30:22.123",
|
||||
eval("convertDateTime(src, \"yyyyMMddHHmmssSSS\", \"yyyy-MM-dd HH:mm:ss.SSS\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("날짜 포맷만 변환 (시간 없음)")
|
||||
void testDateOnly() {
|
||||
jep.addVariable("src", "2024/05/08");
|
||||
assertEquals("20240508",
|
||||
eval("convertDateTime(src, \"yyyy/MM/dd\", \"yyyyMMdd\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("잘못된 날짜 → 오류 발생")
|
||||
void testInvalidDate() {
|
||||
jep.addVariable("src", "99999999");
|
||||
jep.parseExpression("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "잘못된 날짜는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.GetCurrent;
|
||||
|
||||
@DisplayName("GetCurrent 현재시간 반환 함수 테스트")
|
||||
class GetCurrentFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("getCurrent", new GetCurrent());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMdd 형식 — 오늘 날짜와 일치")
|
||||
void testDateFormat() {
|
||||
String expected = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String result = eval("getCurrent(\"yyyyMMdd\")");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd 형식 — 오늘 날짜와 일치")
|
||||
void testHyphenDateFormat() {
|
||||
String expected = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
String result = eval("getCurrent(\"yyyy-MM-dd\")");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmss 형식 — 결과가 14자리")
|
||||
void testDateTimeFormat() {
|
||||
String result = eval("getCurrent(\"yyyyMMddHHmmss\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{14}"), "결과가 14자리 숫자여야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd'T'HH:mm:ss ISO 8601 형식")
|
||||
void testIso8601Format() {
|
||||
String result = eval("getCurrent(\"yyyy-MM-dd'T'HH:mm:ss\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"),
|
||||
"ISO 8601 형식이어야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmssSSS 형식 — 결과가 17자리")
|
||||
void testDateTimeMillisFormat() {
|
||||
String result = eval("getCurrent(\"yyyyMMddHHmmssSSS\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{17}"), "결과가 17자리 숫자여야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("잘못된 포맷 → 오류 발생")
|
||||
void testInvalidFormat() {
|
||||
jep.parseExpression("getCurrent(\"invalid!!!###\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "잘못된 포맷은 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEquals(String expected, String result) {
|
||||
org.junit.jupiter.api.Assertions.assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.GetMapping;
|
||||
|
||||
@DisplayName("GetMapping 프러퍼티 그룹 매핑 함수 테스트")
|
||||
class GetMappingFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
/**
|
||||
* PropManager(Spring 컨텍스트) 없이 테스트하기 위해 lookup()을 로컬 맵으로 대체한 스텁.
|
||||
* groups: 그룹명 → (key → value) 매핑 테이블
|
||||
*/
|
||||
static class StubGetMapping extends GetMapping {
|
||||
private final Map<String, Map<String, String>> groups;
|
||||
|
||||
StubGetMapping(Map<String, Map<String, String>> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String lookup(String group, String key) {
|
||||
Map<String, String> g = groups.get(group);
|
||||
if (g == null) {
|
||||
return null;
|
||||
}
|
||||
return g.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 프러퍼티 그룹 "COMMON_CODE": 1000->AAAAA, 1001->BBBBB
|
||||
Map<String, String> commonCode = new HashMap<>();
|
||||
commonCode.put("1000", "AAAAA");
|
||||
commonCode.put("1001", "BBBBB");
|
||||
|
||||
// 한글 그룹명/값도 지원하는지 확인용
|
||||
Map<String, String> bankCode = new HashMap<>();
|
||||
bankCode.put("081", "하나은행");
|
||||
bankCode.put("088", "신한은행");
|
||||
|
||||
Map<String, Map<String, String>> groups = new HashMap<>();
|
||||
groups.put("COMMON_CODE", commonCode);
|
||||
groups.put("BANK_CODE", bankCode);
|
||||
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("getmapping", new StubGetMapping(groups));
|
||||
}
|
||||
|
||||
private Object eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-인자 매핑 성공 — 1000 → AAAAA")
|
||||
void testMappingFound() {
|
||||
assertEquals("AAAAA", eval("getmapping(\"COMMON_CODE\", \"1000\")"));
|
||||
assertEquals("BBBBB", eval("getmapping(\"COMMON_CODE\", \"1001\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-인자 매핑 없음 — 원본값 그대로 반환(passthrough)")
|
||||
void testMappingNotFoundPassthrough() {
|
||||
assertEquals("9999", eval("getmapping(\"COMMON_CODE\", \"9999\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-인자 그룹 자체가 없음 — 원본값 그대로 반환")
|
||||
void testGroupNotFoundPassthrough() {
|
||||
assertEquals("1000", eval("getmapping(\"NO_SUCH_GROUP\", \"1000\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-인자 매핑 성공 — default 무시하고 매핑값 반환")
|
||||
void testMappingFoundWithDefault() {
|
||||
assertEquals("AAAAA", eval("getmapping(\"COMMON_CODE\", \"1000\", \"UNKNOWN\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-인자 매핑 없음 — default 반환")
|
||||
void testMappingNotFoundWithDefault() {
|
||||
assertEquals("UNKNOWN", eval("getmapping(\"COMMON_CODE\", \"9999\", \"UNKNOWN\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-인자 그룹 자체가 없음 — default 반환")
|
||||
void testGroupNotFoundWithDefault() {
|
||||
assertEquals("UNKNOWN", eval("getmapping(\"NO_SUCH_GROUP\", \"1000\", \"UNKNOWN\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 리터럴 값 — 1000.0이 아닌 \"1000\"으로 조회")
|
||||
void testNumericValueLookup() {
|
||||
// JEP는 1000 리터럴을 Double로 처리하므로 toKey에서 "1000"으로 변환되어야 매핑됨
|
||||
assertEquals("AAAAA", eval("getmapping(\"COMMON_CODE\", 1000)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 리터럴 값 매핑 없음 — 원본 숫자값 그대로 반환")
|
||||
void testNumericValueNotFoundPassthrough() {
|
||||
Object result = eval("getmapping(\"COMMON_CODE\", 9999)");
|
||||
assertEquals(9999.0, ((Number) result).doubleValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("변수 바인딩 값 매핑 — 필드값 변환")
|
||||
void testVariableValueMapping() {
|
||||
jep.addVariable("code", "1001");
|
||||
assertEquals("BBBBB", eval("getmapping(\"COMMON_CODE\", code)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("한글 그룹/값 매핑 — 081 → 하나은행")
|
||||
void testKoreanGroupAndValue() {
|
||||
assertEquals("하나은행", eval("getmapping(\"BANK_CODE\", \"081\")"));
|
||||
assertEquals("신한은행", eval("getmapping(\"BANK_CODE\", \"088\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("첫 번째 파라미터(group)가 문자열이 아니면 오류")
|
||||
void testNonStringGroupCausesError() {
|
||||
jep.parseExpression("getmapping(1000, \"1000\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "숫자 group 파라미터는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("파라미터 개수가 1개면 오류")
|
||||
void testTooFewParamsCausesError() {
|
||||
jep.parseExpression("getmapping(\"COMMON_CODE\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "파라미터 1개는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.GetTrinomial;
|
||||
|
||||
@DisplayName("GetTrinomial 삼항연산자 함수 테스트")
|
||||
class GetTrinomialFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
|
||||
jep.addVariable("true", Boolean.TRUE);
|
||||
jep.addVariable("false", Boolean.FALSE);
|
||||
|
||||
jep.addFunction("getTrinomial", new GetTrinomial());
|
||||
}
|
||||
|
||||
private Object eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 숫자형 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 = 비교 — 참")
|
||||
void testNumberEqualTrue() {
|
||||
jep.addVariable("a", 10.0);
|
||||
assertEquals("같음", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 = 비교 — 거짓")
|
||||
void testNumberEqualFalse() {
|
||||
jep.addVariable("a", 5.0);
|
||||
assertEquals("다름", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 < 비교 — 참")
|
||||
void testNumberLessThanTrue() {
|
||||
jep.addVariable("score", 70.0);
|
||||
assertEquals("미달", eval("getTrinomial(score, 80, \"<\", \"미달\", \"통과\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 > 비교 — 참")
|
||||
void testNumberGreaterThanTrue() {
|
||||
jep.addVariable("score", 90.0);
|
||||
assertEquals("우수", eval("getTrinomial(score, 80, \">\", \"우수\", \"보통\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 <= 비교 — 경계값")
|
||||
void testNumberLessOrEqual() {
|
||||
jep.addVariable("v", 80.0);
|
||||
assertEquals("이하", eval("getTrinomial(v, 80, \"<=\", \"이하\", \"초과\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 >= 비교 — 경계값")
|
||||
void testNumberGreaterOrEqual() {
|
||||
jep.addVariable("v", 80.0);
|
||||
assertEquals("이상", eval("getTrinomial(v, 80, \">=\", \"이상\", \"미만\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 문자열 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 = 비교 — 참")
|
||||
void testStringEqualTrue() {
|
||||
jep.addVariable("status", "Y");
|
||||
assertEquals("활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 = 비교 — 거짓")
|
||||
void testStringEqualFalse() {
|
||||
jep.addVariable("status", "N");
|
||||
assertEquals("비활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 < 비교 — 사전순")
|
||||
void testStringLessThan() {
|
||||
jep.addVariable("s", "apple");
|
||||
assertEquals("앞", eval("getTrinomial(s, \"banana\", \"<\", \"앞\", \"뒤\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 결과값 타입 — 숫자 반환
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("결과값이 숫자형")
|
||||
void testNumericReturnValue() {
|
||||
jep.addVariable("flag", "Y");
|
||||
Object result = eval("getTrinomial(flag, \"Y\", \"=\", 1, 0)");
|
||||
assertTrue(result instanceof Number);
|
||||
assertEquals(1.0, ((Number) result).doubleValue());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// boolean 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("boolean = 비교 — 참 (JEP는 boolean 리터럴 미지원, 변수로 바인딩)")
|
||||
void testBooleanEqual() {
|
||||
jep.addVariable("b", Boolean.TRUE);
|
||||
jep.addVariable("bTrue", Boolean.TRUE);
|
||||
assertEquals("참", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("boolean = 비교 — 거짓")
|
||||
void testBooleanEqualFalse() {
|
||||
jep.addVariable("b", Boolean.FALSE);
|
||||
jep.addVariable("bTrue", Boolean.TRUE);
|
||||
assertEquals("거짓", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 실전 Transformer 표현식 패턴
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("실전 패턴 — true/false 결과값 (Boolean 타입 반환 검증)")
|
||||
void testBooleanLiteralReturnValue() {
|
||||
// JEP addStandardConstants() 에는 true/false 가 없다.
|
||||
// Parser 가 이를 Boolean 으로 직접 등록하므로(Parser.addBooleanConstants),
|
||||
// 결과값은 숫자 1.0/0.0 이 아니라 Boolean 이다. setUp 도 동일하게 바인딩한다.
|
||||
jep.addVariable("intVal", 10.0);
|
||||
Object result = eval("getTrinomial(intVal, 10, \"=\", true, false)");
|
||||
assertTrue(result instanceof Boolean,
|
||||
"true/false 결과값이 Boolean 타입이어야 함 (실제: " + result.getClass().getSimpleName() + ")");
|
||||
assertEquals(Boolean.TRUE, result,
|
||||
"조건 참일 때 true 반환 기대");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("실전 패턴 — dot 경로 변수(AGW_…SRCS.INT_VALUE) + boolean 결과값")
|
||||
void testRealWorldExpression() {
|
||||
// Transformer 실사용 표현식:
|
||||
// getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, "=", true, false)
|
||||
//
|
||||
// JEP에서 dot(.)은 element-access 연산자로 파싱됨
|
||||
// → 파싱 성공 여부와 변수 바인딩 방법을 확인하기 위한 테스트
|
||||
String expr = "getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, \"=\", true, false)";
|
||||
|
||||
// [1] 파싱만 먼저 검증
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
|
||||
// [2] 파싱 후 JEP 심볼 테이블에 등록된 변수명 확인
|
||||
// dot-expression 이므로 "AGW_TESTREQ10031S1_SRCS" 가 심볼로 잡힐 것으로 예상
|
||||
java.util.Set<String> symbols = jep.getSymbolTable().keySet();
|
||||
assertTrue(
|
||||
symbols.contains("AGW_TESTREQ10031S1_SRCS") || symbols.contains("AGW_TESTREQ10031S1_SRCS.INT_VALUE"),
|
||||
"심볼 테이블에 소스 변수가 포함되어야 함. 실제 심볼: " + symbols
|
||||
);
|
||||
|
||||
// [3] 심볼에 값 바인딩 후 평가
|
||||
// dot-expression(AGW_…SRCS.INT_VALUE)은 JEP vector element 접근이므로
|
||||
// 스칼라 바인딩 시 평가 오류 발생 가능 → 오류 메시지로 대응 방법 확인
|
||||
jep.addVariable("AGW_TESTREQ10031S1_SRCS", 10.0);
|
||||
|
||||
Object result = jep.getValueAsObject();
|
||||
if (jep.hasError()) {
|
||||
// dot element-access 실패 시: Transformer는 심볼 전체 경로를 변수명으로 등록해야 함
|
||||
// → Parser.addVariable("AGW_TESTREQ10031S1_SRCS.INT_VALUE", value) 방식 필요
|
||||
jep.addVariable("AGW_TESTREQ10031S1_SRCS.INT_VALUE", 10.0);
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "재파싱 오류: " + jep.getErrorInfo());
|
||||
result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
}
|
||||
|
||||
assertTrue(result instanceof Boolean,
|
||||
"결과값이 Boolean 타입이어야 함 (실제: " + (result == null ? "null" : result.getClass().getSimpleName()) + ")");
|
||||
assertEquals(Boolean.TRUE, result,
|
||||
"INT_VALUE=10, 비교값=10, 연산자=등호 → 참(true) 반환 기대");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.eactive.eai.transformer.message.MessageSetDataException;
|
||||
* - INT_VALUE : INT, length=5, decimal=0
|
||||
* - LONG_VALUE : LONG, length=10, decimal=0
|
||||
* - BIG_VALUE : BIGDECIMAL, length=10, decimal=5 → 정수부 최대 4자리
|
||||
* (length 는 소수점(.) 문자를 포함한 전체 길이 규약)
|
||||
* - BIG15_VALUE : BIGDECIMAL, length=15, decimal=0 → 정수부 최대 15자리
|
||||
* - STR_VALUE : STRING, length=20
|
||||
*
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.ReplaceAll;
|
||||
|
||||
@DisplayName("ReplaceAll 문자열 치환 함수 테스트")
|
||||
class ReplaceAllFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("replaceAll", new ReplaceAll());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("단일 치환 — 홍길동 → 홍길순")
|
||||
void testSingleReplace() {
|
||||
jep.addVariable("name", "홍길동");
|
||||
assertEquals("홍길순", eval("replaceAll(name, \"동\", \"순\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("다중 치환 — 모든 매칭 문자열 변경")
|
||||
void testMultipleOccurrences() {
|
||||
// "aababab" 안의 'a' 4개가 모두 'X'로 치환 → "XXbXbXb"
|
||||
jep.addVariable("src", "aababab");
|
||||
assertEquals("XXbXbXb", eval("replaceAll(src, \"a\", \"X\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("치환 대상 없음 — 원본 그대로 반환")
|
||||
void testNoMatch() {
|
||||
jep.addVariable("src", "hello");
|
||||
assertEquals("hello", eval("replaceAll(src, \"z\", \"X\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("치환 후 문자열이 빈 문자열 (JEP는 \"\" 리터럴 미지원 → 변수 바인딩)")
|
||||
void testReplaceWithEmpty() {
|
||||
jep.addVariable("src", "abc123def");
|
||||
assertEquals("abcdef", eval("replaceAll(src, \"123\", \"\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("날짜 구분자 변환 — '-' → '/'")
|
||||
void testDateSeparator() {
|
||||
jep.addVariable("date", "2024-05-08");
|
||||
assertEquals("2024/05/08", eval("replaceAll(date, \"-\", \"/\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("리터럴만 사용")
|
||||
void testLiteralsOnly() {
|
||||
assertEquals("world world",
|
||||
eval("replaceAll(\"hello hello\", \"hello\", \"world\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("비문자열 파라미터 → 오류 발생")
|
||||
void testNonStringParamCausesError() {
|
||||
jep.parseExpression("replaceAll(123, \"1\", \"X\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "숫자 파라미터는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class LayoutRepositoryTest {
|
||||
|
||||
@Test
|
||||
void testFindById() {
|
||||
LayoutEntity layoutEntity = layoutLoaderRepository.getReferenceById("TST_TTST00000002_RES");
|
||||
LayoutEntity layoutEntity = layoutLoaderRepository.getReferenceById("TST_TOSSTEST1_TGT");
|
||||
layoutEntity = layoutEntity.getLayoutItemEntities().stream().findAny().get().getLayoutEntity();
|
||||
assertNotNull(layoutEntity);
|
||||
System.out.println(layoutEntity);
|
||||
|
||||
@@ -26,7 +26,8 @@ import com.eactive.eai.transformer.message.MessageFactory;
|
||||
@ContextConfiguration(classes = { TransformJPATestConfiguration.class })
|
||||
@Sql({ "/com/eactive/eai/transformer/init_tseaitr06.sql", "/com/eactive/eai/transformer/init_tseaitr07.sql",
|
||||
"/com/eactive/eai/transformer/init_tseaitr08.sql", "/com/eactive/eai/transformer/init_tseaitr01.sql",
|
||||
"/com/eactive/eai/transformer/init_tseaitr02.sql", "/com/eactive/eai/transformer/init_tseaitr03.sql" })
|
||||
"/com/eactive/eai/transformer/init_tseaitr02.sql", "/com/eactive/eai/transformer/init_tseaitr03.sql",
|
||||
"/com/eactive/eai/transformer/init_tseaitr01_noitem.sql" })
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TransformManagerTest {
|
||||
|
||||
@@ -240,4 +241,18 @@ class TransformManagerTest {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TSEAITR03(변환 항목 매핑)이 한 건도 없더라도 TSEAITR01/02 에 등록된 변환은 로딩되어야 한다.
|
||||
* loadAll() 이 inner join fetch 이면 이 변환은 transformMap 에서 누락되고,
|
||||
* 호출 시 "전문레이아웃매핑(변환)정보를 찾을 수 없음" 오류가 발생한다.
|
||||
*/
|
||||
@Test
|
||||
void testTransformWithoutTransformItemIsLoaded() {
|
||||
Transform transform = transformManager.getTransform("TRANSFORM_TST_NOITEM_REQ");
|
||||
|
||||
Assert.assertNotNull("TSEAITR03 매핑이 없는 변환도 로딩되어야 함", transform);
|
||||
Assert.assertEquals(0, transform.getTransformItemList().size());
|
||||
Assert.assertEquals("TST_TOSSTEST1_TGT", transform.getTargetLayout().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 변환 마스터(TSEAITR01) / 레이아웃 지정(TSEAITR02)은 있으나
|
||||
-- 변환 항목 매핑(TSEAITR03)은 한 건도 없는 변환.
|
||||
-- loadAll() 이 inner join fetch 를 쓰면 이 변환은 로딩에서 누락된다.
|
||||
INSERT INTO tseaitr01 (cnvsnname,cnvsndesc,lastamndhms,eaisevrdstcd,modfimgtstusdstcd,useyn,verinfo,author) VALUES
|
||||
('TRANSFORM_TST_NOITEM_REQ', 'TSEAITR03 매핑 항목 없음', '20241226104652 ','E',' ','1','0',NULL);
|
||||
|
||||
INSERT INTO tseaitr02 (cnvsnname,loutname,sourcrsultdstcd) VALUES
|
||||
('TRANSFORM_TST_NOITEM_REQ', 'TST_TOSSTEST1_TGT', 'TGT_LAYOUT'),
|
||||
('TRANSFORM_TST_NOITEM_REQ', 'TST_TOSSTEST1_SRC', 'SRC_LAYOUT');
|
||||
Reference in New Issue
Block a user