3 Commits

Author SHA1 Message Date
curry772 cd5cb4c0b5 JSON grid가 아닌 필드배열 처리 패치 2026-07-06 09:23:22 +09:00
curry772 a1d822c2d7 DJB 함수 개발 2026-06-18 08:53:53 +09:00
curry772 05e887f7f1 Merge branch 'feature/bytes-decimal-validation' 2026-05-20 15:10:04 +09:00
10 changed files with 842 additions and 205 deletions
@@ -36,6 +36,8 @@ public class Parser implements Serializable {
getJep().setAllowAssignment(true);
getJep().setAllowUndeclared(true);
addBooleanConstants();
try {
addGenericFunctions();
} catch (Exception e) {
@@ -89,6 +91,13 @@ 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")
@@ -144,8 +153,9 @@ public class Parser implements Serializable {
private void addFunction(String name, PostfixMathCommand command) {
if(name == null || command == null) {
logger.warn("addFunction ("+name+","+command+") skip null");
return;
return;
}
getJep().addFunction(name, command); // 원본 대소문자 (camelCase 표현식 지원)
getJep().addFunction(name.toUpperCase(), command);
getJep().addFunction(name.toLowerCase(), command);
}
@@ -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,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} - 비교 연산자 문자열 (=, &lt;, &gt;, &lt;=, &gt;=)</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));
}
}
@@ -10,6 +10,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 +19,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 +39,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
@@ -98,59 +102,28 @@ public class JSONMessage extends Message {
throw new InvalidDataTypeException("Not able to generate JSON messages with data. data type = " + data.getClass().getName(), data);
}
}
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());
setValue(this.data, getName());
}
else {
setValue(node, getName());
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 {
public void setValue(JsonNode node, String itempPath) throws InvalidAccessException {
try {
// this.value = Converter.convert(value, getDataTypeId());
/**
* occRef 설정을 위해
*/
HashMap validationTargets = new HashMap();
HashMap validationTargets = new HashMap();
doSetValue(node, validationTargets, itempPath);
// validation
@@ -172,22 +145,33 @@ public class JSONMessage extends Message {
} catch(Exception e) {
throw new InvalidAccessException("setValue error", getItemPath(), null, e);
}
}
}
protected void doSetValue(JsonNode node, HashMap validationTargets, String parentPath) {
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();
Iterator<String> fieldNames = node.fieldNames();
if(StringUtils.isNotEmpty(parentPath)) {
item = getOrCreateItem(denomalizeXPath(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 +190,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 {
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) {
@@ -283,12 +251,12 @@ public class JSONMessage extends Message {
case Types.TYPE_FLOAT :
setFloat( itemPath, new Float(value) );
break;
case Types.TYPE_BOOLEAN :
case Types.TYPE_BOOLEAN :
setObject( itemPath, new Boolean(value) );
break;
case Types.TYPE_STRING :
setString(itemPath, value);
break;
break;
default :
setString(itemPath, value);
break;
@@ -370,9 +338,8 @@ public class JSONMessage extends Message {
if( parent instanceof ObjectNode ) {
obj = (ObjectNode)parent;
String val = getStringValue(item.getValue(),item);
int itemType = item.getDataTypeId();
String val = getStringValue(item.getValue(),item);
int itemType = item.getDataTypeId();
if (isMask && "Y".equals(item.getMask()) && (Types.TYPE_STRING == itemType )){
val = maskString2(val, item.getMaskLength(), item.getMaskOffset());
item.setValue(val);
@@ -385,8 +352,9 @@ public class JSONMessage extends Message {
arr = mapper.createArrayNode();
setJsonArrayValue(arr, item);
obj.set(prefix+item.getName(), arr);
}else{
arr.add(value);
}else{
// 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;
@@ -443,38 +411,11 @@ public class JSONMessage extends Message {
private void setJsonNodeValue(ObjectNode node, String fieldName, Item item) {
int itemType = item.getDataTypeId();
Object value = item.getValue();
if(value == null) {
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,7 +476,7 @@ public class JSONMessage extends Message {
arrayNode.add( (String) value);
break;
default :
arrayNode.add( (String) value);
arrayNode.add( (String) value);
}
}
@@ -583,8 +520,7 @@ public class JSONMessage extends Message {
}
@Override
public Object getData() {
if (bStaled) {
if (bStaled || this.data == null) {
try {
this.data = getJSONData();
} catch(Exception e) {
@@ -604,7 +540,6 @@ public class JSONMessage extends Message {
return null;
}
if(this.data == null) return null;
if(isPretty) return data.toPrettyString();
else return data.toString();
}
@@ -703,6 +638,36 @@ public class JSONMessage extends Message {
}
}
private boolean isValid(String value,int length,int pointLength){
String[] data = value.replaceAll(" ", "").split("[.]");
if (pointLength > 0 ){
if (data.length ==1){
if (data[0].length() > length - pointLength - 1 ){ //정수부 오류
return false;
}
}else if (data.length ==2){
if (data[0].length() > length - pointLength - 1 ){ //정수부 오류
return false;
}
if (data[1].length() > pointLength ){ //실수부 오류
return false;
}
}else{
//오류
return false;
}
}else{
if (data.length != 1){ //소수점이 들어와서 오류
return false;
}
if (data[0].length() > length - pointLength - 1){ //정수부 오류
return false;
}
}
return true;
}
// private Object toJsonObjectType(int itemType, String value) {
// Object jsonValueObject = null;
//
@@ -735,71 +700,12 @@ public class JSONMessage extends Message {
private String getHeader() {
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);
}
@@ -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,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 결과값 (JEP 상수 1.0/0.0 반환 검증)")
void testBooleanLiteralReturnValue() {
// JEP addStandardConstants()가 true=1.0, false=0.0 으로 등록하는지 확인
// → 등록되어 있다면 변수 바인딩 없이 숫자로 반환됨
// → 등록 안 됐다면 파싱 오류 또는 undeclared 변수(NaN)로 처리됨
jep.addVariable("intVal", 10.0);
Object result = eval("getTrinomial(intVal, 10, \"=\", true, false)");
assertTrue(result instanceof Number,
"true/false 결과값이 Number 타입이어야 함 (실제: " + result.getClass().getSimpleName() + ")");
assertEquals(1.0, ((Number) result).doubleValue(), 0.0,
"조건 참일 때 true(=1.0) 반환 기대");
}
@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 Number,
"결과값이 Number 타입이어야 함 (실제: " + (result == null ? "null" : result.getClass().getSimpleName()) + ")");
assertEquals(1.0, ((Number) result).doubleValue(), 0.0,
"INT_VALUE=10, 비교값=10, 연산자=등호 → 참(true=1.0) 반환 기대");
}
}
@@ -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(), "숫자 파라미터는 오류가 발생해야 합니다");
}
}
}