diff --git a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java index a396282..1704c54 100644 --- a/src/main/java/cokr/xit/foundation/component/AbstractMapper.java +++ b/src/main/java/cokr/xit/foundation/component/AbstractMapper.java @@ -1,5 +1,9 @@ package cokr.xit.foundation.component; +import java.util.ArrayList; +import java.util.List; +import java.util.function.ToIntFunction; + import cokr.xit.foundation.ApplicationException; import cokr.xit.foundation.Assert; import cokr.xit.foundation.UserInfo; @@ -105,4 +109,41 @@ public interface AbstractMapper extends Assert.Support, Convert.Support { default ApplicationException applicationException(Throwable t) { return ApplicationException.get(null); } + + /**주어진 객체들을 주어진 함수로 실행하고 각각의 실행결과를 목록으로 반환한다. + * @param 객체 유형 + * @param proc 객체 실행함수 + * @param objs 저장할 객체 목록 + * @return 실행결과 목록 + */ + default List save(ToIntFunction proc, List objs) { + ArrayList results = new ArrayList<>(); + + for (T obj: objs) + try { + int affected = proc.applyAsInt(obj); + if (affected > 0) + results.add( + affected == 1 ? + ExecInfo.SUCCESS : + new ExecInfo().setAffected(affected) + ); + else if (affected < 1) + results.add( + new ExecInfo() + .setTarget(obj) + .setAffected(affected) + .setSuccess(false) + .setMessage("저장하지 못했습니다.") + ); + } catch (Exception e) { + results.add( + new ExecInfo() + .setTarget(obj) + .setThrowable(e) + ); + } + + return results; + } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/ExecInfo.java b/src/main/java/cokr/xit/foundation/component/ExecInfo.java new file mode 100644 index 0000000..46e9a46 --- /dev/null +++ b/src/main/java/cokr/xit/foundation/component/ExecInfo.java @@ -0,0 +1,269 @@ +package cokr.xit.foundation.component; + +import java.util.List; + +import cokr.xit.foundation.AbstractObject; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.data.DataObject; + +/**실행결과 정보 + * @author mjkhan + */ +public class ExecInfo extends AbstractObject { + /**데이터처리 유형 + * @author mjkhan + */ + public static enum Type { + /** 데이터 생성 */ + CREATE("C"), + /** 데이터 생성 */ + READ("R"), + /** 데이터 수정 */ + UPDATE("U"), + /** 데이터 삭제 */ + DELETE("D"); + + private final String code; + + private Type(String code) { + this.code = code; + } + + /**데이터처리 유형의 코드를 반환한다. + * @return 데이터처리 유형 코드 + */ + public String getCode() { + return code; + } + + /**코드에 해당하는 Type을 반환한다. + * @param code 데이터처리 유형 코드 + * @return DataProc + */ + public static Type codeOf(String code) { + if (Assert.isEmpty(code)) + return null; + + for (Type type: values()) { + if (type.code.equals(code)) + return type; + } + + throw new IllegalArgumentException("code: " + code); + } + } + + /** 저장된 데이터 수가 1인 실행성공 정보 */ + public static final ExecInfo SUCCESS = new ExecInfo() + .setSuccess(true) + .setAffected(1) + .seal(); + + private Type type; + private Object target; + private int affected; + private String code; + private String message; + private DataObject info; + private Throwable throwable; + private Boolean success; + private boolean sealed; + + private ExecInfo seal() { + sealed = true; + return this; + } + + private void ensureNotSealed() { + if (sealed) + throw new RuntimeException("The ExecInfo is sealed and unmodifiable."); + } + + /**데이터처리 유형을 반환한다. + * @return 데이터처리 유형 + */ + public Type getType() { + return type; + } + + /**데이터처리 유형을 설정한다. + * @param type 데이터처리 유형 + */ + public ExecInfo setType(Type type) { + ensureNotSealed(); + this.type = type; + return this; + } + + /**실행 대상 객체를 반환한다. + * @return 실행 대상 객체 + */ + @SuppressWarnings("unchecked") + public T getTarget() { + return (T)target; + } + + /**실행 대상 객체를 설정한다. + * @param object 실행 대상 객체 + * @return 현재 ExecResult + */ + public ExecInfo setTarget(Object object) { + ensureNotSealed(); + this.target = object; + return this; + } + + /**저장된 데이터 수를 반환한다. + * @return 저장된 데이터 수 + */ + public int getAffected() { + return affected; + } + + /**저장된 데이터 수를 설정한다. + * @param affected 저장된 데이터 수 + * @return 현재 ExecResult + */ + public ExecInfo setAffected(int affected) { + ensureNotSealed(); + this.affected = affected; + return this; + } + + /**결과코드를 반환한다. + * @return 결과코드 + */ + public String getCode() { + return code; + } + + /**결과코드를 설정한다. + * @param code 결과코드 + * @return 현재 ExecResult + */ + public ExecInfo setCode(String code) { + ensureNotSealed(); + this.code = code; + return this; + } + + /**결과메시지를 반환한다. + * @return 결과메시지 + */ + public String getMessage() { + return message; + } + + /**결과메시지를 설정한다. + * @param message 결과메시지 + * @return 현재 ExecResult + */ + public ExecInfo setMessage(String message) { + ensureNotSealed(); + this.message = message; + return this; + } + + public DataObject getInfo() { + return info; + } + + @SuppressWarnings("unchecked") + public T getInfo(String key) { + if (info == null) return null; + return (T)info.get(key); + } + + public ExecInfo setInfo(String key, Object obj) { + ensureNotSealed(); + if (info == null) + info = new DataObject(); + info.put(key, obj); + return this; + } + + /**오류를 반환한다. + * @return 오류 + */ + public Throwable getThrowable() { + return throwable; + } + + /**오류를 설정한다. + * @param throwable 오류 + * @return 현재 ExecResult + */ + public ExecInfo setThrowable(Throwable throwable) { + ensureNotSealed(); + this.throwable = throwable; + return this; + } + + /**오류가 설정되어 있으면 throw한다. + * @return 오류가 설정되어 있지 않으면 false + */ + public boolean throwThrowable() { + if (throwable != null) + throw runtimeException(throwable); + return false; + } + + /**성공여부를 반환한다. + * @return 성공여부 + */ + public boolean isSuccess() { + return throwable == null + && ifEmpty(success, Boolean.TRUE); + } + + /**성공여부를 설정한다. + * @param success 성공여부 + * @return 현재 ExecResult + */ + public ExecInfo setSuccess(boolean success) { + ensureNotSealed(); + this.success = success; + return this; + } + + /**성공한 결과로 영향받은 데이터수를 반환한다. + * @param results 실행결과 목록 + * @return 성공한 결과로 영향받은 데이터수 + */ + public static final int getAffected(List results) { + return results.stream() + .mapToInt(ExecInfo::getAffected) + .sum(); + } + + /**모든 결과가 성공인지 반환한다. + * @param results 실행결과 목록 + * @return 모든 결과의 성공 여부 + */ + public static final boolean isSuccess(List results) { + for (ExecInfo result: results) { + if (!result.isSuccess()) + return false; + } + return true; + } + + public static final List getTargets(List results, Boolean success) { + return results.stream() + .filter(result -> + (success != null ? success.equals(result.isSuccess()) : true) + && null != result.getTarget() + ) + .map(result -> { + T t = result.getTarget(); + return t; + }) + .toList(); + } + + public static final boolean throwThrowable(List results) { + for (ExecInfo result: results) + result.throwThrowable(); + return false; + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/QueryRequest.java b/src/main/java/cokr/xit/foundation/component/QueryRequest.java index a591cba..97b5501 100644 --- a/src/main/java/cokr/xit/foundation/component/QueryRequest.java +++ b/src/main/java/cokr/xit/foundation/component/QueryRequest.java @@ -8,5 +8,4 @@ package cokr.xit.foundation.component; * @author mjkhan */ public class QueryRequest extends ServiceRequest { - private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/component/ServiceRequest.java b/src/main/java/cokr/xit/foundation/component/ServiceRequest.java index e0db135..5ad8123 100644 --- a/src/main/java/cokr/xit/foundation/component/ServiceRequest.java +++ b/src/main/java/cokr/xit/foundation/component/ServiceRequest.java @@ -1,17 +1,13 @@ package cokr.xit.foundation.component; -import java.io.Serializable; - -import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.AbstractObject; /**서비스 요청을 위한 정보를 갖는 객체의 베이스 클래스.
* 서비스를 요청할 때 전달해야할 정보가 많을 경우(4개 이상) 이 클래스를 상속받아 요청을 정의한다.
* 전달해야할 정보가 많지 않으면 서비스 요청을 정의할 필요없다. * @author mjkhan */ -public abstract class ServiceRequest extends AbstractComponent implements Serializable { - private static final long serialVersionUID = 1L; - +public abstract class ServiceRequest extends AbstractObject { private String type, by, diff --git a/src/main/java/cokr/xit/foundation/component/ServiceResponse.java b/src/main/java/cokr/xit/foundation/component/ServiceResponse.java index ce49483..79201db 100644 --- a/src/main/java/cokr/xit/foundation/component/ServiceResponse.java +++ b/src/main/java/cokr/xit/foundation/component/ServiceResponse.java @@ -1,7 +1,10 @@ package cokr.xit.foundation.component; -import cokr.xit.foundation.AbstractComponent; -import cokr.xit.foundation.Assert; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import cokr.xit.foundation.AbstractObject; import cokr.xit.foundation.data.DataObject; /**서비스 응답으로 반환하는 정보를 갖는 객체의 베이스 클래스.
@@ -9,133 +12,29 @@ import cokr.xit.foundation.data.DataObject; * 결과 정보가 하나일 경우는 응답클래스를 정의할 필요없다. * @author mjkhan */ -public class ServiceResponse extends AbstractComponent { - /**데이터처리 유형 - * @author mjkhan - */ - public static enum Type { - /** 데이터 생성 */ - CREATE("C"), - /** 데이터 생성 */ - READ("R"), - /** 데이터 수정 */ - UPDATE("U"), - /** 데이터 삭제 */ - DELETE("D"); - - private final String code; - - private Type(String code) { - this.code = code; - } - - /**데이터처리 유형의 코드를 반환한다. - * @return 데이터처리 유형 코드 - */ - public String getCode() { - return code; - } - - /**코드에 해당하는 Type을 반환한다. - * @param code 데이터처리 유형 코드 - * @return DataProc - */ - public static Type codeOf(String code) { - if (Assert.isEmpty(code)) - return null; - - for (Type type: values()) { - if (type.code.equals(code)) - return type; - } - - throw new IllegalArgumentException("code: " + code); - } - } - - private Type type; - private int affected; - private String - code, - message; - private Object target; +public class ServiceResponse extends AbstractObject { + private List results; private DataObject info; - private Boolean success; - private Throwable throwable; - /**데이터처리 유형을 반환한다. - * @return 데이터처리 유형 - */ - public Type getType() { - return type; + public List getResults() { + return ifEmpty(results, Collections::emptyList); } - /**데이터처리 유형을 설정한다. - * @param type 데이터처리 유형 - */ - public ServiceResponse setType(Type type) { - this.type = type; - return this; - } - - /**저장된 데이터수를 반환한다. - * @return 저장된 데이터수 - */ - public int getAffected() { - return affected; - } - - /**저장된 데이터수를 설정한다. - * @param 응답 타입 - * @param affected 저장된 데이터수 - * @return 현재 응답 - */ - public ServiceResponse setAffected(int affected) { - this.affected = affected; - return this; - } - - /**결과코드를 반환한다. - * @return 결과코드 - */ - public String getCode() { - return code; - } - - /**결과코드를 설정한다. - * @param 응답 타입 - * @param code 결과코드 - * @return 현재 응답 - */ - public ServiceResponse setCode(String code) { - this.code = code; + public ServiceResponse setResults(List results) { + this.results = results; return this; } - /**결과메시지를 반환한다. - * @return 결과메시지 - */ - public String getMessage() { - return message; - } - - /**결과메시지를 설정한다. - * @param 응답 타입 - * @param message 결과메시지 - * @return 현재 응답 - */ - public ServiceResponse setMessage(String message) { - this.message = message; - return this; - } + public ServiceResponse add(ExecInfo result) { + if (results == null) + results = new ArrayList<>(); + else if (!(results instanceof ArrayList)) { + List tmp = results; + results = new ArrayList<>(tmp); + } - @SuppressWarnings("unchecked") - public T getTarget() { - return (T)target; - } + results.add(result); - public ServiceResponse setTarget(Object target) { - this.target = target; return this; } @@ -149,7 +48,7 @@ public class ServiceResponse extends AbstractComponent { /**응답 추가정보를 초기화 후 반환한다. * @return 응답 추가정보 */ - public DataObject info() { + private DataObject info() { return ifEmpty(info, () -> info = new DataObject()); } @@ -164,66 +63,17 @@ public class ServiceResponse extends AbstractComponent { return this; } - /**성공여부를 반환한다. - * @return 성공여부 - *
  • 디폴트는 true
  • - *
  • 오류가 설정되어 있으면 false
  • - *
  • 설정된 성공여부
  • - *
- */ - public boolean isSuccess() { - return throwable == null - && ifEmpty(success, Boolean.TRUE); - } - - /**성공여부(디폴트는 true)를 설정한다. - * @param 응답 타입 - * @param success 성공여부 - * @return 현재 응답 + /**성공한 결과로 저장한 데이터수를 반환한다. + * @return 성공한 결과로 저장한 데이터수 */ - public ServiceResponse setSuccess(boolean success) { - this.success = success; - return this; - } - - /**오류를 반환한다. - * @return 오류 - */ - public Throwable getThrowable() { - return throwable; - } - - /**오류를 RuntimeException으로 변환하여 설정한다. - * @param 응답 타입 - * @param throwable 오류 - * @return 현재 응답 - */ - public ServiceResponse setThrowable(Throwable throwable) { - this.throwable = throwable; - return this; + public int getAffected() { + return ExecInfo.getAffected(results); } - /**오류가 설정되어 있으면 throw한다. - * @return 오류가 설정되어 있지 않으면 false + /**모든 결과가 성공인지 반환한다. + * @return 모든 결과의 성공 여부 */ - public boolean throwThrowable() { - if (throwable != null) - throw runtimeException(throwable); - return false; - } - - public DataObject toDataObject() { - DataObject map = new DataObject() - .set("affected", getAffected()) - .set("code", getCode()) - .set("message", getMessage()) - .set("target", getTarget()); - - if (!isEmpty(info)) - info.forEach(map::put); - - return map - .set("success", isSuccess()) - .set("throwable", getThrowable()); + public boolean isSuccess() { + return ExecInfo.isSuccess(results); } } \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/DataProc.java b/src/main/java/cokr/xit/foundation/data/DataProc.java index c9642f0..a8014f7 100644 --- a/src/main/java/cokr/xit/foundation/data/DataProc.java +++ b/src/main/java/cokr/xit/foundation/data/DataProc.java @@ -111,4 +111,4 @@ public class DataProc { this.error = Assert.rootCause(error); return this; } -} +} \ No newline at end of file